SOLID 软件设计原则

* 软件腐化的原因:

问题所在   设计目标
----------------------------------------------------------------------------
过于僵硬   可扩展性(新性能可以很容易加入系统)
过于脆弱   灵活性(修改不会波及其它)
复用率低  
粘度过高   可插入性(新功能容易加入系统(气囊加入方向盘))

* 提高系统可复用性的几点原则:
传统复用:
1. 代码的粘帖复用
2. 算法的复用
3. 数据结构的复用

* 可维护性与可复用性并不完全一致

* 对可维护性的支持:

过于僵硬、过于脆弱、复用率低、粘度过高的系统都必然导致可维护性的减弱。反过来,可扩展、可插入、灵活的设计是提高可维护性的基础,但不一定能够保证可维护性。

有些人开发的系统高度灵活,所有数据库字段都可以动态添加、删除,表也可以动态构建。可以说灵活性高、可插入、可扩展性也很强。但导致代码超级复杂,读起来根本就找不到北,更不用说修改了。这时候,可维护性就显得很低。甚至在维护代码时,不小心触动一些关键部件造成系统出现问题。

有时候我提倡为开发人员写代码(注意:不是注释)。有些代码不是给客户用的,而是给开发人员用的。包括必要的错误定位工具,内部调试断点,性能计数器等等。这些代码可以帮助提高系统的可维护性。

总之,可维护性是一个综合的概念,任何人都无法预料客户需求会发生什么样的变化,“未雨绸缪”和“亡羊补牢”都是提高可维护性的一个很好切入点。测试驱动开发以及契约式开发对提高可维护性也有不少帮助。


一、 "开放-封闭"原则(OCP)

Open-Closed Principle原则讲的是:一个软件实体应当对扩展开放,对修改关闭。

优点:
    通过扩展已有软件系统,可以提供新的行为,以满足对软件的新的需求,使变化中的软件有一定的适应性和灵活性。
    已有软件模块,特别是最重要的抽象层模块不能再修改,这使变化中的软件系统有一定的稳定性和延续性。

二、 里氏代换原则(LSP)

Liskov Substitution Principle(里氏代换原则):子类型(subtype)必须能够替换它们的基类型。


三、 依赖倒置原则(DIP)

依赖倒置(Dependence Inversion Principle)原则讲的是:要依赖于抽象,不要依赖于具体。

简单的说,依赖倒置原则要求客户端依赖于抽象耦合。原则表述:

抽象不应当依赖于细节;细节应当依赖于抽象;
要针对接口编程,不针对实现编程。

反面例子:

 

缺点:耦合太紧密,Light发生变化将影响ToggleSwitch。

解决办法一:
将Light作成Abstract,然后具体类继承自Light。

 SOLID 软件设计原则_第1张图片

优点:ToggleSwitch依赖于抽象类Light,具有更高的稳定性,而BulbLight与TubeLight继承自Light,可以根据"开放-封闭"原则进行扩展。只要Light不发生变化,BulbLight与TubeLight的变化就不会波及ToggleSwitch。

缺点:如果用ToggleSwitch控制一台电视就很困难了。总不能让TV继承自Light吧。

解决方法二:
 SOLID 软件设计原则_第2张图片

优点:更为通用、更为稳定。

结论:
使用传统过程化程序设计所创建的依赖关系,策略依赖于细节,这是糟糕的,因为策略受到细节改变的影响。依赖倒置原则使细节和策略都依赖于抽象,抽象的稳定性决定了系统的稳定性。

四、 接口隔离原则(ISP)

接口隔离原则(Interface Segregation Principle)讲的是:使用多个专门的接口比使用单一的总接口总要好。换而言之,从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小接口上的。

过于臃肿的接口是对接口的污染。不应该强迫客户依赖于它们不用的方法。

My object-oriented umbrella(摘自Design Patterns Explained)

Let me tell you about my great umbrella. It is large enough to get into! In fact, three or four other people can get in it with me. While we are in it, staying out of the rain, I can move it from one place to another. It has a stereo system to keep me entertained while I stay dry. Amazingly enough, it can also condition the air to make it warmer or colder. It is one cool umbrella.

My umbrella is convenient. It sits there waiting for me. It has wheels on it so that I do not have to carry it around. I don't even have to push it because it can propel itself. Sometimes, I will open the top of my umbrella to let in the sun. (Why I am using my umbrella when it is sunny outside is beyond me!)

In Seattle, there are hundreds of thousands of these umbrellas in all kinds of colors. Most people call them cars.

实现方法:
1、 使用委托分离接口
2、 使用多重继承分离接口

五、 合成/聚合复用原则(CARP)

合成/聚合复用原则(Composite/Aggregate Reuse Principle或CARP)经常又叫做合成复用原则(Composite Reuse Principle或CRP),就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新对象通过向这些对象的委派达到复用已有功能的目的。

简而言之,要尽量使用合成/聚合,尽量不要使用继承。

o Design to interfaces.
o Favor composition over inheritance.
o Find what varies and encapsulate it.
(摘自:Design Patterns Explained)

区分"Has-A"与"Is-A"

"Is-A"是严格的分类学意义上定义,意思是一个类是另一个类的"一种"。而"Has-A"则不同,它表示某一个角色具有某一项责任。

导致错误的使用继承而不是合成/聚合的一个常见的原因是错误的把"Has-A"当作"Is-A"。

例如:
 SOLID 软件设计原则_第3张图片

实际上,雇员、经理、学生描述的是一种角色,比如一个人是"经理"必然是"雇员",另外一个人可能是"学生雇员",在上面的设计中,一个人无法同时拥有多个角色,是"雇员"就不能再是"学生"了,这显然是不合理的。

