2010/07 3
设计模式能帮助你获得别人的成功经验,减少自己在摸索过程中的错误。 --Mark Johnson.

可以讲,面向对象设计的发展史,就是设计模式的发展史。《设计模式》一书是设计模式发展中的里程碑著作。它描述了23中不同的面向对象解决方案。在这本书中,主要的设计模式都附有示例。Gamma四人组的这本书绝对值得你去阅读。它是面向对象开发人员所必须的了解的。

接下来本书将通过一些实例来介绍设计思想的分析过程,从开始状态,到理性思考,再到分析方法的改进,最终确定一种更合适的设计。这个流程经过了很长时间的提炼,你也可以将他作为一个参考原型,用来分析特殊问题和改进。


什么是设计模式

最开始,你可以将设计模式理解为一种用来组织类结构的聪明的有效的方法,它是许多人的的经验总结和归纳。一般他们都具有很好的的通用性和可扩展性。某一个设计模式可能能够解决你所遇到的问题,但并非和你的逻辑完全一致。

虽然我们称它为“设计模式”,但他和设计并没有太多关系。一个设计模式和传统意义中的分析,设计,实施是不同的。设计模式体现的是一种思维模式,他时常出现在分析阶段或更高级别的设计阶段。但讽刺的是,设计模式通常描诉一些具体实现代码,它和底层设计或实施密切相关(而经常的,你只有在到达这一阶段才会意识到,你需要一个设计模式来帮助你解决问题)

设计模式基本概念和程序设计的基本概念相当,只是在其基础之上,增加了一个抽象层概念而已。 任何的抽象都是建立在具体细节之上的,抽象的动力在于如何封装变化。或者说,当需要修改一处代码时,你不会影响到其他的功能。使用设计模式可以花很少的成本,来可以降低维护成本,也能提升代码的可理解性。

通常,程序设计中最难的问题就是如何编写优雅而易于维护的代码。这样的代码成本我们称它为“线性变化”(这里的线性,是指一种增长曲线,而不是线性队列这一容器类型)。这意味着,找到未来最重要的变化,或者说找到最大的变化成本显得格外重要。一旦你找到了他们,你就需要考虑,如何设计你的代码结构,来减少这些变化风险。

设计模式的目的是在代码中关联变化。通过这一个理解,上面的代码中已经体现了一些设计模式的思想。类继承可以看成是一种设计模式,他允许你在各个子类中编写独立的和父类不同的行为。组合也可以看成是一种设计模式,他允许你动态或静态的改变类中的组合对象行为,就像改变类本身行为一样。

另一种设计模式是迭代器。在Python之初,你就可以隐晦的使用到它,在Python2.2,它作为一种重要的特性被提炼了出来。迭代器隐藏了容器内部的元素,通过迭代来逐个访问容器中的各个元素。同时,你可以写一些通用的代码来实现顺序访问,而不用关心该容器是队列的类型。你的通用方法能够被任何一个提供迭代器的队列调用。


模式分类

当前,大量的设计模式被提了出来,开发人员认为设计模式的就是“好”的,但另一部分人认为这是词汇爆炸,或者说是词汇污染。在反复思考后,我用几个阶段来定义模式:

1. 惯用法: 在不同的语言中,我们通常有一些写代码的惯用习惯,比如在C中对数组的for循环遍历(特别是防止越界的方法)

2. 特殊的设计: 为了解决一些特殊的问题,我们说采用的特殊的处理方式。它可能设计的很巧妙,但它并不通用。

3. 标准设计: 它可以解决某一种问题。这种设计比较通用,而且考虑了重用。

4. 设计模式: 它可以解决某一类相似的问题。在使用标准设计一段时间之后,它才又可能被抽象出来,用来解决这一类的相似问题。

我发现这种透视图的方法很管用,他展示了哪一等级是最合适的。这并不说明,哪一种优于另一种。将所有问题都应用于设计模式是没有意义的,它只会浪费你的时间。设计模式不是被意外发现或研究出来的,而应该建立在对一些巧妙方法的使用经验和总结之上。

还有一些设计模式分类方法,如设计模式分析或设计模式架构进行分类。


设计结构

