stand on the shoulders of giants

【设计模式】之 开篇

开始学习一下设计模式,当然是面向对象的设计模式(GoF的,不是企业级架构的)
这方面TerryLeezhenyulu等都写了不错的专题(补充:刚看到WebCast上有李建忠老师的好评如潮)
偶不才就拿来借鉴一下,其实发现好多东西最新的、前沿的,经典的都是国外的。我们经常都是跟着人家后面学习... 
设计原则部分转载自justinw在此一并表示感谢!

(前人栽树 后人乘凉 那我为什么还要栽呢?
因为把别人的树拿过来种一遍 心里面踏实,呵呵;学习是个不断迭代的过程另外,到现在不会设计模式,没用过设计模式(可能用过自己却不清楚),惭愧啊!
还好,学习永远不算晚,当然,也不能太晚。还是废话少说,Go!)

 


 

1. GoF 23 种设计模式

2. 几点基本思想

3. GRASP (职责分配原则)

4. 面向对象设计原则


 

 1. GoF 23 种设计模式

创建型

1. Factory               Creates an instance of several families of classes

2. Singleton            A class of which only a single instance can exist

3. Builder                Separates object construction from its representation

4. Factory method    Creates an instance of several derived classes

5. Prototype            A fully initialized instance to be copied or cloned

结构型

1. Facade                A single class that represents an entire subsystem

2. Adapter               Match interfaces of different classes

3. Bridge                 Separates an object’s interface from its implementation

4. Composite           A tree structure of simple and composite objects

5. Decorator            Add responsibilities to objects dynamically

6. Flyweight             A fine-grained instance used for efficient sharing

7. Proxy                  An object representing another object

行为模式

1. Command            Encapsulate a command request as an object

2. Stragety              Encapsulates an algorithm inside a class

3. Interator             Sequentially access the elements of a collection

4. Observer             A way of notifying change to a number of classes

5. Chain of Responsibility      A way of passing a request between a chain of objects

6. Interpreter          A way to include language elements in a program

7. Mediator             Defines simplified communication between classes

8. Memento            Capture and restore an object's internal state

9. State                 Alter an object's behavior when its state changes

10.Visitor               Defines a new operation to a class without change

11.Template method            Defer the exact steps of an algorithm to a subclass 

2. 几点基本思想

***为什么用设计模式?
应对变化(客户、技术),提高复用。
客户需求变化是软件的一部分,软件工程师是不应该去抱怨的,而是要去解决的。


***怎么用设计模式?
Refactoring to patterns
敏捷软件开发: 源代码即设计 设计模式是通过重构得来的,不是拿来套用的(没有一步到位的实际模式)
要求设计者在设计之初就预见所有的变化,并不容易,再者如果考虑全面的设计,会带来设计上的冗余。应该采用渐进式的设计,遇到需求变化时,利用重构的方法,改进现有的设计。
架构师设计好,交给Dev就好了?不,这就是软件工程危机


***如何学习设计模式?
从设计层面学习设计模式 而不是从代码结构学习。


***面向对象设计原则是模式的基础
OOPL并不是面向对象的全部
我们从封装、继承、多态开始接触面向对象,却忽视了从设计上来理解面向对象
这三种机制只是面向对象在语言层面上的实现。所以我们不能仅仅停留在语言这个层面上。
使用面向对象语言就是实现了面向对象开发?不见得!


***Change

不能杜绝改变 只能降低改变的影响
面向对象的构建方式能将变化带来的影响减为最小
OCP Software entities should be open for extension,but closed for modification
(凳子增加防水功能,加防水材质是extension; 改变凳子材质是modification)


3.GRASP (职责分配原则)

要学习设计模式,有些基础知识是我们必须要先知道的,设计模式是关于类和对象的一种高效、灵活的使用方式,也就是说,必须先有类和对象,才能有设计模式的用武之地,否则一切都是空谈,那么类和对象是从那冒出来的呢?这时就需要比23种设计模式更重要更经典的GRASP模式登场了,嘿嘿,原来这才是老大!