错误源于把"角色"的等级结构与"人"的等级结构混淆起来,误把"Has-A"当作"Is-A"。解决办法:

 SOLID 软件设计原则_第4张图片

六、 迪米特法则(LoD)

迪米特法则(Law of Demeter或简写LoD)又叫最少知识原则(Least Knowledge Principle或简写为LKP),也就是说,一个对象应当对其它对象有尽可能少的了解。

其它表述:
  只与你直接的朋友们通信
  不要跟"陌生人"说话
  每一个软件单位对其它的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

迪米特法则与设计模式
Facade模式、Mediator模式

使民无知
《老子》第三章曰:"是以圣人之治,虚其心,实其腹,弱其志,常使民无知无欲。"使被"统治"的对象"愚昧"化,处于"无知"的状态,可以使"统治"的成本降低。
所谓"最少知识"原则,实际上便是老子的"使民无知"的统治之术。

不相往来
《老子》云:"小国寡民……邻国相望,鸡犬之声相闻,民至老死,不相往来。"将被统治的对象隔离开来,使它们没有直接的通信,可以达到分化瓦解,继而分而治之的效果。迪米特法则与老子的"小国寡民"的统治之术不谋而合。


参考文献:
阎宏,《Java与模式》,电子工业出版社
[美]James W. Cooper,《C#设计模式》,电子工业出版社
[美]Alan Shalloway  James R. Trott,《Design Patterns Explained》,中国电力出版社
[美]Robert C. Martin,《敏捷软件开发-原则、模式与实践》,清华大学出版社
[美]Don Box, Chris Sells,《.NET本质论 第1卷:公共语言运行库》,中国电力出版社
http://www.dofactory.com/Patterns/Patterns.aspx


所谓“倒置”,我的理解是抽象来源于细节,即对细节的抽象,而反过来在设计的时候要依赖抽象,实现细节依赖抽象,是为“倒置”。

过于臃肿的接口是对接口的污染,这句话很精典~


 SOLID 软件设计原则

Initial

Stands for
(acronym)

Concept

S

SRP

Single responsibility principle

The notion that an object should have only a single responsibility.

O

OCP

Open/closed principle

The notion that “software entities … should be open for extension, but closed for modification”.

L

LSP

Liskov substitution principle

The notion that “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program”. See alsodesign by contract.

I

ISP

Interface segregation principle

The notion that “many client specific interfaces are better than one general purpose interface.

D

DIP

Dependency inversion principle

The notion that one should “Depend upon Abstractions. Do not depend upon concretions.”
Dependency injection is one method of following this principle.

  迪米特法则(Law ofDemeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。英文简写为: LoD.

  迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。

  迪米特法则不希望类直接建立直接的接触。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。

  有兴趣可以研究一下设计模式的门面模式(Facade)和中介模式(Mediator),都是迪米特法则应用的例子。

  狭义的迪米特法则的缺点:

  在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的商务逻辑无关。

  遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。

  门面模式和调停者模式实际上就是迪米特法则的应用。

常见23种模式概述

1) 抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

2) 适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。

3) 桥梁模式(Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化。

4) 建造模式(Builder):将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。

5) 责任链模式(Chain of Responsibility):为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。

6) 命令模式(Command):将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。

7) 合成模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。

8) 装饰模式(Decorator):动态地给一个对象添加一些额外的职责。就扩展功能而言,它能生成子类的方式更为灵活。

9) 门面模式(Facade):为子系统中的一组接口提供一个一致的界面,门面模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

10) 工厂方法(Factory Method):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method 使一个类的实例化延迟到其子类。

11) 享元模式(Flyweight):运用共享技术以有效地支持大量细粒度的对象。

12) 解释器模式(Interpreter):给定一个语言,定义它的语法的一种表示,并定义一个解释器,该解释器使用该表示解释语言中的句子。

13) 迭代子模式(Iterator):提供一种方法顺序访问一个聚合对象中的各个元素,而又不需暴露该对象的内部表示。

14) 调停者模式(Mediator):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式的内部表示。

15) 备忘录模式(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。

16) 观察者模式(Observer):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。

17) 原始模型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这个原型创建新的对象。

18) 代理模式(Proxy):为其他对象提供一个代理以控制对这个对象的访问。

19) 单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点。

20) 状态模式(State):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。

21) 策略模式(Strategy):定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。

22) 模板模式(Template Method):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

23) 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作。该模式可以实现在不改变各元素的类的前提下定义作用于这些元素的新操作。


Bridge模式的特征:

                意图:将一组实现与另一组使用他们的对象分离

                问题:一个抽象类派生类 必须使用多个实现 ,但出现类数量增长 

  未使用Bridge实例 