我在设计模式分类上纠结了很久。我发现,GoF四人组提出的分类很晦涩,而且不是每次都管用。很明显的,构造模式这一分类可以告诉你如何建立一个对象。但它还不够明确,他还是一个设计模式组。它无法清晰的说明:“嗨,毫无疑问,你这里可以使用结构模式”。这样的分类方法,很难让我找到一种解决方案(我承认,我也许错过了某一些细节)。

在这一问题上,一开始我显的十分吃力。首先我发现到GoF设计模式中有一些基础设计模式很相似,而且它们所针对的,也是一些相似的问题。经验告诉我,这些设计模式并没有产生多大的作用。一个有用的方法是分析问题,并找到问题和可能出现问题之间的联系。

后来,我开始收集一些基本的设计结构,并试图找到这些设计结构和设计模式之间的联系,特别是在一些公认的优秀的系统中,他们是怎么设计的。目前我已经总结了一个列表(我以后可能会使用一些其他的方法,目前还并不成熟)

以下是一个草案,它们并不会全部出现在最终终稿中。如果你有任何的建议,或者能够找到他们和设计模式之间的关系,请不吝指教。

  • 封装: 自我控制的一种模型,包含数据和行为
  • 聚合
  • 本地化
  • 分离
  • 隐藏
  • 保护
  • 连接
  • 隔离
  • 行为变化
  • 通知
  • 转化
  • 镜像: “并行的实现方式”
  • 映射 “跟踪变化,但有些许变化” (也可以理解为“代理”的一个变种).


设计准则

当我把我的想法发布到我的新闻列表中时,我收到了不少的有用的反馈,但很多都超出了设计结构所讨论的范围。这让我觉得,一个设计准则列表也是相当有用的。但作用不同,在思考设计时,这些准则用来提出一些疑问,或者说,当作一个核查表。

  • 保持平常,不要让人感到惊讶
  • 能够容易的应用于常用的场景,同时有可能应用于不常见的场景。
  • 一致性 我非常肯定的需要做到这一点,特别是在Python中。随意的规则将打乱程序中的正常秩序,而且会使将死开发效率。效率的降低会随大量规则的引入和成指数级增长。
  • 得墨忒耳定律: "不好和陌生人说话",一个对象只应该访问自己,自己的属性和自己的方法。
  • 减法原则: 一个好的设计完成之后,你应该不能够再从他内部移走什么。
  • 在通用设计之前保持简单: (简单的就是最好的)。一个常见的问题是,一个框架常被设计的很通用,并不针对某一个特定的系统。它增加了大量的不常用,滥用甚至是不会用到的属性,这往往使人感到头晕。大部分开发都是针对特定的系统的,通用性对他们往往没有用。最好的通用型是建立在明确的需求文档之上的。这个规则和“封装并适应变化”又一定冲突。当然,有一条是可以实现的:越简单的解决方法也会越通用。
  • 自反性: 一个抽象一个类,一个类一个抽象。这也被成为同质化
  • 独立,或正交性: 他和隔离,封装和变化相关,也是低耦合高内聚的体现
  • 有且仅有一次:避免复制逻辑代码或结果。
  • 这些想法还在进行头脑风暴中,我希望你在你的分析过程中,能够提出一些有用的想法。其他的想法也在分析过程中作为核查表来使用。



单例

