Pythoner集中营程序猿阵线联盟-汇总各类技术干货程序员

[Python]工厂模式

2018-04-05  本文已影响133人  四明羽客

介绍

工厂模式是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,也只要在工厂类里修改即可。

上一篇下一篇

猜你喜欢

热点阅读