Bridge模式实例 

  1. abstract   class  Shape{   
  2.      public   void  draw();   
  3. }      
  4. class  Rectangle  extends  Shape{}      
  5. class  Circle  extends  Shape{}   
  6.   
  7. //这里业务出现了多种画图方式,DP1,DP2……     
  8. //使用继承,创建不同绘图的类,类数量增多   
  9.   
  10. class  V1Rectangle  extends  Rectangle{   
  11.      public   void  draw(){   
  12.         DP1.draw_line();   
  13.     }   
  14. }      
  15. class  V2Rectangle  extends  Rectangle{   
  16.      public   void  draw(){   
  17.         DP2.draw_line();   
  18.     }   
  19. }      
  20. class  V1Circle  extends  Circle{   
  21.      public   void  draw(){   
  22.         DP1.draw_Circle();   
  23.     }   
  24. }      
  25. class  V2Circle  extends  Circle{   
  26.      public   void  draw(){   
  27.         DP2.draw_Circle();   
  28.     }   
  29. }
  1. abstract   class  Shape{   
  2.      public   void  draw();   
  3. }      
  4. //这里业务出现了多种画图方式,DP1,DP2……   
  5. //抽象出接口出DP1,DP2   
  6. interface  Drawing{   
  7.      public   void  drawLine();  
  8.      public   void  drawCircle();  
  9. }      
  10. class  V1Drawing{   
  11.      public   void  drawLine(){};  
  12.      public   void  drawCircle(){};  
  13. }      
  14. class  V2Drawing{   
  15.      public   void  drawLine(){};  
  16.      public   void  drawCircle(){};  
  17. }      
  18. //使用组合 ,聚集Drawing   
  19. class  Rectangle  extends  Shape{   
  20.      public   void  draw(Drawing dp){  
  21.         dp.drawLine();  
  22.     }  
  23. }      
  24. class  Circle  extends  Shape{   
  25.      public   void  draw(Drawing dp){  
  26.         dp.drawCircle();  
  27.     }  
  28. }
  29. //抽象类Shape的派生类,使用一组实现(DP1,DP2)的接口
//使得派生类不依赖于一组具体的实现,从设计模式而言,这称为Bridge模式

我想大家小时候都有用蜡笔画画的经历吧。红红绿绿的蜡笔一大盒,根据想象描绘出格式图样。而毛笔下的国画更是工笔写意,各展风采。而今天我们的故事从蜡笔与毛笔说起。

设想要绘制一幅图画,蓝天、白云、绿树、小鸟,如果画面尺寸很大,那么用蜡笔绘制就会遇到点麻烦。毕竟细细的蜡笔要涂出一片蓝天,是有些麻烦。如果有可能,最好有套大号蜡笔,粗粗的蜡笔很快能涂抹完成。至于色彩吗,最好每种颜色来支粗的,除了蓝天还有绿地呢。这样,如果一套12种颜色的蜡笔,我们需要两套24支,同种颜色的一粗一细。呵呵,画还没画,开始做梦了:要是再有一套中号蜡笔就更好了,这样,不多不少总共36支蜡笔。

SOLID 软件设计原则_第5张图片

再看看毛笔这一边,居然如此简陋:一套水彩12色,外加大中小三支毛笔。你可别小瞧这"简陋"的组合,画蓝天用大毛笔,画小鸟用小毛笔,各具特色。

SOLID 软件设计原则_第6张图片 

呵呵,您是不是已经看出来了,不错,我今天要说的就是Bridge模式。为了一幅画,我们需要准备36支型号不同的蜡笔,而改用毛笔三支就够了,当然还要搭配上12种颜料。通过Bridge模式,我们把乘法运算3×12=36改为了加法运算3+12=15,这一改进可不小。那么我们这里蜡笔和毛笔到底有什么区别呢?

实际上,蜡笔和毛笔的关键一个区别就在于笔和颜色是否能够分离。【GOF95】桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。关键就在于能否脱耦。蜡笔的颜色和蜡笔本身是分不开的,所以就造成必须使用36支色彩、大小各异的蜡笔来绘制图画。而毛笔与颜料能够很好的脱耦,各自独立变化,便简化了操作。在这里,抽象层面的概念是:"毛笔用颜料作画",而在实现时,毛笔有大中小三号,颜料有红绿蓝等12种,于是便可出现3×12种组合。每个参与者(毛笔与颜料)都可以在自己的自由度上随意转换。

蜡笔由于无法将笔与颜色分离,造成笔与颜色两个自由度无法单独变化,使得只有创建36种对象才能完成任务。Bridge模式将继承关系转换为组合关系,从而降低了系统间的耦合,减少了代码编写量。

Bridge与Strategy模式

   桥接(Bridge)模式是结构型模式的一种,而策略(strategy)模式则属于行为模式。以下是它们的UML结构图。

   在桥接模式中,Abstraction通过聚合的方式引用Implementor。

SOLID 软件设计原则_第7张图片

   在策略模式中,Context也使用聚合的方式引用Startegy抽象接口。

SOLID 软件设计原则_第8张图片

从他们的结构图可知,在这两种模式中,都存在一个对象使用聚合的方式引用另一个对象的抽象接口的情况,而且该抽象接口的实现可以有多种并且可以替换。可以说两者在表象上都是调用者与被调用者之间的解耦,以及抽象接口与实现的分离。

那么两者的区别体现在什么地方呢?

1.首先,在形式上,两者还是有一定区别的,对比两幅结构图,我们可以发现,在桥接模式中不仅Implementor具有变化(ConcreateImplementior),而且Abstraction也可以发生变化(RefinedAbstraction),而且两者的变化是完全独立的,RefinedAbstraction与ConcreateImplementior之间松散耦合,它们仅仅通过Abstraction与Implementor之间的关系联系起来。而在策略模式中,并不考虑Context的变化,只有算法的可替代性。

2.其次在语意上,桥接模式强调Implementor接口仅提供基本操作,而Abstraction则基于这些基本操作定义更高层次的操作。而策略模式强调Strategy抽象接口的提供的是一种算法,一般是无状态、无数据的,而Context则简单调用这些算法完成其操作。

3. 桥接模式中不仅定义Implementor的接口而且定义Abstraction的接口,Abstraction的接口不仅仅是为了与Implementor通信而存在的,这也反映了结构型模式的特点:通过继承、聚合的方式组合类和对象以形成更大的结构。在策略模式中,Startegy和Context的接口都是两者之间的协作接口,并不涉及到其它的功能接口,所以它是行为模式的一种。行为模式的主要特点就是处理的是对象之间的通信方式,往往是通过引入中介者对象将通信双方解耦,在这里实际上就是将Context与实际的算法提供者解耦。

