Python设计模式
从理论上来说,设计模式是对程序问题比较好的解决方案。无数的程序员都曾经遇到过这些问题,并且他们使用这些解决方案去处理这些问题。所以当你遇到同样的问题,为什么要去想着创建一个解决方案而不是用现成的并且被证明是有效的呢?
例子
假定现在有一个任务,需要你找到一个有效的方法合并两个做不同事情的类,在已有系统中这两个类在许多不同的地方被大量使用,所以移除这两个类或是改动已有的代码都是异常困难的。不仅如此,更改已有的代码会导致大量的测试工作,因为在这样一种依赖大量不同组件的系统中,这些修改总是会引入一些新的错误。为了避免这些麻烦,你可以实现一个策略模式(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() |
很简单是吧?现在让我们来仔细研究一下策略模式。
策略模式
策略模式是一种与行为相关的设计模式,允许你在运行时根据指定的上下文确定程序的动作。你可以在两个类中封装不同的算法,并且在程序运行时确定到底执行哪种策略。
在上面的例子中,策略是根据实例化时context变量的值来决定的。如果给定context变量的值是“class_one”,将会执行class_one,否则就会执行class_two。
我在那里使用它?
假定你现在正在写一个类能够更新或创建一条新的用户记录,接收同样的输入参数(诸如姓名、地址、手机号等),但是根据不同的情况会调用对应的更新或是创建方法。当然,你可能会用一个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 |
常规的策略模式涉及到将算法封装到另一个类中,但如果这样的话,那个类就太浪费了。切记不要死记模板,把握住核心概念灵活的变通,最重要是解决问题。
适配器模式
适配器模式是一个结构性的设计模式,允许通过不同的接口为一个类赋予新的用途,这使得使用不同调用方式的系统都能够使用这个类。
也可以让你改变通过客户端类接收到的输入参数以适应被适配者的相关函数。
怎么使用?
另一个使用适配器类的地方是包装器(wrapper),允许你将一个动作包装成为一个类,然后可以在合适的情形下复用这个类。一个典型的例子是当你为一个table类簇创建一个domain类时,你能够将所有的对应不同表的相同动作封装成为一个适配器类,而不是一个接一个的单独调用这些不同的动作。这不仅使得你能够重用你想要的所有操作,而且当你在不同的地方使用同样的动作时不用重写代码。
比较一下两种实现:
不用适配器的方案
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() |
在这种情况下,我们通过一个包装类来实现账户domain类:
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() |
这样的话,你就能够在你需要的时候使用账户domain了,你也可以将其他的类包装到domain类下。
工厂模式
工厂模式是一种创建型的设计模式,作用如其名称:这是一个就像工厂那样生产对象实例的类。
这个模式的主要目的是将可能涉及到很多类的对象创建过程封装到一个单独的方法中。通过给定的上下文输出指定的对象实例。
什么时候使用?
使用工厂模式的最佳时机就是当你需要使用到单个实体的多个变体时。举个例子,你有一个按钮类,这个按钮类有多种变体,例如图片按钮、输入框按钮或是flash按钮等。那么在不同的场合你会需要创建不同的按钮,这时候就可以通过一个工厂来创建不同的按钮。
让我们先来创建三个类:
class Button(object): html = "123123123"; def get_html(self): return self.html class Image(Button): html = ""; class Input(Button): html = ""; class Flash(Button): html = ""; |
然后创建我们的工厂类:
class ButtonFactory(): def create_button(self, typ): targetclass = typ.capitalize() return globals()[targetclass]() |
注:globals()将以字典的方式返回所有全局变量,因此targetclass = typ.capitalize()将通过传入的typ字符串得到类名(Image、Input或Flash),而globals()[targetclass]将通过类名取到类的类(见元类),而globals()[targetclass]()将创建此类的对象。
我们可以这么使用工厂类:
button_obj = ButtonFactory()button = [a,b,c]for b in button: print button_obj.create_button(b).get_html() |
输出将是所有按钮类型的HTML属性。这样骂你就能够根据不同的情况指定不同类型的按钮了,并且很易于重用。
装饰器模式
装饰器模式是一个结构性模式,允许我们根据情况,在运行时为一个对象添加新的或附加的行为。
目的是为给一个特定的对象实例应用扩展的函数方法,并且同时也能够产生没有新方法的原对象。它允许多装饰器结合用于一个实例,所以你就不会出现实例同单个装饰器相捆绑的情况了。这个模式是实现子类继承外的一个可选方式,子类继承是指从父类集成相应的功能。与子类继承必须在编译时添加相应的行为不同,装饰器允许你在运行时根据需要添加新的行为。
可以根据以下步骤实现装饰器模式:
- 以原组件类为基类创建装饰器类。
- 在装饰器类中添加一个组件类的指针域
- 将一个组件传递给装饰器类的构造器以初始化组件类指针
- 在装饰器类中,将所有的组件方法指向组件类指针,并且,
- 在装饰器类中,重写每个需要修改功能的组件方法。
相关维基百科(http://en.wikipedia.org/wiki/Decorator_pattern)
什么时候使用?
使用装饰器模式的最佳时机是当你有一个根据情况需要添加新的行为的实体时。假设你有一个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 = ""; 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(self.format(self.logout_link.get_html())) def call(self, name, args): self.logout_link.name(args[0]) logout_link = LogoutLink()is_logged_in = 0in_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() |
单例模式
单例模式是一个创建型的设计模式,功能是确保运行时对某个类只存在单个实例对象,并且提供一个全局的访问点来访问这个实例对象。
因为对于调用单例的其他对象而言这个全局唯一的访问点“协调”了对单例对象的访问请求,所以这些调用者看到的单例内变量都将是同一份。
什么时候能够使用?
单例模式可能是最简单的设计模式了,它提供特定类型的唯一对象。为了实现这个目标,你必须控制程序之外的对象生成。一个方便的方法是将一个私有内部类的单个对象作为单例对象。
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()print(x)y = OnlyOne()print(y)z = OnlyOne()print(z)print(x)print(y)print(`x`)print(`y`)print(`z`) |
因为内置类是用双下划线开始命名,所以它是私有的,用户无法直接访问。内置类包含了所有你希望放在普通类中的方法,并且通过外层包装类的构造器控制其创建。当第一次你创建OnlyOne时,初始化一个实例对象,后面则会忽略创建新实例的请求。
通过代理的方式进行访问,使用__getattr__()方法将所有调用指向单例。你可以从输出看到虽然看起来好像创建了多个对象(OnlyOne),但 __OnlyOne对象只有一个。虽然OnlyOne实例有多个,但他们都是唯一的 __OnlyOne对象的代理。
请注意上面的方法并没有限制你只能创建一个对象,这也是一个创建有限个对象池的技术。然而在那种情况下,你可能会遇到共享池内对象的问题。如果这真是一个问题,那你可以通过为共享对象设计签入“check-in”和迁出“check-out”机制来解决这个问题。
总结
在本文中,我只列举了几个我再编程中觉得十分重要的设计模式来讲,除此之外还有很多设计模式需要学习。如果你对其他的设计模式感兴趣,维基百科的设计模式部分可以提供很多信息。如果还嫌不够,你可以看看四人帮的《设计模式:可复用面向对象软件的基础》一书,此书是关于设计模式的经典之作。
从理论上来说,设计模式是对程序问题比较好的解决方案。无数的程序员都曾经遇到过这些问题,并且他们使用这些解决方案去处理这些问题。所以当你遇到同样的问题,为什么要去想着创建一个解决方案而不是用现成的并且被证明是有效的呢?
例子
假定现在有一个任务,需要你找到一个有效的方法合并两个做不同事情的类,在已有系统中这两个类在许多不同的地方被大量使用,所以移除这两个类或是改动已有的代码都是异常困难的。不仅如此,更改已有的代码会导致大量的测试工作,因为在这样一种依赖大量不同组件的系统中,这些修改总是会引入一些新的错误。为了避免这些麻烦,你可以实现一个策略模式(Strategy Pattern)和适配器模式(Adapter Pattern)的变体,这两种模式能够很好的处理这种问题。
|
1
2
3
4
5
6
7
8
9
10
11
12
|
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()
|
很简单是吧?现在让我们来仔细研究一下策略模式。
策略模式
策略模式是一种与行为相关的设计模式,允许你在运行时根据指定的上下文确定程序的动作。你可以在两个类中封装不同的算法,并且在程序运行时确定到底执行哪种策略。
在上面的例子中,策略是根据实例化时context变量的值来决定的。如果给定context变量的值是“class_one”,将会执行class_one,否则就会执行class_two。
我在那里使用它?
假定你现在正在写一个类能够更新或创建一条新的用户记录,接收同样的输入参数(诸如姓名、地址、手机号等),但是根据不同的情况会调用对应的更新或是创建方法。当然,你可能会用一个if-else判断处理这个问题,但是如果你需要在不同的地方使用这个类呢?那么你就得不停地重写if-else判断。为什么不简单地通过指定上下文来解决这个问题。
|
1
2
3
4
5
6
|
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
|
常规的策略模式涉及到将算法封装到另一个类中,但如果这样的话,那个类就太浪费了。切记不要死记模板,把握住核心概念灵活的变通,最重要是解决问题。
适配器模式
适配器模式是一个结构性的设计模式,允许通过不同的接口为一个类赋予新的用途,这使得使用不同调用方式的系统都能够使用这个类。
也可以让你改变通过客户端类接收到的输入参数以适应被适配者的相关函数。
怎么使用?
另一个使用适配器类的地方是包装器(wrapper),允许你将一个动作包装成为一个类,然后可以在合适的情形下复用这个类。一个典型的例子是当你为一个table类簇创建一个domain类时,你能够将所有的对应不同表的相同动作封装成为一个适配器类,而不是一个接一个的单独调用这些不同的动作。这不仅使得你能够重用你想要的所有操作,而且当你在不同的地方使用同样的动作时不用重写代码。
比较一下两种实现:
不用适配器的方案
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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()
|
如果我们需要在不同的地方做同样的事,或是在不同的项目中重用这段代码,那么我们需要重新敲一遍。
使用包装类的解决方案
看看我们怎么反其道而行:
|
1
2
|
account_domain = Account()
account_domain.NewAccount()
|
在这种情况下,我们通过一个包装类来实现账户domain类:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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()
|
这样的话,你就能够在你需要的时候使用账户domain了,你也可以将其他的类包装到domain类下。
工厂模式
工厂模式是一种创建型的设计模式,作用如其名称:这是一个就像工厂那样生产对象实例的类。
这个模式的主要目的是将可能涉及到很多类的对象创建过程封装到一个单独的方法中。通过给定的上下文输出指定的对象实例。
什么时候使用?
使用工厂模式的最佳时机就是当你需要使用到单个实体的多个变体时。举个例子,你有一个按钮类,这个按钮类有多种变体,例如图片按钮、输入框按钮或是flash按钮等。那么在不同的场合你会需要创建不同的按钮,这时候就可以通过一个工厂来创建不同的按钮。
让我们先来创建三个类:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Button(object):
html = ""
def get_html(self):
return self.html
class Image(Button):
html = "<img alt="" />"
class Input(Button):
html = "<input type="text" />"
class Flash(Button):
html = ""
|
然后创建我们的工厂类:
|
1
2
3
4
|
class ButtonFactory():
def create_button(self, typ):
targetclass = typ.capitalize()
return globals()[targetclass]()
|
译注:globals()将以字典的方式返回所有全局变量,因此targetclass = typ.capitalize()将通过传入的typ字符串得到类名(Image、Input或Flash),而globals()[targetclass]将通过类名取到类的类(见元类),而globals()[targetclass]()将创建此类的对象。
我们可以这么使用工厂类:
|
1
2
3
4
|
button_obj = ButtonFactory()
button = ['image', 'input', 'flash']
for b in button:
print button_obj.create_button(b).get_html()
|
输出将是所有按钮类型的HTML属性。这样骂你就能够根据不同的情况指定不同类型的按钮了,并且很易于重用。
装饰器模式
装饰器模式是一个结构性模式,允许我们根据情况,在运行时为一个对象添加新的或附加的行为。
目的是为给一个特定的对象实例应用扩展的函数方法,并且同时也能够产生没有新方法的原对象。它允许多装饰器结合用于一个实例,所以你就不会出现实例同单个装饰器相捆绑的情况了。这个模式是实现子类继承外的一个可选方式,子类继承是指从父类集成相应的功能。与子类继承必须在编译时添加相应的行为不同,装饰器允许你在运行时根据需要添加新的行为。
可以根据以下步骤实现装饰器模式:
- 以原组件类为基类创建装饰器类。
- 在装饰器类中添加一个组件类的指针域
- 将一个组件传递给装饰器类的构造器以初始化组件类指针
- 在装饰器类中,将所有的组件方法指向组件类指针,并且,
- 在装饰器类中,重写每个需要修改功能的组件方法。
相关维基百科(http://en.wikipedia.org/wiki/Decorator_pattern)
什么时候使用?
使用装饰器模式的最佳时机是当你有一个根据情况需要添加新的行为的实体时。假设你有一个HTML链接元素,一个登出链接,并且你希望根据当前页面对具体的行为做微小的改动。这种情况下,我们可以使用装饰器模式。
首先,建立我们所需要的装饰模式。
如果我们在主页并且已经登录,那么将登出链接用h2标签标记。
如果我们在不同的页面并且已经登录,那么用下划线标签标记链接
如果已登录,用加粗标记链接。
一旦建立了装饰模式,我们就可以开工了。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
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()
|
单例模式
单例模式是一个创建型的设计模式,功能是确保运行时对某个类只存在单个实例对象,并且提供一个全局的访问点来访问这个实例对象。
因为对于调用单例的其他对象而言这个全局唯一的访问点“协调”了对单例对象的访问请求,所以这些调用者看到的单例内变量都将是同一份。
什么时候能够使用?
单例模式可能是最简单的设计模式了,它提供特定类型的唯一对象。为了实现这个目标,你必须控制程序之外的对象生成。一个方便的方法是将一个私有内部类的单个对象作为单例对象。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
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实例有多个,但他们都是唯一的 __OnlyOne对象的代理。
请注意上面的方法并没有限制你只能创建一个对象,这也是一个创建有限个对象池的技术。然而在那种情况下,你可能会遇到共享池内对象的问题。如果这真是一个问题,那你可以通过为共享对象设计签入“check-in”和迁出“check-out”机制来解决这个问题。
总结
在本文中,我只列举了几个我再编程中觉得十分重要的设计模式来讲,除此之外还有很多设计模式需要学习。如果你对其他的设计模式感兴趣,维基百科的设计模式部分可以提供很多信息。如果还嫌不够,你可以看看四人帮的《设计模式:可复用面向对象软件的基础》一书,此书是关于设计模式的经典之作。

浙公网安备 33010602011771号