工厂方法模式是一种创建型模式,即一种用于创建对象的模式。举个例子,现在大部分的应用都想要变得international,国际化(特别是现在面临超强监管下的手游行业)。那国际化的第一步是什么呢?当然是语言的国际化,文化、历史这样的先搁一边,得先让人看懂,再来让人喜欢。所以我们第一步就必须让应用能做到能切换语言,要做到切换语言其实很容易,比如下面这段:

if lan is 'Chinese':
	dog = "狗"
	cat = "猫"
else lan is 'English':
	dog = "dog"
	cat = "cat"

这样做简单是简单,但不方便,非常不方便。一旦你要新增一门语言,比如法语,得加一个else;新增德语,又得加一个else,无穷无尽,而且每次都得修改代码。

谨记一点:如果你不清楚你的else将会有多少个,只要有可能有很多个,那就一个也别用,用设计模式重构

好,下面来改进一下:

# coding=UTF-8

def get_localizer(lan):
    locales = {}
    locales["Chinese"] = dict(login='登录', register='注册')
    locales["English"] = dict(login='Login', register='Sign out')
    return locales[lan]

if __name__ == "__main__":
    lan = "Chinese"
    locale = get_localizer(lan)
    print(locale.dog)
    print(locale.cat)

现在我们如果要添加一门语言,只需要修改get_localizer函数,往里面添加一门语言就好。比起上一个方便了很多。

但注意,现在还要增加语言还要修改,所有我们还是违背了开闭原则。

面向对象开闭原则:对扩展开放,对修改关闭

上面的那段代码很明显没有对修改关闭,那怎么样连get_localizer都不要修改呢?答曰:使用工厂方法模式:

# coding=UTF-8
class ChineseGetter:

    """A simple localizer a la gettext"""

    def __init__(self):
        self.trans = dict(dog="狗", cat="猫")

    def get(self, msgid):
        """We'll punt if we don't have a translation(如果没有就直接返回,第二个参数是默认值)"""
        return self.trans.get(msgid, str(msgid))


class EnglishGetter:

    """Simply echoes the msg ids"""

    def get(self, msgid):
        return str(msgid)


# Create our localizers
e, c = EnglishGetter(), ChineseGetter()
# Localize some text
for msgid in "dog parrot cat bear".split():
    print(e.get(msgid), c.get(msgid))

### OUTPUT ###
# dog 狗
# parrot parrot
# cat 猫
# bear bear
	

我们创建了两个语言工厂,一个ChineseGetter,专门创建中文;一个EnglishGetter,专门创建英文。现在想新增,不过是新增一个类,对扩展开放;而使用哪种语言,在实际过程中,一般都写在配置文件或数据库中可供动态修改,不需修改代码(上述是测试需要,两个都使用了),对修改也关闭了。

原代码取自https://github.com/faif/python-patterns,但对于原代码有一些修改,把GreekGetter换成了ChineseGetter,并且去掉了原代码中的get_localizer,使之更符合开闭原则,我修改过的代码在这里

注:如果是学过Java、C++设计模式的同学,会发现我们的代码里没有用到抽象类,这是Python这种动态类型独有的功能——鸭子类型。即“如果一只鸟像鸭子一样呱呱叫,像鸭子一样走路,那它就是鸭子”。我们的XXXGetter只要实现了get(self, msgid) 方法,那么不管它继承自谁,都可以执行这个方法。