所以相对策略模式,桥接模式要表达的内容要更多,结构也更加复杂。桥接模式表达的主要意义其实是接口隔离的原则,即把本质上并不内聚的两种体系区别开来,使得它们可以松散的组合,而策略在解耦上还仅仅是某一个算法的层次,没有到体系这一层次。从结构图中可以看到,策略的结构是包容在桥接结构中的,桥接中必然存在着策略模式,Abstraction与Implementor之间就可以认为是策略模式,但是桥接模式一般Implementor将提供一系列的成体系的操作,而且Implementor是具有状态和数据的静态结构。而且桥接模式Abstraction也可以独立变化。

 

DesignPattern: State 模式

如果您不了解TCP的连线方式,在看 Gof的书介绍State模式时,大概会看得一头雾水吧!TCP的连线状态图,光是要了解就要花点精神了,它的连线状态很多,用来说明状态模式确实很适合,但不适合教导初学模式的人。

由简单的开始会比较好理解状态模式的作用,先来看一个例子,如果您有一个只能顺时针转动的瓦斯开关,转动一次的状态为off、 small fire、medium fire与large fire,您如何在程式中控制状态的变化与行为呢?一个最简单的方式就是用if..else或是switch流程来控制,例如:

  • State.java
    
    
    
    
?
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
public class State {
 
     private int state;
 
     public State() {
         state = 0 ;
     }
 
     public void switchFire() {
 
         if (state == 0 ) {
             state = 1 ;
             System.out.println( "small fire" );
         } else if (state == 1 ) {
             state = 2 ;
             System.out.println( "medium fire" );
 
         } else if (state == 2 ) {
             state = 3 ;
             System.out.println( "large fire" );
         } else {
             state = 0 ;
             System.out.println( "turning off" );
         }
     }
  • Main.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
public class Main {
      public static void main(String[] args) {
         State state = new State();
         state.switchFire();
         state.switchFire();
         state.switchFire();
         state.switchFire();
     }
}

这个方法很简单,每个人都会,但如果您的状态变化并不是流水式的变化,而是像TCP连线状态一样,会是一个网络图的时候,用 if...else或switch来写的话,您的程式就会乱的不像话了;来考虑如何让物件控制自己的状态转换与所应表现的行为,这个程式可以这样改写:

  • IState.java
    
    
    
    
?
1
2
3
public interface IState {
     public void switchFire(FireSwitch sw);
}
  • OffState
    
    
    
    
?
1
2
3
4
5
6
7
public class OffState implements IState {
 
     public void switchFire(FireSwitch sw) {
         sw.setState( new SmallState());
         System.out.println( "small fire" );
     }
}
  • SmallState.java
    
    
    
    
?
1
2
3
4
5
6
7
public class SmallState implements IState {
 
     public void switchFire(FireSwitch sw) {
         sw.setState( new MediumState());
         System.out.println( "medium fire" );
     }
  • MediumState.java
    
    
    
    
?
1
2
3
4
5
6
7
public class MediumState implements IState {
 
     public void switchFire(FireSwitch sw) {
         sw.setState( new LargeState());
         System.out.println( "large fire" );
     }
  • LargeState.java
    
    
    
    
?
1
2
3
4
5
6
7
public class LargeState implements IState {
 
     public void switchFire(FireSwitch sw) {
         sw.setState( new OffState());
         System.out.println( "off fire" );
     }
  • FireSwitch.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FireSwitch {
 
     private State current;
 
     public FireSwitch() {
         current = new OffState();
     }
 
     public void setState(State s) {
         current = s;
     }
 
     public void switchFire() {
         current.switchFire( this );
     }
}
  • Main.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
public class Main {
 
     public static void main(String[] args) {
         FireSwitch fireSwitch = new FireSwitch();
         fireSwitch.switchFire();
         fireSwitch.switchFire();
         fireSwitch.switchFire();
         fireSwitch.switchFire();
     }
}

程式执行结果与上一个例子是一样的,但这次并没有用流程控制来进行状态转换,而由物件自行控制自己的状态,与必须表现的行为,这个方式就是State 模式,将这个例子的 UML 类别结构画出就如下所示:

 SOLID 软件设计原则_第9张图片


再进一步考虑开关可以顺时针与逆时针转动,这时如果您仍以if...else或switch来写,就会让流程显示复杂,来看看如何使用状态模式来撰写:

  • IState.java
    
    
    
    
?
1
2
3
4
public interface IState {
     public void switchClockWise(FireSwitch sw);
     public void switchCountClock(FireSwitch sw);
  • OffState.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
public class OffState implements IState {
     public void switchClockWise(FireSwitch sw) {
         sw.setState( new SmallState());
         System.out.println( "small fire" );
     }
 
     public void switchCountClock(FireSwitch sw) {
         sw.setState( new LargeState());
         System.out.println( "large fire" );
     }
  • SmallState.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
public class SmallState implements IState {
 
     public void switchClockWise(FireSwitch sw) {
         sw.setState( new MediumState());
         System.out.println( "medium fire" );
     }
 
     public void switchCountClock(FireSwitch sw) {
         sw.setState( new OffState());
         System.out.println( "off fire" );
     }
  • MediumState.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
public class MediumState implements IState {
 
     public void switchClockWise(FireSwitch sw) {
         sw.setState( new LargeState());
         System.out.println( "large fire" );
     }
 
     public void switchCountClock(FireSwitch sw) {
         sw.setState( new SmallState());
         System.out.println( "small fire" );
     }
  • LargeState.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
public class LargeState implements State {
 
     public void switchClockWise(FireSwitch sw) {
         sw.setState( new OffState());
         System.out.println( "off fire" );
     }
 