GRASP(General Responsibility Assignment Software Patterns),中文名称为“通用职责分配软件模式”,GRASP一共包括9种模式,它们描述了对象设计和职责分配的基本原则。也就是说,如何把现实世界的业务功能抽象成对象,如何决定一个系统有多少对象,每个对象都包括什么职责,GRASP模式给出了最基本的指导原则。初学者应该尽快掌握、理解这些原则,因为这是如何设计一个面向对象系统的基础。可以说,GRASP是学习使用设计模式的基础。


1.     
Information Expert (信息专家)

信息专家模式是面向对象设计的最基本原则,是我们平时使用最多,应该跟我们的思想融为一体的原则。也就是说,我们设计对象()的时候,如果某个类拥有完成某个职责所需要的所有信息,那么这个职责就应该分配给这个类来实现。这时,这个类就是相对于这个职责的信息专家。

例如常见的网上商店里的购物车(ShopCar),需要让每种商品(SKU)只在购物车内出现一次,购买相同商品,只需要更新商品的数量即可。如下图:

针对这个问题需要权衡的是,比较商品是否相同的方法需要放到那里类里来实现呢?分析业务得知需要根据商品的编号(SKUID)来唯一区分商品,而商品编号是唯一存在于商品类里的,所以根据信息专家模式,应该把比较商品是否相同的方法放在商品类里。  

 


2.     
Creator (创造者)

实际应用中,符合下列任一条件的时候,都应该由类A来创建类B,这时A是B的创建者:

a.       A是B的聚合

b.       A是B的容器

c.       A持有初始化B的信息(数据)

d.       A记录B的实例

e.       A频繁使用B

如果一个类创建了另一个类,那么这两个类之间就有了耦合,也可以说产生了依赖关系。依赖或耦合本身是没有错误的,但是它们带来的问题就是在以后的维护中会产生连锁反应,而必要的耦合是逃不掉的,我们能做的就是正确地创建耦合关系,不要随便建立类之间的依赖关系,那么该如何去做呢?就是要遵守创建者模式规定的基本原则,凡是不符合以上条件的情况,都不能随便用A创建B

例如:因为订单(Order)是商品(SKU)的容器,所以应该由订单来创建商品。如下图:

       这里因为订单是商品的容器,也只有订单持有初始化商品的信息,所以这个耦合关系是正确的且没办法避免的,所以由订单来创建商品。  

 


3.     
Low coupling (低耦合)

低耦合模式的意思就是要我们尽可能地减少类之间的连接。

其作用非常重要:

a.       低耦合降低了因一个类的变化而影响其他类的范围。

b.       低耦合使类更容易理解,因为类会变得简单,更内聚。

下面这些情况会造成类AB之间的耦合:

a.       AB的属性

b.       A调用B的实例的方法

c.       A的方法中引用了B,例如BA方法的返回值或参数。

d.       AB的子类,或者A实现了B

关于低耦合,还有下面一些基本原则:

a.       Don’t Talk to Strangers原则:

意思就是说,不需要通信的两个对象之间,不要进行无谓的连接,连接了就有可能产生问题,不连接就一了百了啦!

b.       如果A已经和B有连接,如果分配A的职责给B不合适的话(违反信息专家模式),那么就把B的职责分配给A

c.       两个不同模块的内部类之间不能直接连接,否则必招报应!嘿!

例如Creator模式的例子里,实际业务中需要另一个出货人来清点订单(Order)上的商品(SKU),并计算出商品的总价,但是由于订单和商品之间的耦合已经存在了,那么把这个职责分配给订单更合适,这样可以降低耦合,以便降低系统的复杂性。如下图:

       这里我们在订单类里增加了一个TotalPrice()方法来执行计算总价的职责,没有增加不必要的耦合。 

 

              4.      High cohesion (高内聚)

高内聚的意思是给类尽量分配内聚的职责,也可以说成是功能性内聚的职责。即功能性紧密相关的职责应该放在一个类里,并共同完成有限的功能,那么就是高内聚合。这样更有利于类的理解和重用,也便于类的维护。

高内聚也可以说是一种隔离,就想人体由很多独立的细胞组成,大厦由很多砖头、钢筋、混凝土组成,每一个部分()都有自己独立的职责和特性,每一个部分内部发生了问题,也不会影响其他部分,因为高内聚的对象之间是隔离开的。

