[Python]工厂模式

作者: 四明羽客 | 来源:发表于2018-04-05 23:39 被阅读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,也只要在工厂类里修改即可。

    相关文章

      网友评论

      本文标题:[Python]工厂模式

      本文链接:https://www.haomeiwen.com/subject/glypqftx.html