(零雨其蒙原创 转载请注明)
2007
年
3
月
10
日星期六
得墨忒耳定律
该定律(更通俗的名字叫“不要和陌生人说话”)的含义是,不要历经远距离的对象结构路径去向远距离的间接对象
(
陌生人
)
发送消息(说话)。该定律约束了你应该在方法里给哪些对象发送消息,其要求在方法里,只应该给以下对象发送消息:
1)
this
对象(自身)
2)
方法的参数
3)
this
的属性
4)
作为
this
属性的集合中的元素
5)
在方法中创建的对象
Information Hiding
信息隐藏
Parnas
所指的信息隐藏绝非数据封装的思想,而是由于困难和可能的变化而对其他模块
隐藏
与设计相关的
信息
。以下引用了他对信息隐藏的论述,并作为指导性的原则:
实际上,我们所建议的是,以一组困难的设计决策或可能变化的设计决策作为开始。然后,设计每个模块,使其对其他模块隐藏此类决策。
我的理解:
也就是说,我们要对于依赖它的模块隐藏那些容易变化的部分(信息),很显然,我们经常遇到触一发而动全身的情况,而且有时这种情况还很频繁的出现,这意味着在设计时我们向外界暴露了容易变化的部分(信息,消息等),因此慎重的考虑公开的部分(
public
方法或属性),是
OOD
的难点。
OCP
开放—封闭原则
P315 OCP
的定义是:模块应该同时(对扩展、可适应性)开放和(对影响客户的更改)封闭。
第
26
章
应用
GoF
设计模式
GRASP
原则对其他设计模式的归纳
适配器模式
支持防止变异(
PV
)
,因为它通过应用了接口和多态的间接转换对象,改变了外部接口或第三方软件包。
我觉得这幅图非常好的反映了模式之间的关系,了解模式的动机有助于理解模式,这是我从自己学习模式和其他技术时得到的最好的经验之一。该图使用了泛化—特化的分类学表示法,有助于了解
GoF
设计模式的本质,其实对于
GRASP
模式而言,弄清各个模式的关系(主次、大小、推导)也是很有意义的。(该图位于
319
页)
在学习完这一章后我将把
GoF
设计模式按照
GRASP
重新分类,那或许是明天要做的事情了,而且或许随着实践的延伸,这种分类将变得越来越有意义。
Adapter
适配器
问题:
如何解决不相容的接口问题,或如何为具有不同接口的类似构件提供稳定的接口?
解决方案:
通过中介适配器对象,将构件的原有接口转换为其他接口。
这个方案我暂时理解为,添加一个对象,这个对象含有一种方法,这种方法能将一个接口
IA
转换为另一个接口
IB
(就可以说
IA
通过适配器转换,使其可以通过
IB
访问其他对象),比如将
ICommonAccounting
(通用财务系统接口)转换为
ISAPAccounting
(
SAP
财务系统接口),两个接口不同就可能比如都有一个叫获得总帐信息的方法,在通用财务系统中只需要有一个参数,而在
SAP
财务系统中需要有多个参数,这样就造成了接口不统一。就好比日本的电压是
110V
而中国是
220V
,就需要一个变压器(变压器的别名其实就是适配器),这样就可以在中国使用日本的电器了。在变压器内部,将
220V
转成了
110V
。
适配器模式简单理解就是,增加一个中间层,使其可以访问一个原来对象不能访问直接的对象(原因是接口不匹配)。所谓转换也不是变魔术,其实就是通过适配器来访问某个对象,将其结果返回给调用适配器的那个对象。(应该就是如此简单的完成了接口的转化,暂时我是这样理解的)
Factory
工厂
问题:
当有特殊情况(例如存在负责的创建罗辑、为了改良内聚而奋力创建职责等)时,应该由谁来负责创建对象?
解决方案
:创建成为工厂的纯虚构对象来处理这些创建职责。
无论怎么说,我们必须指定要创建的类,因为这不是变魔术,可以采用反射机制(在
Java
中),动态的指定类名(
String
型的,可以从外部特性文件或
XML
文件中读取),然后根据指定类名来生产要的类。(这样做可以避免使用条件分支语句,
if
需要
A
类,生产
A
类等等)。但是如果所使用的语言不支持反射机制(例如
Delphi
)呢?我先前都是用条件分支语句搞定的,等看完
Delphi
模式编程那本书可能就会明白了。
Singleton
单实例类
问题:
只有唯一实例的类即为“单实例类”。对象需要全局可见性和单点访问。
解决方案
:对类定义静态方法用以返回单实例。
下面来一段代码来说明
Singleton
,并且解决多线程应用中的并发问题。
public static synchronized
ServicesFatory getInstance()
{
if(instance==null)
{
Instance=new ServicesFatory();
}
return instance;
}
这里多线程解决方案是缓式初始化(lazy initialization)。整个代码来看非常朴素,如果instance已经存在就直接返回,否则创建一个新的。instance是一个static实例,即静态属性。
上述三种设计模式的关系
基本上也是基于动机来理解的,为了解决接口不匹配的问题(就是一个对象不能直接访问对象的方法,而需要一个中间层),而创建了适配器,而对于不同的外部系统需要不同的适配器,因此需要一个工厂来生产相应的适配器,然而适配器可能需要常常生产,而工厂不必总是被创建(当然,每天都要生产计算机,但是没有必要每天都把生产计算机的工厂都重盖一遍),于是我们创建了只有一个实例的工厂类,也就是单实例类。
通过大一统的思想来理解,三种模式的一般用法就可以了然于心了(不过
Larman
已经在本章的开头提醒我们本书对于设计模式的讲解只是初级的,面对复杂的现实问题,可能并没有这样易于理解)。
Strategy
策略
问题:
如何设计变化但相关的算法或政策?如何设计才能使这些算法或政策具有可变更的能力。
解决方案
:在单独的类中分别定义每种算法
/
政策
/
策略,并且使其具有共同的接口。
注意:语境对象需要其策略的属性可见性(就是说策略要作为语境对象的属性)。
实际上,策略是多态的一个应用,策略也是基于多态技术的,其含义在解决方案中已经可以完全读出了。
Composite
组合
问题:
如何能够像处理非组合(原子对象)一样,(多态地)处理一组对象或具有组合结构的对象呢?
解决方案
:定义组合和原子对象的类,使它们实现相同的接口。
组合对象的标志性特性
:外部的组合对象包含一组内部对象,外部和内部对象实现相同的接口。
从书中给的
Java
实现代码可以看出,组合对象实现了和其内部对象相同的接口,而其内部对象又是以集合的形式(定义为它们共同的接口)作为组合对象的属性,然后组合对象通过一个
add
方法,来动态装载原子对象,使其成为其内部对象,这样就将那些有共同接口的原子对象组合到一起了。组合对象和原子对象(都实现了同一个接口)都可以由工厂来生产。
组合也是基于多态的。
将聚合对象作为参数传递
惯例:
避免将子对象从父对象或聚合对象提取出来,并且传递这些子对象。相反,应该传递包含子对象的聚合对象。(不要单独把聚合对象的子对象一个一个拎出来,然后传递它们,要传的话就直接传聚合对象就好了。)
Facade
外观
问题:
对一组完全不同的实现接口(例如子系统中的实现和接口)需要公共、统一的接口。可能会与子系统内部的大量事物产生耦合,或者子系统的实现可能会改变。怎么办?
解决方案
:对子系统定义唯一的接触点——使用外观对象封装子系统、该外观对象提供了唯一和统一的接口,并负责与子系统构件进行协作。
外观将一个子系统隐藏于其后
,就像键盘一样,键盘隐藏了计算机,计算机来处理我们的请求(击键),但是我们完全不知道计算机的方法,我们只知道同时按
Ctrl+Alt+Del
可以调出任务管理器,这就是外观(键盘)的一个公开的方法(按那三个键子)。
适配器对象可能用来封装对具有不同接口的外部系统的访问。这就是一种外观,但是其强调的是对不同接口的适配,因此被更具体的称为适配器(
P337
)。
如果这样说的话,那么适配器就是外观模式的特化了。
Observer
观察者
/
发布—订阅
/
委派事件模型
问题:
不同类型的订阅者对象关注与发布者对象的状态变化或事件,并且想要在发布者产生事件时以自己独特的方式作为反应。此外,发布者想要保持与订阅者的低耦合。如何对此进行设计呢?
解决方案
:定义“订阅者”或“监听器”的接口。订阅者实现此接口。发布者可以动态注册关注某件事情的订阅者,并在事情发生时通知它们。
通过本书所给示例可以看到,发布者内部有一个装载订阅者(监听器、观察者)的集合(这意味着所有订阅者实现同一个接口),并且有一个将订阅者加入到该集合的方法,同时有一个通知集合中每一个订阅者的方法(被称为发布“特性事件”)。然后订阅者负责去改变其他对象的状态。
我的总结
:组合模式和观察者模式都用到了集合,组合模式是将子对象放入组合对象中的集合属性中,而观察者模式是将观察者放入发布者对象的集合属性中。然后都是通过遍历来调用或通知集合中的每一个对象。
观察者模式也是基于
多态
的。
P409
当初与较低层的应用或领域层需要向上与
UI
层通信时,通常使用观察者模式。