2010/07
3
设计模式能帮助你获得别人的成功经验,减少自己在摸索过程中的错误。 --Mark Johnson.
可以讲,面向对象设计的发展史,就是设计模式的发展史。《设计模式》一书是设计模式发展中的里程碑著作。它描述了23中不同的面向对象解决方案。在这本书中,主要的设计模式都附有示例。Gamma四人组的这本书绝对值得你去阅读。它是面向对象开发人员所必须的了解的。
接下来本书将通过一些实例来介绍设计思想的分析过程,从开始状态,到理性思考,再到分析方法的改进,最终确定一种更合适的设计。这个流程经过了很长时间的提炼,你也可以将他作为一个参考原型,用来分析特殊问题和改进。
什么是设计模式
最开始,你可以将设计模式理解为一种用来组织类结构的聪明的有效的方法,它是许多人的的经验总结和归纳。一般他们都具有很好的的通用性和可扩展性。某一个设计模式可能能够解决你所遇到的问题,但并非和你的逻辑完全一致。
虽然我们称它为“设计模式”,但他和设计并没有太多关系。一个设计模式和传统意义中的分析,设计,实施是不同的。设计模式体现的是一种思维模式,他时常出现在分析阶段或更高级别的设计阶段。但讽刺的是,设计模式通常描诉一些具体实现代码,它和底层设计或实施密切相关(而经常的,你只有在到达这一阶段才会意识到,你需要一个设计模式来帮助你解决问题)
设计模式基本概念和程序设计的基本概念相当,只是在其基础之上,增加了一个抽象层概念而已。 任何的抽象都是建立在具体细节之上的,抽象的动力在于如何封装变化。或者说,当需要修改一处代码时,你不会影响到其他的功能。使用设计模式可以花很少的成本,来可以降低维护成本,也能提升代码的可理解性。
通常,程序设计中最难的问题就是如何编写优雅而易于维护的代码。这样的代码成本我们称它为“线性变化”(这里的线性,是指一种增长曲线,而不是线性队列这一容器类型)。这意味着,找到未来最重要的变化,或者说找到最大的变化成本显得格外重要。一旦你找到了他们,你就需要考虑,如何设计你的代码结构,来减少这些变化风险。
设计模式的目的是在代码中关联变化。通过这一个理解,上面的代码中已经体现了一些设计模式的思想。类继承可以看成是一种设计模式,他允许你在各个子类中编写独立的和父类不同的行为。组合也可以看成是一种设计模式,他允许你动态或静态的改变类中的组合对象行为,就像改变类本身行为一样。
另一种设计模式是迭代器。在Python之初,你就可以隐晦的使用到它,在Python2.2,它作为一种重要的特性被提炼了出来。迭代器隐藏了容器内部的元素,通过迭代来逐个访问容器中的各个元素。同时,你可以写一些通用的代码来实现顺序访问,而不用关心该容器是队列的类型。你的通用方法能够被任何一个提供迭代器的队列调用。
模式分类
当前,大量的设计模式被提了出来,开发人员认为设计模式的就是“好”的,但另一部分人认为这是词汇爆炸,或者说是词汇污染。在反复思考后,我用几个阶段来定义模式:
1. 惯用法: 在不同的语言中,我们通常有一些写代码的惯用习惯,比如在C中对数组的for循环遍历(特别是防止越界的方法)
2. 特殊的设计: 为了解决一些特殊的问题,我们说采用的特殊的处理方式。它可能设计的很巧妙,但它并不通用。
3. 标准设计: 它可以解决某一种问题。这种设计比较通用,而且考虑了重用。
4. 设计模式: 它可以解决某一类相似的问题。在使用标准设计一段时间之后,它才又可能被抽象出来,用来解决这一类的相似问题。
我发现这种透视图的方法很管用,他展示了哪一等级是最合适的。这并不说明,哪一种优于另一种。将所有问题都应用于设计模式是没有意义的,它只会浪费你的时间。设计模式不是被意外发现或研究出来的,而应该建立在对一些巧妙方法的使用经验和总结之上。
还有一些设计模式分类方法,如设计模式分析或设计模式架构进行分类。
设计结构
我在设计模式分类上纠结了很久。我发现,GoF四人组提出的分类很晦涩,而且不是每次都管用。很明显的,构造模式这一分类可以告诉你如何建立一个对象。但它还不够明确,他还是一个设计模式组。它无法清晰的说明:“嗨,毫无疑问,你这里可以使用结构模式”。这样的分类方法,很难让我找到一种解决方案(我承认,我也许错过了某一些细节)。
在这一问题上,一开始我显的十分吃力。首先我发现到GoF设计模式中有一些基础设计模式很相似,而且它们所针对的,也是一些相似的问题。经验告诉我,这些设计模式并没有产生多大的作用。一个有用的方法是分析问题,并找到问题和可能出现问题之间的联系。
后来,我开始收集一些基本的设计结构,并试图找到这些设计结构和设计模式之间的联系,特别是在一些公认的优秀的系统中,他们是怎么设计的。目前我已经总结了一个列表(我以后可能会使用一些其他的方法,目前还并不成熟)
以下是一个草案,它们并不会全部出现在最终终稿中。如果你有任何的建议,或者能够找到他们和设计模式之间的关系,请不吝指教。
设计准则
当我把我的想法发布到我的新闻列表中时,我收到了不少的有用的反馈,但很多都超出了设计结构所讨论的范围。这让我觉得,一个设计准则列表也是相当有用的。但作用不同,在思考设计时,这些准则用来提出一些疑问,或者说,当作一个核查表。
单例
可能单例是最简单的设计模式了。他提供一种方式,用来构建并只构建一个实例。为了实现这一个目的,你需要对对象的构造函数加以调整。一个便捷的方式是使用通过代表的形式,使用递归的方式建立子类:
内联类以双下划线命名,代表私有类,这样用户无法直接访问到他。内联类应该实现所有你需要的功能,就像他不是一个内联类一样。外部封装类负责内联类的构建,第一次调用将建立一个OnlyOne类,它将初始化一个内联类的实例,再次调用他将忽略实例化操作。
访问将通过委托的方式,使用__getattr__()的方法,来将调用委托给单例。在输出中你可以看到多个OnlyOne对象被建立了,但__OnleOne实例只有一个。虽然各个OnlyOne实例不同,但他们都委托给同一个__OnlyOne实例。
上面的方法并没有限制你只能建立一个对象,这也是一种建立共享对象池的技术。然而共享对象池有时候会带来一些问题。你可以使用另一种当时来解决:
注意这里的实现变化,类使用了__new__方法,该方法与Pyhton2.2被引入。
Alex Martelli发现,有时候我们确实需要建立一个单例,来存储一组状态信息。也就是说你可以建立根据需求,建立任意多个对象,但他们的状态信息都是一样的。他的实现代码如下,使用了__dict__技术来存储一个静态的共享存储区域。
这和SingletonPattern.py文件的作用是一样的,但显得更加优雅。之前的例子中我们需要些一些代码将单例的行为和外部类关联起来,但这里Borg的代码使用了继承,更方便进行重用。
通过包装或定义元类的方式也可以实现单例。首先我们来看看第一个方式,主类被另一个类所包装,他比较像类装饰模型(装饰将在本书后面继续讨论):
第二种方法是使用元类。这一章我还没有完全明白,但他看起来很有趣很强大。(注意,Python2.2改进和简化了元类的语法,因此,以下的列子可能需要改写了。)
练习:
修改BorgSingleton.py,使其使用__new__方法
可以讲,面向对象设计的发展史,就是设计模式的发展史。《设计模式》一书是设计模式发展中的里程碑著作。它描述了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__方法
Last modified on 2010-07-07 18:14










0 Trackbacks