例如:一个订单数据存取类(OrderDAO),订单即可以保存为Excel模式,也可以保存到数据库中;那么,不同的职责最好由不同的类来实现,这样才是高内聚的设计,如下图:

       这里我们把两种不同的数据存储功能分别放在了两个类里来实现,这样如果未来保存到Excel的功能发生错误,那么就去检查OrderDAOExcel类就可以了,这样也使系统更模块化,方便划分任务,比如这两个类就可以分配个不同的人同时进行开发,这样也提高了团队协作和开发进度。  
 

 

5.      Controller (控制器)

用来接收和处理系统事件的职责,一般应该分配给一个能够代表整个系统的类,这样的类通常被命名为“XX处理器”、“XX协调器”或者“XX会话”。

关于控制器类,有如下原则:

a.       系统事件的接收与处理通常由一个高级类来代替。

b.       一个子系统会有很多控制器类,分别处理不同的事务。

关于这个模式更详细的论述,请参考《UML和模式应用》第16章。   

 

6.      Polymorphism (多态)

这里的多态跟OO三大基本特征之一的“多态”是一个意思。

例如:我们想设计一个绘图程序,要支持可以画不同类型的图形,我们定义一个抽象类Shape,矩形(Rectangle)、圆形(Round)分别继承这个抽象类,并重写(override)Shape类里的Draw()方法,这样我们就可以使用同样的接口(Shape抽象类)绘制出不同的图形,如下图:

这样的设计更符合高内聚和低耦合原则,虽然后来我们又增加了一个菱形(Diamond)类,对整个系统结构也没有任何影响,只要增加一个继承Shape的类就行了。  

 

 

7.      Pure Fabrication (纯虚构)

这里的纯虚构跟我们常说的纯虚构函数意思相近。高内聚低耦合,是系统设计的终极目标,但是内聚和耦合永远都是矛盾对立的。高内聚以为这拆分出更多数量的类,但是对象之间需要协作来完成任务,这又造成了高耦合,反过来毅然。该如何解决这个矛盾呢,这个时候就需要纯虚构模式,由一个纯虚构的类来协调内聚和耦合,可以在一定程度上解决上述问题。

例如:上面多态模式的例子,如果我们的绘图程序需要支持不同的系统,那么因为不同系统的API结构不同,绘图功能也需要不同的实现方式,那么该如何设计更合适呢?如下图:

这里我们可以看到,因为增加了纯虚构类AbstractShape,不论是哪个系统都可以通过AbstractShape类来绘制图形,我们即没有降低原来的内聚性,也没有增加过多的耦合,可谓鱼肉和熊掌兼得,哈哈哈! 

 

        8.      Indirection (间接)

“间接”顾名思义,就是这个事不能直接来办,需要绕个弯才行。绕个弯的好处就是,本来直接会连接在一起的对象彼此隔离开了,一个的变动不会影响另一个。就想我在前面的低耦合模式里说的一样,“两个不同模块的内部类之间不能直接连接”,但是我们可以通过中间类来间接连接两个不同的模块,这样对于这两个模块来说,他们之间仍然是没有耦合/依赖关系的。

 

9.      Protected Variations (受保护变化)

预先找出不稳定的变化点,使用统一的接口封装起来,如果未来发生变化的时候,可以通过接口扩展新的功能,而不需要去修改原来旧的实现。也可以把这个模式理解为OCP(开闭原则)原则,就是说一个软件实体应当对扩展开发,对修改关闭。在设计一个模块的时候,要保证这个模块可以在不需要被修改的前提下可以得到扩展。这样做的好处就是通过扩展给系统提供了新的职责,以满足新的需求,同时又没有改变系统原来的功能。关于OCP原则,后面还会有单独的论述。



面向对象设计原则
1. 针对接口编程,而不是针对实现编程 (方法返回接口类型对象)
2. 优先使用对象组合,而不是类继承 (类继承“白箱复用”耦合度高  对象组合“黑箱复用”耦合度低,被组合对象只要具有要使用的接口)
3. 封装变化点