     public void switchCountClock(FireSwitch sw) {
         sw.setState( new MediumState());
         System.out.println( "medium fire" );
     }
  • FireSwitch.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FireSwitch {
 
     private State current;
     public FireSwitch() {
         current = new OffState();
     }
 
     public void setState(State s) {
         current = s;
     }
 
     public void switchClockWise() {
         current.switchClockWise( this );
     }
 
     public void switchCountClock() {
        current.switchCountClock( this );
     }
  • Main.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
 
     public static void main(String[] args) {
         FireSwitch fireSwitch = new FireSwitch();
         fireSwitch.switchClockWise();
         fireSwitch.switchClockWise();
         fireSwitch.switchClockWise();
         fireSwitch.switchClockWise();
 
         System.out.println();
         fireSwitch.switchCountClock();
         fireSwitch.switchCountClock();
         fireSwitch.switchCountClock();
         fireSwitch.switchCountClock();
     }
}

接下来您可以任意的转动开关了,无论是顺时针转动或是逆时针转动,状态的转换都由物件自己来表现,这是双向状态转换下的例子,如果一个状态可能转换至三个以上的状态,使用State模式就更可以看出它的好处了,就像Gof的TCP连线例子一样,如果您了解TCP连线,可以看看原书是如何实现TCP连线之间的状态转换的。

State模式的UML结构图如下:

SOLID 软件设计原则_第10张图片 

DesignPattern: Observer 模式

假设今天您设计一个试算表程式,当中有一个资料物件,您可以用表格图形物件、柱状图形物件、圆饼图形物件等方式来呈现物件,无论您是用哪种图形物件,重点是若资料物件的内容作了更改,则图形物件的内容也必须跟着修改,或许您的程式中有两个以上的图形物件来呈现资料,您在图形物件上更动资料,则另一个图形物件也必须作出相对应的变化。

主题

资料物件

观察者

柱状图形

表格图形

圆饼图形


又假设您今天设计一个网路游戏,您在伺服器上维护一个连线客户端共享的资料物件,当其中一个客户端作了操作,将对此资料物件作修改,则伺服器必须通知其它客户端作相对应的变化(像是人物位置走动、建了一个城堡等)。

主题

资料物件

观察者

客户端一

客户端二

客户端三


在Observer模式中的主角为主题(subject)与观察者(observer),观察者订阅它感兴趣的主题,一个主题可以被多个观察者订阅,当主题的状态发生变化时,它必须通知(notify)所有订阅它的观察者,观察者检视主题的状态变化,并作出对应的动作,所以Observer 模式也称之为Publish-Subscribe模式。
Observer模式的 UML 图如下所示:

SOLID 软件设计原则_第11张图片 


Subject类中有一个notify()方法,通常是在Subject的状态发生改变时呼叫它,notify()中会呼叫 Observer的update()方法,通常会先取得Subject的新状态,然后更新Observer的显示或行为,这个过程我们可以透过 Sequence Diagram来表达:

 SOLID 软件设计原则_第12张图片


在Java中支援观察者模式,要成为观察者的类必须实作Observer介面,这个介面中定义了一个update()方法,这个方法会被主题物件在通知状态变化时呼叫,您必须在这个方法中实作您所想要的对应行为。

主题物件会是Observable的子类,在这边注意两个重要的方法:setChanged()与notifyObserver()。 setChanged()是用来设定主题物件的状态已经被改变,而notifyObserver()方法会通知所要订阅主题物件的观察者,调用其 update()方法。

有兴趣的话,建议看一下Java的Observable.java中是如何实作的,这有助于了解Observer模式的运作方式。

DesignPattern: Adapter 模式 - ObjectAdapter

您的电脑是个旧电脑,新的滑鼠都在使用USB接口了,而您的电脑上并没有USB,而只有一个PS2接口,这时您可以使用一个USB转PS2的接头作为转换,这样您的电脑就可以使用新滑鼠了(当然您也可以使用USB扩充卡,意思是相同的)。

类似的概念,有时候您想在原来的程式中加入一个外部元件,例如一个类别,但是这个类别与您目前所设计的程式在介面上并不一致,为了让这个外部类与原程式的介面一致,您必须使用一个类别作为中介来配接它们,这时您可以采用Adapter模式。

举个例子来说,在Java 1.0中有个Enumeration,您在这个版本发行之后,使用它来设计了一个MessageApplication,例如:

  • MessageApplication.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.*;
 
public class MessageApplication {
 
     public void showAllMessage(Enumeration enum ) {
 
         Object msg;
         while ( enum .hasMoreElements()) {
             msg = enum .nextElement();
             System.out.println(msg);
         }
     }    
}

您的客户端程式是这么使用MessageApplication的:

  • MessageClient.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.*;
 
public class MessageClient {
 
     private MessageApplication msgApp;
 
     public void run() {
         Vector vector = new Vector();
         for ( int i = 0 ; i < 10 ; i++)
             vector.addElement( "物件 " + i);       
 
         msgApp = new MessageApplication();
         msgApp.showAllMessage(vector.elements());
     }   
 
     public static void main(String[] args) {
         MessageClient msgClient = new MessageClient();
         msgClient.run();
     }
}

现在Java 1.2中新增了Iterator,您想要使用它的功能,但基本上您不想更动原来程式中已设计好的MessageApplication类别,这时候您可以使用Adapter模式,将Iterator的介面转换为Enumeration相容,例如:

  • IteratorAdapter.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.*;
 
public class IteratorAdapter implements Enumeration {
 
     private Iterator iterator;
 
     IteratorAdapter(Iterator iterator) {
         this .iterator = iterator;  
     }
 