可能单例是最简单的设计模式了。他提供一种方式,用来构建并只构建一个实例。为了实现这一个目的,你需要对对象的构造函数加以调整。一个便捷的方式是使用通过代表的形式,使用递归的方式建立子类:
#: c01:SingletonPattern.py
class OnlyOne:
	class __OnlyOne:
		def __init__(self, arg):
			self.val = arg
		def __str__(self):
			return `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对象被建立了,但__OnleOne实例只有一个。虽然各个OnlyOne实例不同,但他们都委托给同一个__OnlyOne实例。

上面的方法并没有限制你只能建立一个对象,这也是一种建立共享对象池的技术。然而共享对象池有时候会带来一些问题。你可以使用另一种当时来解决:

注意这里的实现变化,类使用了__new__方法,该方法与Pyhton2.2被引入。

#: c01:NewSingleton.py
class OnlyOne(object):
	class __OnlyOne:
		def __init__(self):
			self.val = None
		def __str__(self):
			return `self` + self.val

	instance = None
	def __new__(cls): # __new__ always a classmethod
		if not OnlyOne.instance:
			OnlyOne.instance = OnlyOne.__OnlyOne()
		return OnlyOne.instance

	def __getattr__(self, name):
		return getattr(self.instance, name)
	def __setattr__(self, name):
		return setattr(self.instance, name)

x = OnlyOne()
x.val = 'sausage'
print x
y = OnlyOne()
y.val = 'eggs'
print y
z = OnlyOne()
z.val = 'spam'
print z
print x
print y
#
output = ''' <__main__.__OnlyOne instance at 0x00798900>sausage <__main__.__OnlyOne instance at 0x00798900>eggs <__main__.__OnlyOne instance at 0x00798900>spam <__main__.__OnlyOne instance at 0x00798900>spam <__main__.__OnlyOne instance at 0x00798900>spam ''' #:~


Alex Martelli发现,有时候我们确实需要建立一个单例,来存储一组状态信息。也就是说你可以建立根据需求,建立任意多个对象,但他们的状态信息都是一样的。他的实现代码如下,使用了__dict__技术来存储一个静态的共享存储区域。
#: c01:BorgSingleton.py
# Alex Martelli's 'Borg'
class Borg:
	_shared_state = {}
	def __init__(self):
		self.__dict__ = self._shared_state

class Singleton(Borg):
	def __init__(self, arg):
		Borg.__init__(self)
		self.val = arg
	def __str__(self): return self.val

x = Singleton('sausage')
print x
y = Singleton('eggs')
print y
z = Singleton('spam')
print z
print x
print y
print `x`
print `y`
print `z`
output = '''
sausage
eggs
spam
spam
spam
<__main__.Singleton instance at 0079EF2C>
<__main__.Singleton instance at 0079E10C>
<__main__.Singleton instance at 00798F9C>
'''
#:~


这和SingletonPattern.py文件的作用是一样的,但显得更加优雅。之前的例子中我们需要些一些代码将单例的行为和外部类关联起来,但这里Borg的代码使用了继承,更方便进行重用。

通过包装或定义元类的方式也可以实现单例。首先我们来看看第一个方式,主类被另一个类所包装,他比较像类装饰模型(装饰将在本书后面继续讨论):
#: c01:SingletonDecorator.py
class SingletonDecorator:
	def __init__(self,klass):
		self.klass = klass
		self.instance = None
	def __call__(self,*args,**kwds):
		if self.instance == None:
			self.instance = self.klass(*args,**kwds)
		return self.instance

class foo: pass

foo = SingletonDecorator(foo)
x=foo()
y=foo()
z=foo()
x.val = 'sausage'
y.val = 'eggs'
z.val = 'spam'
print x.val
print y.val
print z.val
print x is y is z
#:~


第二种方法是使用元类。这一章我还没有完全明白,但他看起来很有趣很强大。(注意,Python2.2改进和简化了元类的语法,因此,以下的列子可能需要改写了。)

#: c01:SingletonMetaClass.py
class SingletonMetaClass(type):
	def __init__(cls,name,bases,dict):
		super(SingletonMetaClass,cls)\
			.__init__(name,bases,dict)
		original_new = cls.__new__

		def my_new(cls,*args,**kwds):
			if cls.instance == None:
				cls.instance = \
					original_new(cls,*args,**kwds)
				return cls.instance
			cls.instance = None
			cls.__new__ = staticmethod(my_new)

class bar(object):
	__metaclass__ = SingletonMetaClass
	def __init__(self,val):
		self.val = val
	def __str__(self):
		return `self` + self.val

x=bar('sausage')
y=bar('eggs')
z=bar('spam')
print x
print y
print z
print x is y is z
#:~


练习:
修改BorgSingleton.py,使其使用__new__方法

Defined tags for this entry: ,

Posted by rollenc

Last modified on 2010-07-07 18:14

0 Trackbacks

  1. No Trackbacks

1 Comments

  1. Captain says:

    python实现单例还真没有其它OO语言直接啊

Add Comment


E-Mail addresses will not be displayed and will only be used for E-Mail notifications.