介绍
工厂模式是23种设计模式中最常用的模式之一,它又可以细分成简单工厂,工厂方法和抽象工厂。光凭概念很难理解,下面我会通过一个场景来介绍。
🌰情景模拟
爸爸是一个热爱阅读的人,而且涉猎广泛, 喜欢读小说,杂志。只要一有空爸爸就会选择其中的一种来阅读,并且还会做笔记。
把上面的情景转化成程序:
class IBook(object):
def content(self):
raise NotImplementedError
def read(self):
print "阅读"
def note(self):
print "记笔记"
class Novel(IBook):
def content(self):
print "这是本小说"
class Journal(IBook):
def content(self):
print "这是本杂志"
class SimpleFactory(object):
@classmethod
def choice_book(cls, _type):
if _type == 'novel':
return Novel()
elif _type == 'journal':
return Journal()
else:
raise ValueError, 'unknown book type'
if __name__ == '__main__':
import random
types = ['novel', 'journal', 'newspaper']
book = SimpleFactory.choice_book(random.choice(types))
book.content()
book.read()
book.note()
SimpleFactory就是一个简单工厂,选择不同的书籍类进行实例化。
过了一段时间,因为爸爸的好榜样,儿子和妈妈也开始读书,但他们和爸爸喜欢的书不一样,儿子喜欢童话书,科普类书籍,妈妈喜欢看爱情小说,时尚杂志。
如果还是使用简单工厂,那么choice_book这个方法就改成这样
def choice_book(cls, role, _type):
if role == 'father':
if _type == 'novel':
return FatherNovel()
elif _type == 'journal':
return FatherJournal()
else:
raise ValueError, '[Father]Unknown book type'
elif role == 'mother':
if _type == 'novel':
return MotherNovel()
elif _type == 'journal':
return MotherJournal()
else:
raise ValueError, "[Mother]Unknown book type"
elif role == 'son':
if _type == 'novel':
return SonNovel()
elif _type == 'journal':
return SonJournal()
else:
raise ValueError, "[Son]Unknown book type"
else:
raise ValueError, "Unknown role"
可以看到,代码变得又长又不美观,而且也不利于扩展,比如以后爷爷也要加入家庭阅读小组,要修改choice_book方法,另外也不符合“开闭原则”
为了应对这种情况,就要把简单工厂升级为工厂方法。
books.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
class IBook(object): # 产品
def content(self):
raise NotImplementedError
def read(self):
print "阅读"
def note(self):
print "记笔记"
class FatherNovel(IBook):
def content(self):
print "这是本推理小说"
class FatherJournal(IBook):
def content(self):
print "这是本财经杂志"
class SonNovel(IBook):
def content(self):
print "这是本童话书"
class SonJournal(IBook):
def content(self):
print "这是本儿童杂志"
class MotherNovel(IBook):
def content(self):
print "这是本爱情小说"
class MotherJournal(IBook):
def content(self):
print "这是本时尚杂志"
readers.py
import books
class IReader(object): # 工厂类
def read(self, _type):
book = self.choice_book(_type)
book.content()
book.read()
book.note()
def choice_book(self, _type):
raise NotImplementedError
class FatherReader(IReader):
def choice_book(self, _type):
if _type == 'novel':
return books.FatherNovel()
elif _type == 'journal':
return books.FatherJournal()
else:
raise ValueError, '[Father]Unknown book type'
class MotherReader(IReader):
def choice_book(self, _type):
if _type == 'novel':
return books.MotherNovel()
elif _type == 'journal':
return books.MotherJournal()
else:
raise ValueError, "[Mother]Unknown book type"
class SonReader(IReader):
def choice_book(self, _type):
if _type == 'novel':
return books.SonNovel()
elif _type == 'journal':
return books.SonJournal()
else:
raise ValueError, "[Son]Unknown book type"
reading_day.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from readers import FatherReader, MotherReader, SonReader
def main():
print "家庭阅读日..."
father = FatherReader()
mother = MotherReader()
son = SonReader()
print '-' * 5, '爸爸', '-' * 5
father.read('novel')
print '-' * 5, '妈妈', '-' * 5
mother.read('novel')
print '-' * 5, '儿子', '-' * 5
son.read('novel')
if __name__ == '__main__':
main()
输出
家庭阅读日...
----- 爸爸 -----
这是本推理小说
阅读
记笔记
----- 妈妈 -----
这是本爱情小说
阅读
记笔记
----- 儿子 -----
这是本童话书
阅读
记笔记
现在爷爷也要加入家庭阅读小组,不需要改动已有的代码,只要在books.py模块加上爷爷要读的书
class GrandpaNovel(IBook):
def content(self):
print "这是本历史小说"
class GrandpaJournal(IBook):
def content(self):
print "这是本老年杂志"
增加一个GrandpaReader子类
class GrandReader(IReader):
def choice_book(self, _type):
if _type == 'novel':
return books.GrandpaNovel()
elif _type == 'journal':
return books.GrandpaJournal()
else:
raise ValueError, “[Grandpa]Unknown book type"
最后在阅读日里加入爷爷就可以
grandpa = GrandpaReader()
grandpa.read(‘novel’)
可以看到完全没有改动到爸爸,妈妈和儿子的代码,增加爷爷之后,也只要对爷爷进行单元测试即可,符合“开闭原则”
现在我们可以对工厂方法下个定义了
工厂方法定义了一个创建对象的接口,但由子类决定要实例化的类是哪个。工厂方法让类把实例化推迟到之类。
OK,随着家庭阅读日的坚持,家庭成员们的阅读能力逐渐提升,但是对于不同书籍,每个家庭成员的阅读方式和记笔记的方式都有区别,比如爸爸是个狂热的推理迷,对于推理小说喜欢精读,并且记录每个人物关系,但是对于财经杂志则只是速读,也不会记笔记;而妈妈和儿子,不管是小说或时尚杂志,都只是速读,也不会记笔记。
总结起来就是说不同的书有不同的阅读和笔记方法,那么代码上改如何实现呢?
因为read()和note()都是IBook的方法,而所有的书籍类都是继承IBook,那么我们直接在每个书籍类里重写这两个方法就可以实现功能了。这当然可以实现功能,但这样一来会导致出现大量重复代码,不可取!
那么分别把阅读和笔记作为产品,并创建工厂类,将工厂类实例传递给书籍类,让不同的书籍类选择实例化不同的阅读或者笔记的类。这样一来减少了重复代码,而且也方便增加新的阅读和笔记方法,提高了可扩展性。看上去很完美,但是还是存在一个问题,还是要书籍类还是要重写IBook的read()和note()方法,另外如果要增加一个新的动作呢?比如读后处理书籍,保留或者二手转卖。要修改大量的代码,而且也不符合“开闭原则”。
对书籍而言,阅读和笔记这两个产品是有关联性的。应对这种有关联性的产品的场景,抽象工厂就非常合适。来看它的定义
抽象工厂提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体的类。
首先要把阅读和记笔记两个动作抽象出来,做成接口
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# 阅读
class IReadAction(object):
def read(self):
raise NotImplementedError
class SpeedReadAction(IReadAction):
def read(self):
print "速读"
class IntensiveReadAction(IReadAction):
def read(self):
print “精读"
# 笔记
class INoteAction(object):
def note(self):
raise NotImplementedError
class FullNoteAction(INoteAction):
def note(self):
print "详细笔记"
class NoneNoteAction(INoteAction):
def note(self):
print "不做笔记"
接着为阅读和记笔记两个动作创建工厂
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from read_action import SpeedReadAction, IntensiveReadAction
from note_action import FullNoteAction, NoneNoteAction
class ActionFactory(object):
def read_action(self):
raise NotImplementedError
def note_action(self):
raise NotImplementedError
class FatherNovelActionFactory(ActionFactory):
def read_action(self):
IntensiveReadAction().read()
def note_action(self):
FullNoteAction().note()
class FatherJournalActionFactory(ActionFactory):
def read_action(self):
SpeedReadAction().read()
def note_action(self):
NoneNoteAction().note()
class MotherAndSonActionFactory(ActionFactory):
def read_action(self):
SpeedReadAction().read()
def note_action(self):
NoneNoteAction().note()
然后修改books.py模块,具体的Book子类不用改,只要改IBook接口就可以
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
class IBook(object):
def __init__(self, action_factory):
self.action_factory = action_factory
def content(self):
raise NotImplementedError
def read(self):
self.action_factory.read_action()
def note(self):
self.action_factory.note_action()
class FatherNovel(IBook):
def content(self):
print "这是本推理小说"
class FatherJournal(IBook):
def content(self):
print "这是本财经杂志"
class SonNovel(IBook):
def content(self):
print "这是本童话书"
class SonJournal(IBook):
def content(self):
print "这是本儿童杂志"
class MotherNovel(IBook):
def content(self):
print "这是本爱情小说"
class MotherJournal(IBook):
def content(self):
print "这是本时尚杂志"
最后修改readers.py模块,为每种book加上不同的ActionFactory即可
import books
from action import FatherJournalActionFactory, FatherNovelActionFactory, MotherAndSonActionFactory
class IReader(object):
def read(self, _type):
book = self.choice_book(_type)
book.content()
book.read()
book.note()
def choice_book(self, _type):
raise NotImplementedError
class FatherReader(IReader):
def choice_book(self, _type):
if _type == 'novel':
return books.FatherNovel(FatherNovelActionFactory()) # 改变部分
elif _type == 'journal':
return books.FatherJournal(FatherJournalActionFactory()) # 改变部分
else:
raise ValueError, '[Father]Unknown book type'
class MotherReader(IReader):
def choice_book(self, _type):
if _type == 'novel':
return books.MotherNovel(MotherAndSonActionFactory()) # 改变部分
elif _type == 'journal':
return books.MotherJournal(MotherAndSonActionFactory()) # 改变部分
else:
raise ValueError, "[Mother]Unknown book type"
class SonReader(IReader):
def choice_book(self, _type):
if _type == 'novel':
return books.SonNovel(MotherAndSonActionFactory()) # 改变部分
elif _type == 'journal':
return books.SonJournal(MotherAndSonActionFactory()) # 改变部分
else:
raise ValueError, "[Son]Unknown book type"
到此为止我们就完成了,reading_day模块完全不需要在改动,但为了测试爸爸对推理小说和财政杂志的不同阅读和笔记动作,我们让爸爸同时读推理小说和财经杂志。
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from readers import FatherReader, MotherReader, SonReader
def main():
print "家庭阅读日..."
father = FatherReader()
mother = MotherReader()
son = SonReader()
print '-' * 5, '爸爸', '-' * 5
father.read('novel')
print '-' * 5
father.read('journal')
print '-' * 5, '妈妈', '-' * 5
mother.read('novel')
print '-' * 5, '儿子', '-' * 5
son.read('novel')
if __name__ == '__main__':
main()
输出
----- 爸爸 -----
这是本推理小说
精读
详细笔记
-----
这是本财经杂志
速读
不做笔记
----- 妈妈 -----
这是本爱情小说
速读
不做笔记
----- 儿子 -----
这是本童话书
速读
不做笔记
可以看到引入抽象工厂之后,完美的解决了问题,既不需要让书籍类重写父类方法,另外如果要增加处理书籍的动作,也只需要创建处理产品,并加入到ActionFactory工厂中即可,非常方便而且符合“开闭原则”。
总结
说了怎么多,那么使用工厂模式到底有什么好处呢?我的理解:
首先,工厂模式可以解耦,把实例的创建和使用过程分开。比如Class A要调用Class B,那么Class A只是调用Class B的方法,而不用去实例化Class B,实例化的动作交给工厂类。
其次,工厂模式将类实例化过程统一管理起来了,方便以后维护。
比如Class B要改类名了,而它在很多类里都有被实例化,你就要一个个去改,如果使用工厂模式,你只要在工厂类里改就可以了。
再比如,Class B 要替换成Class C,也只要在工厂类里修改即可。