     // 转接介面
     public boolean hasMoreElements() {
         return iterator.hasNext();
     }
 
     public Object nextElement() throws NoSuchElementException {
         return iterator.next();
     }
}

您可以在客户端程式中照样使用MessageApplication类别,而不用作任何的变动:

  • MessageClient.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.*;
 
public class MessageClient {
 
     // We could still use MessageApplication
     private Enumeration iteratorAdapter;   
 
     public void run() {
         List arrayList = new ArrayList();
         for ( int i = 0 ; i < 10 ; i++)
             arrayList.add( "物件 " + i);       
 
         iteratorAdapter = new IteratorAdapter(arrayList.iterator());
         // We could still use MessageApplication
         MessageApplication msgApp = new MessageApplication();      
         msgApp.showAllMessage(iteratorAdapter);
     }
 
     public static void main(String[] args) {
         MessageClient msgClient = new MessageClient();
         msgClient.run();
     }
}

如程式所示的,透过Adapter模式,您原有程式中已设计好的类别不用更动,就可以引进新类别的功能,将上面的程式UML类别结构画出如下:

SOLID 软件设计原则_第13张图片 


上面的作法,是将要引进的新类别当作Adapter类别的一个物件成员,这是IbObject Adapter模式,其抽象结构如下:

 

 SOLID 软件设计原则_第14张图片

DesignPattern: Adapter 模式- Class Adapter

Adapter模式的另一种作法是Class Adapter模式,在这个模式下,Adapter直接继承Adaptee(要引进的新类别),以拥有当中的成员及方法,在C++中的话可以这么作:

SOLID 软件设计原则_第15张图片 


C++中可以多重继承,但在Java中不行,所以在Java中若要采用Class Adapter,必须作点修改,一方面继承Adaptee,一方面实作Target的介面:

 SOLID 软件设计原则_第16张图片

代码的实现是这样的: 

       publicclass Adapter extends Adaptee implements Target {
              // ....
       }

当然,这必须您原先的Target定义了共同的介面,所以Class Adapter在Java中适用的场合较少,事实上,也比较建议使用Object Adapter,这样的Adapter模式比较有弹性,例如,您可以在Adapter上加上个setter,以随时可以抽换Adaptee。

在Java中,Class Adapter的一个应用场合是达到多重继承的效果,您一定在很多时候听别人说,介面(interface)可以达到多重继承的效果,这是怎么回事?

其实要讨论这个问题,首先您对于C++中多重继承要先有认识,新手看了书说介面可以达到多重继承,切莫人云亦云,尤其是没有学过C++的新手们,如果您对于C++多重继承想要有所认识,请先看看多重继承(一)多重继承(二)

Java不能多重继承,但为何说Java中可以使用介面(interface)来达到多重继承的效果,首先效果之一,就如 多重继承(二) 中描述的“ 多重继承时通常其中一个基底类别作为private实作体,而其它的用以表现完全的抽象介面。”,在Java中这个效果可以使用介面来达到,介面此时所扮演的即多重继承(二) 中的抽象类别,一个完全的抽象介面,这个效果的达成方式,如 介面(interface)型态 中所介绍的,您可以直接对应这两个主题中的程式实作来了解,了解Java中如何使用介面(interface)来达到C++中所谓多重继承的“一种”效果。

来看看另一个情况。
如果有SomeClass类别与OtherClass类别,您想要SomeAndOther类别可以同时拥有SomeClass类别与 OtherClass类别中已定义好的操作,并可以进行多型操作,在C++中可以用多重继承来达到,但在Java中显然的无法使用多重继承,怎么办?您可以在设计上先绕个弯,先使用两个介面分别定义好SomeClass与OtherClass两个类别的公开方法,例如:

public interface ISome {
    public void doSome();
}
public interface IOther {
    public void doOther();
}

接着让SomeClass与OtherClass分别实作两个介面:

public class SomeClass implements ISome{
    public void doSome() {
        ....
    }
}

public class OtherClass implementsIOther {
    public void doOther() {
        ....
    }
}

SomeAndOther如何同时拥有两个SomeClass与OtherClass类别已定义好的操作?并可以多型操作?SomeAndOther可以继承其中之一,并拥有其中之一,例如:

public class SomeAndOther extendsSomeClass implements IOther {
    private IOther other = new OtherClass();

    public void doOther() {
        other.doOther();
    }
}

虽不满意,但至少解决了目前的问题,当然这边只是其中一例,毕竟C++是C++,Java是Java,两者语法并不是一对一的关系,视实际需求还可以变化一下。

DesignPattern: Visitor 模式

在Java中所有的物件都继承自Object物件,这样作的优点之一,就是使得一些集合物件的资料结构容易管理,例如您可以将任何型态的物件放入Vector中。

然而现在有个问题是,如果您的集合(connection)物件中不仅储存一种型态的物件,如果想要对这些物件作出一些个别化的操作,首要条件就是要知道该物件的型态,使用 instanceof 似乎是个不错的方式,在程式简单的情况下,也许您会这么作:

?
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
public class ElementA {
    // some implementing
}
 
public class ElementB {
    // some implementing
}
 
public class ElementC {
    // some implementing
}
 
// ......
 
    Iterator iterator = arrayList.iterator()
    while (iterator.hasNext()) {
        if (o instanceof ElementA)
       (ElementA) o.operationA();
        else if (o instanceof ElementB)
        (ElementB) o.operationB();
     else if (o instanceof ElementC)
        (ElementC) o.operationC();
     else
        System.out.println(
                  "Sorry! I don't know who you are! "
                   + o.toString());
        //....
    }
    //....

 
这么作并不是不可以,只是将来的扩充性不大,如果今天您想要一次改变对每一种类型物件的操作,您必须修改很多地方。

从物件自身的角度来想好了,物件在一个个的房子中,物件说:“不要在房子外费尽心思判断了,即然您不知道我是谁,那么您就进来访问我好了,我告诉您我是谁,这么一来您就知道如何操作我了!”

用程式来实现上面这个描述:

  • IElement.java
    
    
    
    
?
1
2
3
public interface IElement {
     public void accept(IVisitor visitor);
}
  • ElementA.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
public class ElementA implements IElement {
     public void accept(IVisitor visitor) {
         visitor.visit( this );
     }
 
     public void operationA() {
         System.out.println( "do A's job....such-and-such...." );
     }
}
  • ElementB.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
public class ElementB implements IElement {
 
     public void accept(IVisitor visitor) {
         visitor.visit( this );
     }
 
     public void operationB() {
         System.out.println( "do B's job....such-and-such...." );
     }
}
  • ElementC.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
public class ElementC implements IElement {
 
     public void accept(IVisitor visitor) {
         visitor.visit( this );
     }
 
     public void operationC() {
         System.out.println( "do C's job....such-and-such...." );
     }
}
  • IVisitor.java
    
    
    
    
?
1
2
3
4
5
public interface IVisitor {
     public void visit(ElementA element);
     public void visit(ElementB element);
     public void visit(ElementC element);
  • VisitorA.java
     
     
     
     
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class VisitorA implements IVisitor {
 
     public void visit(ElementA element) {
         element.operationA();
     }
 
     public void visit(ElementB element) {
         element.operationB();
     }
 
     public void visit(ElementC element) {
         element.operationC();
     }
  • Main.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
 
     public static void main(String[] args) {
 
         // know nothing about their type after storing them into Element array
 
         IElement[] list = { new ElementA(), new ElementB(), new ElementC()};
         IVisitor visitor = new VisitorA();
         for ( int i= 0 ; i < list.length; i++)
             list[i].accept(visitor);
     }
}

Visitor访问是基于overload来完成,对于每一个实现IElement的物件来说,它接受IVisitor来访问它,在accept()方法中,IVisitor使用正确的方法来访问IElement(显然的,这么部份可以靠不同的函式名称,或是overload来达成),并在visit() 中对IElement作出对应的操作,如果您今天想要换掉每一个IElement的操作,只要更换IVisitor类型的物件就可以了,也就是这行:

 //IVisitor visitor = new VisitorA();
 // 换掉一个IVisitor,就可以换掉所有的操作
 // 不用修改多个地方
 IVisitor visitor = new VisitorB();

 
举个实际的例子,假设VisitorA只是个懒惰的推销员好了,今天有一个比较勤快的推销员VisitorB,在访问过IElement之后,会对 IElement作出更多的操作,要在程式中实现VisitorB,只要增加一个VisitorB类别就可以了:

  • VisitorB.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class VisitorB implements IVisitor {
 
     public void visit(ElementA element) {
         System.out.println( "VisitorB is a hard worker...." );
         element.operationA();
         System.out.println( "I want to do some extra work on A...." );
     }
 
     public void visit(ElementB element) {
         System.out.println( "VisitorB is a hard worker...." );
         element.operationB();
         System.out.println( "I want to do some extra work on B...." );
     }
 
     public void visit(ElementC element) {
         System.out.println( "VisitorB is a hard worker...." );
         element.operationC();
         System.out.println( "I want to do some extra work on C...." );
     }
}

改一下Main来示范:

  • Main.java
    
    
    
    
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
 
     public static void main(String[] args) {
         IElement[] list = { new ElementA(), new ElementB(), new ElementC()};
         System.out.println( "visitorA is coming......." );
         IVisitor visitorA = new VisitorA();
         for ( int i= 0 ; i < list.length; i++)
            list[i].accept(visitorA);
 
         System.out.println( "\nvisitorB is coming......." );
         IVisitor visitorB = new VisitorB();
         for ( int i= 0 ; i < list.length; i++)
             list[i].accept(visitorB);
     }
}

在范例中的System.out.println();只是个示意,它也可能是您对IElement的额外方法的直接调用。
Visitor模式的 UML 结构类图如下:

SOLID 软件设计原则_第17张图片 

Java World中有一篇文章,提到可以利用reflection来改进使用访问者模式时的弹性,有兴趣的可以进一步参考一下Reflecton the Visitor design pattern

一、 职责链(Chain ofResponsibility)模式

责任链模式是一种对象的行为模式【GOF95】。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。

从击鼓传花谈起

击鼓传花是一种热闹而又紧张的饮酒游戏。在酒宴上宾客依次坐定位置,由一人击鼓,击鼓的地方与传花的地方是分开的,以示公正。开始击鼓时,花束就开始依次传递,鼓声一落,如果花束在某人手中,则该人就得饮酒。

击鼓传花便是责任链模式的应用。责任链可能是一条直线、一个环链或者一个树结构的一部分。


二、 责任链模式的结构

责任链模式涉及到的角色如下所示:

 SOLID 软件设计原则_第18张图片

抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法,以设定和返回对下家的引用。这个角色通常由一个抽象类或接口实现。

具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。


三、 责任链模式的示意性源代码

?
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// Chain of Responsibility pattern -- Structural example 
using System;
 
// "Handler"
abstract class Handler
{
   // Fields
   protected Handler successor;
  