***更具体的 面向对象设计原则
1. 单一职责原则(SRP)

   “就一个类而言,应该仅有一个引起它变化的原因。”也就是说,不要把变化原因各不相同的职责放在一起,因为不同的变化会影响到不相干的职责。再通俗一点地说就是,不该你管的事情你不要管,管好自己的事情就可以了,多管闲事害了自己也害了别人。(当然这里说的多管闲事跟见义勇为是两回事,我们提倡见义勇为!)

       例如:参考下图中的设计,图形计算程序只使用了正方形的Area()方法,永远不会使用Draw()方法,而它却跟Draw方法关联了起来。这违反了单一原则,如果未来因为图形绘制程序导致Draw()方法产生了变化,那么就会影响到本来毫不关系的图形计算程序。

            那么我们该怎么做呢?如下图,将不同的职责分配给不同的类,使单个类的职责尽量单一,就隔离了变化,这样他们也不会互相影响了。

 

2. 开放—封闭原则(OCP)

 

     Software entities should be open for extension,but closed for modification
“软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。”嘿!多么朴实的话语,第一次看这个原则的时候我都看傻了,我当时在想“这不是&#%做白日梦吗!不修改怎么扩展啊?”但是随着学习的深入,理解了这个“不修改”是什么意思,意思是“你可以随便增加新的类,但是不要修改原来的类”。从这个角度去理解就好多了,其实这里还是一个隔离变化的问题。

       例如:如下图,有一个客户端程序通过数据访问接口操作数据,对于这套系统来说,一开始计划使用的是SQL ServerOracle数据库,但是后来考虑到成本,改用免费的MySQL;那么对于客户端程序来说,后来数据的扩展对它没有任何影响,它在不知不觉间就用上了免费好用的MySQL数据库,这全要感谢OCP原则。

 

3. 依赖倒置原则(DIP)

“抽象不应该依赖于细节。细节应该依赖于抽象。”关于这个原则,还有种说法是.“高层不应该依赖于底层,两者都应该依赖于抽象。”其实怎么说都是对的,关键就是要理解一点,只有抽象的东西才是最稳定的,也就是说,我们依赖的是它的稳定。如果将来“抽象”也不稳定了,那么谁稳定我跟谁,其实说白了不就是傍大款吗!哈哈!

例如:参考下图的设计,一个开关跟灯直接连接在一起了,也就是说开关依赖于灯的打开和关闭方法,那么如果我想用这个开关也可以打开其他东西呢,比如电视、音响。显然这个设计是无法满足这个要了,因为我们依赖了细节而不是抽象,这个开关已经等价于“灯的开关”。

       那么我们该如何来设计一个通用的开关呢?参考下图的设计,OK!现在我们不仅可以打开灯,还可以打开电视和音响,甚至未来任何实现了“开关接口”的任何东西。

 

 

4. 接口隔离原则(ISP)

 

“不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。”这个说得很明白了,再通俗点说,不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。

 例如:参考下图的设计,在这个设计里,取款、存款、转帐都使用一个通用界面接口,也就是说,每一个类都被强迫依赖了另两个类的接口方法,那么每个类有可能因为另外两个类的方法(跟自己无关)而被影响。拿取款来说,它根本不关心“存款操作”和“转帐操作”,可是它却要受到这两个方法的变化的影响,真是土鳖!!!

        那么我们该如何解决这个问题呢?参考下图的设计,为每个类都单独设计专门的操作接口,使得它们只依赖于它们关系的方法,这样就不会互相影响,也就不会在发生土鳖的事情了!

 

5. 替换原则(LSP)  Liskov substitution

 

“子类型必须能够替换掉它们的基类型。”也就是说继承中的“IS A”关系是必须保证的,否则还算什么继承啊!如果违反了LSP原则,常会导致在运行时(RTTI)的类型判断违反OCP原则。

例如:函数A的参数是基类型,调用时传递的对象是子类型,正常情况下,增加子类型都不会影响到函数A的,如果违反了LSP,则函数A必须小心的判断传进来的具体类型,否则就会出错,这就已经违反了OCP原则

 

posted @ 2008-11-03 16:56  DylanWind  阅读(303)  评论(0编辑  收藏  举报