[Python]工厂模式

介绍

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容

  • 标签: python 设计模式 工厂模式 引子 如何将实例化具体类的代码从应用中抽离,或者封装起来,使它们不会干扰...
    plectrum阅读 1,164评论 2 7
  • 工厂模式,一个工厂实例化一个指定类。
    虾想家阅读 278评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 我是很不喜欢小猫小狗这类看起来毛茸茸的生物的。 但是我觉得如果连一个喜欢的小动物都没有,会给人很冷漠的感觉。 所以...
    鱼不敢当阅读 144评论 0 0
  • 忙着工作,忙着生活,已经很少有时间静下来看书,996的工作,忙里偷闲花几个晚上看完了阿西莫夫的《永恒的终结》,铺垫...
    玄猫大人阅读 441评论 0 0