   // Methods
   public void SetSuccessor( Handler successor )
   {
     this .successor = successor;
   }
   abstract public void HandleRequest( int request );
}
 
// "ConcreteHandler1"
class ConcreteHandler1 : Handler
{
   // Methods
   override public void HandleRequest( int request )
   {
     if ( request >= 0 && request < 10 )
       Console.WriteLine( "{0} handled request {1}" ,
         this , request );
     else
       if ( successor != null )
       successor.HandleRequest( request );
   }
}
 
// "ConcreteHandler2"
class ConcreteHandler2 : Handler
{
   // Methods
   override public void HandleRequest( int request )
   {
     if ( request >= 10 && request < 20 )
       Console.WriteLine( "{0} handled request {1}" ,
         this , request );
     else
       if ( successor != null )
       successor.HandleRequest( request );
   }
}
// "ConcreteHandler3"
class ConcreteHandler3 : Handler
{
   // Methods
   override public void HandleRequest( int request )
   {
     if ( request >= 20 && request < 30 )
       Console.WriteLine( "{0} handled request {1}" ,
         this , request );
     else
       if ( successor != null )
       successor.HandleRequest( request );
   }
}
/**/ /// <summary>
/// Client test
/// </summary>
public class Client
{
   public static void Main( string [] args )
   {
     // Setup Chain of Responsibility
     Handler h1 = new ConcreteHandler1();
     Handler h2 = new ConcreteHandler2();
     Handler h3 = new ConcreteHandler3();
     h1.SetSuccessor(h2);
     h2.SetSuccessor(h3);
     // Generate and process request
     int [] requests = { 2, 5, 14, 22, 18, 3, 27, 20 };
     foreach ( int request in requests )
       h1.HandleRequest( request );
   }
}

四、 纯的与不纯的责任链模式

一个纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一个是承担责任,二是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又把责任向下传的情况。

在一个纯的责任链模式里面,一个请求必须被某一个处理者对象所接收;在一个不纯的责任链模式里面,一个请求可以最终不被任何接收端对象所接收。纯的责任链模式的例子是不容易找到的,一般看到的例子均是不纯的责任链模式的实现。


五、 责任链模式的实际应用案例

下面的责任链模式代码演示了不同职务的人根据所设定的权限对一个购买请求作出决策或将其交给更高的决策者。

?
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Chain of Responsibility pattern -- Real World example 
using System;
 
// "Handler"
abstract class Approver
{
   // Fields
   protected string name;
   protected Approver successor;
 
   // Constructors
   public Approver( string name )
   {
     this .name = name;
   }
 
   // Methods
   public void SetSuccessor( Approver successor )
   {
     this .successor = successor;
   }
 
   abstract public void ProcessRequest( PurchaseRequest request );
}
 
// "ConcreteHandler"
class Director : Approver
{
   // Constructors
   public Director ( string name ) : base ( name ) {}
 
   // Methods
   override public void ProcessRequest( PurchaseRequest request )
   {
     if ( request.Amount < 10000.0 )
       Console.WriteLine( "{0} {1} approved request# {2}" ,
         this , name, request.Number);
     else
       if ( successor != null )
       successor.ProcessRequest( request );
   }
}
 
// "ConcreteHandler"
class VicePresident : Approver
{
   // Constructors
   public VicePresident ( string name ) : base ( name ) {}
 
   // Methods
   override public void ProcessRequest( PurchaseRequest request )
   {
     if ( request.Amount < 25000.0 )
       Console.WriteLine( "{0} {1} approved request# {2}" ,
         this , name, request.Number);
     else
       if ( successor != null )
       successor.ProcessRequest( request );
   }
}
// "ConcreteHandler"
class President : Approver
{
   // Constructors
   public President ( string name ) : base ( name ) {}
   // Methods
   override public void ProcessRequest( PurchaseRequest request )
   {
     if ( request.Amount < 100000.0 )
       Console.WriteLine( "{0} {1} approved request# {2}" ,
         this , name, request.Number);
     else
       Console.WriteLine( "Request# {0} requires " +
         "an executive meeting!" , request.Number );
   }
}
// Request details
class PurchaseRequest
{
   // Member Fields
   private int number;
   private double amount;
   private string purpose;
 
   // Constructors
   public PurchaseRequest( int number, 
     double amount, string purpose )
   {
     this .number = number;
     this .amount = amount;
     this .purpose = purpose;
   }
   // Properties
   public double Amount
   {
     get { return amount; }
     set { amount = value; }
   }
   public string Purpose
   {
     get { return purpose; }
     set { purpose = value; }
   }
   public int Number
   {
     get { return number; }
     set { number = value; }
   }
}
 
///  ChainApp Application
public class ChainApp
{
   public static void Main( string [] args )  {
     // Setup Chain of Responsibility
     Director Larry = new Director( "Larry" );
     VicePresident Sam = new VicePresident( "Sam" );
     President Tammy = new President( "Tammy" );
     Larry.SetSuccessor( Sam );
     Sam.SetSuccessor( Tammy );
 
     // Generate and process different requests
     PurchaseRequest rs = new PurchaseRequest( 2034, 350.00, "Supplies" );
     Larry.ProcessRequest( rs );
     PurchaseRequest rx = new PurchaseRequest( 2035, 32590.10, "Project X" );
     Larry.ProcessRequest( rx );
     PurchaseRequest ry = new PurchaseRequest( 2036, 122100.00, "Project Y" );
     Larry.ProcessRequest( ry );
   }
}

六、 责任链模式的实现

责任链模式并不创建责任链。责任链的创建必须由系统的其它部分创建出来。

责任链模式降低了请求的发送端和接收端之间的耦合,使多个对象都有机会处理这个请求。一个链可以是一条线,一个树,也可以是一个环。如下图所示,责任链是一个树结构的一部分。

SOLID 软件设计原则_第19张图片


你可能感兴趣的:(SOLID 软件设计原则)