抽象工厂模式

抽象工厂模式一般用在我们的一个系统根据配置和平台的问题拥有多个可能实现的情况。调用代码从抽象工厂中请求对象,但不知道哪个类的对象会被返回。返回的底层实现可能取决于多种因素,如当前位置、操作系统和本地配置。

实际工作中常见的抽象工厂模式的例子包括独立于操作系统的工具包、数据库后端,以及针对具体国家的格式化或计算器代码。一个独立于操作系统的GUI工具包可能会使用一个抽象工厂模式使它能在Windows下返回一套winform组件,在mac下返回Cocoa组件,在Genome下返回GTK控件以及在KDE下返回QT控件。

抽象工厂的UML类图较难理解,通过例子可以使我们更容易理解抽象工厂模式。让我们创建一套依赖于特定语言环境的格式化工具来帮助我们格式化日期和货币。这将是可以挑选特定工厂的抽象工厂类,以及一对包含实际具体例子的工厂,一个是法国,另一个是美国。他们每一个都可以创建格式化的日期和时间对象,并且可以被查询,格式化成指定的值。下面是它的结构图:

在Python中,我们没有必要实现任何接口类,这样我们就可以启用DateFormatter,CurrencyFormatter和FormatterFactory。格式化类本身是非常简单的:

class FranceDateFormatter(object):
    def format_date(self, y, m, d):
        y, m, d = (str(x) for x in (y,m,d))
        y = '20'+y if len(y) == 2 else y
        m = '0'+m if len(m)==1 else m 
        d = '0'+d if len(d)==1 else d 
        return('{0}/{1}/{2}'.format(d,m,y))
    
class USADateFormatter(object):
    def format_date(self, y, m, d):
        y, m, d = (str(x) for x in (y,m,d))
        y = '20'+y if len(y) == 2 else y
        m = '0'+m if len(m)==1 else m 
        d = '0'+d if len(d)==1 else d 
        return('{0}-{1}-{2}'.format(d,m,y))

class FranceCurrencyFormatter(object):
    def format_currency(self, base, cents):
        base, cents = (str(x) for x in (base, cents))
        if len(cents)==0: 
            cents='00'
        elif len(cents)==1:
            cents = '0'+cents
        
        digits = []
        for i,c in enumerate(reversed(base)):
            if i and not i%3:
                digits.append(' ')
            digits.append(c)
        base = ''.join(reversed(digits))
        return '{0}({1}'.format(base, cents)

class USACurrencyFormatter(object):
    def format_currency(self, base, cents):
        base, cents = (str(x) for x in (base, cents))
        if len(cents)==0: 
            cents='00'
        elif len(cents)==1:
            cents = '0'+cents
        
        digits = []
        for i,c in enumerate(reversed(base)):
            if i and not i%3:
                digits.append(',')
            digits.append(c)
        base = ''.join(reversed(digits))
        return '${0}({1}'.format(base, cents)    

这些类使用一些基本的字符串操作来吧各种可能的输入(整数、不同长度的字符串及其他)变为如下格式:

这里的代码可以对输入做更多的验证,但是我们这里只是为了勒脚抽象工厂模式,没必要做那些验证。现在我们已经设置了格式化程序,我们只需要创建格式化工厂:

class USAFormatterFactory(object):
    def create_date_formatter(self):
        return USADateFormatter()
    
    def create_currency_formatter(self):
        return USACurrencyFormatter()

class FranceFormatterFactory(object):
    def create_date_formatter(self):
        return FranceDateFormatter()
    
    def create_currency_formatter(self):
        return FranceCurrencyFormatter()

现在,我们只需要创建可以挑选出合适格式化工具的代码。由于这些事情只需要被创建一次,我们可以让它变成一个单例,只不过单例在Python中并不是非常有用。那就让我们将当前的格式化器变成模块级别变量:

country_code = 'US'
factory_map = {
    'US': USAFormatterFactory,
    'FR': FranceFormatterFactory
}

formatter_factory = factory_map.get(country_code)()

在这个例子中,我们硬编码了当前国家代码;实际上,我们需要仔细考虑语言环境,操作系统或配置文件来选择代码。它通过字典将国家代码与工厂类联系在一起。因此,我们只要从字典中选择正确的类并将它实例化就可以了。

当我们要为更多的国家添加支持时,可以很容易看出需要做什么事情:我们只需要创建新的格式化类和抽象工厂本身。

抽象工厂通常返回一个单例对象,虽然这不是必要的;在我们代码中,每当它被调用时,都会返回格式化工具的一个新实例。格式化工具没有理由不能被储存为实例变量并且每个工厂都要返回同一个实例。

回头再看看这些例子,我们再一次发现,似乎有很多适用于工厂的样板代码在Python中似乎是不需要的。通常,每个工厂类型(如美国和法国)使用单独的模块都可以很容易地满足对抽象工厂的需求,然后确保在工厂模块中,我们访问的是正确的模块。这种模块的封装结构通常是这样的:

localize/
    __init__.py
    backends/ 
        __init__.py
        USA.py
        France.py
         ...

这里的诀窍是localize包中的__init__.py包含了将所有请求重定向到正确后端的代码。有多种方式可以这样做。显然,我们在后端模块中重复每个方法,然后通过__init__.py作为代理模块来进行路由选择。但我们可以做的更为整洁。如果我们指导后端永远不会动态的改变(即不会重新启动),我们可以简单地把一些if语句放到__init__.py中来检查当前的国家代码,并在语句中使用通常是不可接受的from .backends.USA import *, 从适当的后端导入所有变量。或者,我们可以将它导入一个后端。并设置current_backend变量指向一个特定的模块:

from .backends import USA, France

if country_code == "US":
    current_backend = USA

根据我们的解决方案,我们的客户端代码只需要简单的调用localize.format_date(from ... import \*)或localize.current_backend.format_date来获得当前国家的格式化日期。最终的结果是比原来的抽象工厂模式更加Python化,而且在多数情况下同样灵活。

参考:
《Python3 面向对象编程》

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

推荐阅读更多精彩内容