使用python解释设计模式
原文地址
有没有好奇过设计模式是什么呢?在这篇文章中,我们将了解为什么设计模式是重要的,同时也会给出一些python的例子,解释为什么以及在什么时候使用设计模式。
什么是设计模式?
设计模式是我们日常遇到的编程问题时的优化过的可重用的解决方案。一个设计模式不是可以直接嵌入到我们系统中的一个库或者一个类;它的含义要比这广泛的多。它是一个必须在特地场景实现的模板。同时设计模式也不是针对特地语言的。一个好的设计模式应该可以在几乎所有-如果不是全部的话-的语言实现,这取决于语言的能力。最重要的是,任何设计模式都是一把双刃剑。如果在错误的场景使用,它将会给你带来很多麻烦。然而,在合适的场景使用,它将成为你的救世主。
起初,你可以把设计模式看做解决特定一类问题的非常深刻巧妙的方式。就像是一群人考虑了一个问题的各方面然后想出一个最通用,灵活的解决方案。这个问题可能是你之前看见过的解决过的,但是你的解决方案可能不具备设计模式中的完备性。
尽管他们把这叫做“设计模式”,但这和设计领域并没有什么关系。一种模式和传统意义上关于分析,设计和实现的思想相差甚远,一种模式更多地是象征程序中体现的一个完整的想法,因此有时候设计模式会出现在分析阶段或者更高层的设计阶段。这是挺有趣的一件事因为一个模式在代码层面有一个直接的实现所以在底层设计或实现之前你可能看不见它的身影。(事实上你可能在这些阶段之前都不会意识到自己需要一个特定的设计模式)
模式的基本概念也可以被当作是程序设计的基本概念:添加一个抽象层。当你在抽象某个事物的时候,你也在将特定的细节独立出来,这当中最强烈的动机便是把变化的与不变的分开。换个说法就是一旦你在程序中找到了由于种种原因可能变动的部分,你会想要防止它们在代码中产生其他的变动。这不仅使得代码更加容易维护,并且更加易读。
通常,开发一个优雅而易维护的架构最困难的部分在于探究“变化的矢量(vector)”(在这里vector指最大的梯度而不是容器类)。意思就是找到在你的系统中发生变化的最重要的东西。换句话说找到你最巨大的开销。一旦你找到了改变的vector,你便有了一个架构设计的重点。
所以设计模式的目的是隔离你代码中的改变。如果以这个角度来看的话,你已经看过一些设计模式了。比如说,继承可以被看做是一种设计模式(尽管它是被编译器实现的)它让你可以表达有同样接口的对象在行为上的差异。组合也可以被看做是一个设计模式,因为它让你静态或动态地改变类的对象以及类工作的方式。
另一个设计模式是迭代器,在语言中以for 循环的形式实现。迭代器允许你隐藏容器实现的细节,就像你在一步一步遍历元素。于是你可以写出在一个序列上处理所有元素的通用代码而不需要考虑序列建立的方式,因此你的通用代码可以被任何生成出迭代器的对象使用。
有三种基本的设计模式:
结构化模式通常处理实体之间的关系,使他们之间的合作沟通更加简单。
创造性模式提供实例化的机制,使得在特殊场景下创建对象变得更加方便。
行为模式通常被用在实体之间的通信,使得他们之间的通信更加简单和灵活。
为什么我们需要设计模式
设计模式在原则上是程序设计中经过深思熟虑的解决方案。许多程序员在之前遇到过类似的问题,然后用这些方案来解决它们。如果你碰上了类似的问题,为什么二次创造一个已经有给定答案的方案呢?
例子
让我们想象一下你需要一个方法来合并两个不同的类。而这两个类在现有的系统的不同地方中已经被使用很多了,这使得改变已有代码非常困难。如果要合并的话,改变已有代码也需要测试所有改变过的代码,由于系统依赖不同的组件,很大可能会形成新的bug。我们可以换个方法,利用策略模式(strategy pattern)和适配器模式(adapter pattern)的变种,能够完美处理这种情景。
class StrategyAndAdapterExampleClass():
def __init__(self, context, class_one, class_two):
self.context = context
self.class_one = class_one
self.class_two = class_two
def operation1(self):
if self.context == "Context_For_Class_One":
self.class_one.operation1_in_class_one_context()
else:
self.class_two.operational_in_class_two_context()
非常简单不是吗?现在,让我们仔细地看看策略模式。
策略模式(Strategy Pattern)
策略模式是一种让你在运行时决定程序具体行为的行为模式。你可以将两个类中不同的算法封装起来,然后在运行时(runtime)决定你想采取哪个策略。
在我们上面的例子里,策略的选择是基于context变量在实例化类的时候被赋予的值。如果context变量被赋值为class_one,它将使用class_one,反之亦然。
在哪可以使用
想象一下你正在开发一个创建或者更新用户纪录的类。它需要相同的输入(姓名,地址,手机,等)但是取决于给定的情况,它在更新和创建时会调用不同的函数。你当然可以使用if else语句来实现,但这样的话你就必须写很多个if else语句。直接说明你的上下文不是方便多了?
class User():
def create_or_update(self, name, address, mobile, userid=None):
if userid:
# it means the user doesn't exist yet, create a new record
else:
# it means the user already exists, just update based on the given userid
通常情况下的策略模式会将你的代码封装到另外一个类中,但在该例中,使用另外的类会很浪费。记住你不需要完全按照模板来操作。只要符合原则,能解决问题即可。
适配器模式(Adapter Pattern)
适配器模式是一个结构化的设计模式,它让你使用一个不同的接口来重用一个类,使它能在一个调用不同方法的系统中使用。
该模式也允许你更改从client class接受的输入,使其与被适配对象的函数兼容。
如何使用?
适配器类的另一个名称叫做封装类,它让你将一系列操作封装在一个类里然后在合适的场合重用这些操作。
比较下面两种实现。
非适配器方案
class User(object):
def create_or_update(self):
pass
class Profile(object):
def create_or_update(self):
pass
user = User()
user.create_or_update()
profile = Profile()
profile.create_or_update()
如果我们需要在不同的地方重新做一次,或者在另一个项目中重用这份代码,我们需要从头开始再写一遍。
更好的选择
account_domain = Account()
account_domain.NewAccount()
在此处,我们有一个封装类,为Account:
class User(object):
def create_or_update(self):
pass
class Profile(object):
def create_or_update(self):
pass
class Account():
def new_account(self):
user = User()
user.create_or_update()
profile = Profile()
profile.create_or_update()
按这种方式,在需要的时候可以重用你的Account,当然你也可以在你的主类下包装其他的类。
工厂模式
工厂模式是一种创造型设计模式,正如其名,它像工厂一样生产出对象实例。
该模式的主要目的是将可能扩展到不同类中的创建过程封装进单个函数。通过提供合适的上下文,它会返回合适的对象。
何时使用它?
使用工厂模式的最佳时机是你的单个实体有多个变种时。假设你有一个按钮类,该类有不同的变化,比如图像按钮,输入按钮,flash按钮。你可以会根据不同场景创建不同的按钮,此时便可以使用工厂来为你创造按钮。
首先我们先创造三个类:
class Button(object):
html = ""
def get_html(self):
return self.html
class Image(Button):
html = "<img></img>"
class Input(Button):
html = "<input></input>"
class Flash(Button):
html = "<obj></obj>"
接下来我们可以创建工厂类:
class ButtonFactory():
def create_button(self, typ):
targetclass = typ.capitalize()
return globals()[targetclass]()
我们可以这样使用它:
button_obj = ButtonFactory()
button = ['image', 'input', 'flash']
for b in button:
print button_obj.create_button(b).get_html()
输出即为所有按钮类型的html。按此法,你可以在不同场景创建不同的按钮并且可以重用代码。
装饰器模式
装饰器模式是一种结构型模式。它能够让我们在运行时根据情景为对象添加额外的行为。
该模式的目的是在特定情景下对实例进行额外的操作同时也可以创建原有的实例。该模式允许对一个实例添加多个装饰器。该模式可以用来替换子类,创建一个类继承父类的功能。和在编译时添加行为的子类不同,装饰器允许你在运行时添加新的行为。
为了实现装饰器模式,我们可以按照下面的步骤。
将原来的组件类封装进装饰器类作为子类。
在装饰器类中,添加一个字段作为组件的指针。
传递一个组件给装饰器的构造函数来初始化指针。
在装饰器类中,将所有的组件方法重定向到指针,并且
在装饰器类中,覆盖所有需要修改的 组件方法,
何时使用它?
使用装饰器模式的最好场景是当你在特定时期
需要给某个实体特殊的行为时。假设你有一个HTML链接元素,一个登出链接,你希望基于当前页面做出轻微的改变。此时我们可以使用装饰器模式。
首先我们需要建立不同的“装饰”。如果我们在主页登陆,该链接就需要用h2标签包裹。
如果我们在不同的页面登录,该链接需要用下划线标签包裹。
如果我们已登录,那么该链接将被包裹成加强标签。一旦我们定义好了“装饰”,我们就可以开始编程了。
class HtmlLinks():
def set_html(self, html):
self.html = html
def get_html(self):
return self.html
def render(self):
print(self.html)
class LogoutLink(HtmlLinks):
def __init__(self):
self.html = "<a href="logout.html"> Logout </a>"
class LogoutLinkH2Decorator(HtmlLinks):
def __init__(self, logout_link):
self.logout_link = logout_link
self.set_html(" {0} ".format(self.logout_link.get_html()))
def call(self, name, args):
self.logout_link.name(args[0])
class LogoutLinkUnderlineDecorator(HtmlLinks):
def __init__(self, logout_link):
self.logout_link = logout_link
self.set_html(" {0} ".format(self.logout_link.get_html()))
def call(self, name, args):
self.logout_link.name(args[0])
class LogoutLinkStrongDecorator(HtmlLinks):
def __init__(self, logout_link):
self.logout_link = logout_link
self.set_html("<strong> {0} </strong>".format(self.logout_link.get_html()))
def call(self, name, args):
self.logout_link.name(args[0])
logout_link = LogoutLink()
is_logged_in = 0
in_home_page = 0
if is_logged_in:
logout_link = LogoutLinkStrongDecorator(logout_link)
if in_home_page:
logout_link = LogoutLinkH2Decorator(logout_link)
else:
logout_link = LogoutLinkUnderlineDecorator(logout_link)
logout_link.render()
单例模式
单例模式是一种创造型模式,它保证你在runtime时只有一个特定的实例,提供一个全局的指针。
这为其他人调用该实例变得更加简单,由于该变量总会返回同样的东西。
何时使用它?
单例模式也许是最简单的设计模式了。它给某个特殊的类型提供了独一无二的实例。为了达成这点 ,你必须操纵该类型的创建过程。一种简便的方法是将单例委托给一个内部的私有类。
class OnlyOne:
class __OnlyOne:
def __init__(self, arg):
self.val = arg
def __str__(self):
return repr(self) + self.val
instance = None
def __init__(self, arg):
if not OnlyOne.instance:
OnlyOne.instance = OnlyOne.__OnlyOne(arg)
else:
OnlyOne.instance.val = arg
def __getattr__(self, name):
return getattr(self.instance, name)
x = OnlyOne('sausage')
print(x)
y = OnlyOne('eggs')
print(y)
z = OnlyOne('spam')
print(z)
print(x)
print(y)
print(`x`)
print(`y`)
print(`z`)
output = '''
<__main__.__OnlyOne instance at 0076B7AC>sausage
<__main__.__OnlyOne instance at 0076B7AC>eggs
<__main__.__OnlyOne instance at 0076B7AC>spam
<__main__.__OnlyOne instance at 0076B7AC>spam
<__main__.__OnlyOne instance at 0076B7AC>spam
<__main__.OnlyOne instance at 0076C54C>
<__main__.OnlyOne instance at 0076DAAC>
<__main__.OnlyOne instance at 0076AA3C>
'''
由于内部类的名字以双下划线开头,所以用户是不能直接访问它的。内部的类包含了所有你需要的方法,然后将它封装进你创建的单例类。你第一次创建OnlyOne的时候,它会对实例初始化,在此之后它会忽略你的创建请求。
访问实例通过使用__getattr__()的方法来重定向你的访问请求。你可以从输出看出即使看上去创建了很多实例,但实际上只创建了一个__OnlyOne 对象。OnlyOne的实例是独立的,但是他们都指向同一个__OnlyOne 对象。
注意上述方法并不限制你只创建一个实例。这也是一种创建限定数量的对象池的方法。但在此情景下,你可能会遇到对象池分享的问题。如果该问题出现了,你可以创建相应的check in 和 check out 方法。
总结
由于python的动态语言特性,设计模式在python中并不是非常重要的概念,但是在python中展示设计模式非常简单。记住类和函数本身是一等公民所以有些设计模式可能不是非常实用。除此之外也有许多其他的设计模式。在本文中我只介绍了一些在编程中使用较多的设计模式。如果你对其他设计模式感兴趣,可以访问相应的wiki页面。
最后一件事,当你使用设计模式时,确保你在试图解决正确的问题。我之前也提及过,设计模式是把双刃剑:如果用在了错误的地方,它将把事情变的更糟。如果用在对的地方,它将是至关重要的。