Template Method模板:
动机:在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变需求,或者由于故有的原因(比如框架与应用之间)而无法和任务的整体结构同时实现。
Template Method意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,Template
Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
Template Method要点:
1.Template Method模式一种非常基础性的设计模式,在面向对象系统中有着大量的应用,它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
2.除了可以用灵活应对子步骤的变化外,“不要调用我 ,让我调用你”的反向控制结构是Template Method的典型应用。
3.在具体实现方面,被Template Method调用的虚方法可具有实现,也可以没有任何实现(抽象方法,纯虚方法),但一般推荐将它们设置为protected方法。
Command 命令:
耦合是软件不能抵御变化灾难的根本性原因。不仅实体对象与实体对象之间存在耦合关系(创建型模式解决),实体对象与行为操作之间也存在耦合关系。
Command意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或者记录请求日志以及支持可撤销的操作。
一般做法:
|
class App
{
public void Show()
{
Document doc=new Document();
doc.ShowText();
Graphcis graph=new Graphics();
graph.ShowGraphics();
}
}
class Document
{
public void ShowText(){...}
}
class Graphics
{
public void ShowGraphics(){....}
}
|
而现在:
|
public interface Command
{
public void Show();
public void Undo();
public void Redo();
}
class Document:Command
{
public virtual void Show(){....}
}
class Graphics:Command
{
public virtual void Show(){....}
}
class App
{
Stack<Command> stack;
public void Show()
{
foreach(Command c in stack){c.show();}
}
//以下为Redo Undo操作,这样的操作在这个模式中表现充分。
public void Undo()
{
Command command=stack.pop();command.Undo();Undolist.Add(command);
}
public void Redo()
{
Command command=undolist.pop();command.redo();
}
}
|
事实上,标准的Command模式应该如下:
前提:Document类和Graphcis类已经有了。
现在,
|
//实现Command模式
public interface Command
{
public void Show();
public void Undo();
public void Redo();
}
//具体化的命令对象--从抽象意义来讲,DocumentCommand表示一个行为。
class DocumentCommand:Command
{show中调用doc的ShowText。}
class GraphicsCommand:Command
{show中调用graphics的ShowGraphics}
|
class App与上面的类似。
Interpreter解释器:
例子:五亿三百七十五万六千四百五十二--->转换为503756452.
在软件构建过程中,如果某一特定领域的问题比较复杂,类似的模式不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化。
在这种情况下,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。
Interpreter意图:给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。
Interpreter要点:
1.Interpreter模式的应用场合是Interpreter模式应用中难点,只有满足“业务规则频繁变化,且类似的模式不断重复出现,并且容易抽象为语法规则的问题”才适合使用Interpreter模式。
2.使用Interpreter模式来表示文法规则,从而可以用面向对象技巧来方便地“扩展”文法。
3.Interpreter模式比较适合简单的文法表示,对于复杂的文法表示,Interpreter模式会产生比较大的层次结构,需要求助于语法分析生成器这样的标准工具。
Mediator中介者:
在软件构建过程中,经常会出现多一个对象互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。
在这种情况下,我们可使用一个“中介对象”来管理对象间的管理关系,避免相互交互的对象之间的耦合引用关系,从而更好地抵御变化。
Mediator意图:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式的相互应用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
用中介者来通知相关的对象。
Mediator模式要点:
1.将多个对象之间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相管理”为“多个对象和一个中介者管理”,简化系统的维护,抵御了可能的变化。
2.随着控制逻辑的变化,Mediator具体对象的实现可能相当复杂,这时可以对Mediator对象进行分解处理。
3.Facade模式是解耦系统外到系统内(单向)的对象关联关系,Mediator模式是解耦系统内各个对象之间(双向)的关联关系。
Iterator迭代:
很多语言(C++,C#,Java)中,其实已经实现了。
在软件构建过程中,集合对象内部结构常常变化各异,但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素,同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。
使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方法。
Iterator意图:
提供一种方法顺序访问一个聚合对象中的各个元素,却又不暴露该对象的内部表示。
Iterator要点:
1.迭代抽象:访问一个聚合对象的内容而五需暴露他的内部表示。
2.迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同集合结构上进行操作。
3.迭代器的健壮性考虑:遍历的同时更改迭代器所在的结合结构,会导致问题。
Observer观察者:
在软件构建过程中,我们需要为某些对象建立一种“同时依赖关系”---一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。
Observer意图:
定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
常规做法:
|
public class Bank
{
A a; //为通知A。B 声明一个字段。这就是强依赖关系。
B b;
public void Withdraw() //取款
{a.qukuanA();b.qukuanB();}
}
public class A
{
public void qukuanA(){取款}
}
public class B
{
public void qukuanB(){取款}
}
|
现在,应该为A,B抽象出一个抽象类或者接口。
|
public interface
IObserver
{
void Update(); //然后A,B继承这个接口并进行重写
}
//然后在App中用一个集合类,App现在就只依赖上面的接口了。在App中可以进行foreach处理,当然可以增或删被通知的对象。
|
Observer要点:
1.使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。
2.目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知,目标对象对此一无所知。
3.C#的event中,委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象。委托是比抽象Observer接口更为松耦合的设计。
Chain of Responsibility职责链(用的不多):
一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显式指定,将必不可少地带来请求发送者与接受者的紧耦合。
如何使请求的发送者不需要指定具体的接受者?让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。
Chain of Responsibility意图:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
Chain of Responsibility要点:
1.Chain of Responsibility模式的应用场合在于“一个请求可能有多个接受者,但是最后真正的接受者只有一个”,只有这时候请求发送者与接受者的耦合才有可能出现“变化脆弱”的症状,职责链的目的就是将二者解耦,从而更好地应对变化
2.应用了Chain of Responsibility模式后,对象的职责分派将更具灵活性,我们可以在运行时动态添加、修改请求的处理职责
3.如果请求传递到职责链的末尾仍得不到处理,应该有一个合理的缺省机制,这也是每一个接受对象的责任,而不是发出请求的对象的责任。
Memento备忘录:
对象状态的变化无端,如何回溯、回复对象在某个点的装?
在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态,如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。
Memento意图:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
如何实现对象状态的良好保存与恢复?
但同时又不会因此而破坏对象本身的封装性。
例子:
常规做法:
|
class Rectangle
{
int x,y; //坐标
int height,width;
//可能有的操作
Moveto(){....};
ChangeHeight(){...};
//用于保存状态的操作
SetValue(rec);
}
|
客户类:
|
class App
{
Rectangle rec= new Rectangle(0,0,10,20);
Rectangle rSaved= new Rectangle(0,0,10,20);//用于保存状态
process(){首先应该是rec克隆给rSaved,然后进行Moveto,changge等操作}
//保存操作
Save(){rSaved.SetValue();}
}
|
但上面有一个问题:rSaved本来是用来保持状态的,它不应该具有Moveto,change等操作的权限。
我们就有一个想法:把rSaved做成一个只有保存状态的操作,别的一概没有。实现如下:
新增加一个类:
|
class RectangleMemento
{
int x,y,height,width;
SetState(int x,int y,int height,int
width)
{this.x=x;.......}
}
|
然后在Rectangle类中增加一个方法:
|
class Rectangle
{
int x,y; //坐标
int height,width;
//可能有的操作
Moveto(){....};
ChangeHeight(){...};
//用于保存状态的操作
SetValue(rec);
RectangleMemento CreateMemento()
//增加的方法
{RectangleMemento rm=new RectangleMemento();
rm.SetState(this.x,this,y...);
return rm;}
void SetMemento() //增加的恢复方法
{this=x=rm.x;....}
}
|
Memento模式要点:
1.Memento存储原发器对象的内部状态,在需要时恢复原发器状态,Memento模式适用于“由原发器管理,却又必须存储在原发器之外的信息”
2.在实现Memento模式中,要防止原发器以外的对象访问备忘录对象,备忘录对象有两个接口,一个为原发器使用的宽接口,一个为其他对象使用的窄接口
3.在实现Memento模式时,要考虑拷贝对象状态的效率问题,如果对象开销比较大,可以采用某种增量式改变来改进Memento模式。
State状态:
对象状态影响对象行为。
某些对象的状态如果改变,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。
如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?
State意图:
允许一个对象在其内部状态改变时改变它的行为,从而使对象看起来似乎修改了其行为。
State要点:
1.State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换相应的对象,但同时维持State的接口,这样实现了具体的操作与状态转化之间的解耦。
2.为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致情况,因为转换是原子性的,即要么彻底转换过来,要么不转换。
3.如果State对象没有实例变量,那么各个上下文可以共享同一个State对象,从而节省对象开销。
Strategy策略:
对象可能经常需要使用多种不同的算法,但是如果变化频繁,会将类型变得脆弱。
某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂,而且有时候支持不使用的算法也是一个性能负担。
如何在运行时根据需要透明地更改对象的算法?
将算法与对象本身解耦,从而避免上述问题?
Strategy意图:
定义一系列算法,把它们一个个封装起来,并且使他们可相互替换,该模式使得算法可独立于使用它的客户而变化。
Strategy要点:
1.Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换,所谓封装算法,支持算法的变化。
2.Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合,含有许多条件判断语句的代码通常都需要Strategy模式。
3.与State类似,如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。
Visitor访问者:
例子:一个抽象Shape类型,有虚方法Draw,Shape有子类Rectangle,Circle,Line,如果要给Shape类型加一个操作Move,那将在其三个子类中分别加如这个操作。所以抽象出一个Visitor类,让它复杂增加操作。
类层次结构中可能经常引入新的操作,从而将类型变得脆弱。
由于需求的改变,某些类结构中常常需要增加新的方法,如果在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。
如果在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加操作,从而避免上述问题?
Visitor意图:
表示一个作用于某种对象结构中的各元素的操作,它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。
Visitor要点:
1.Visitor模式通过所谓双重分发(double dispatch)来实现在不更改Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作。
2.所属双重分发即Visitor模式中间包括了两个多态分发(注意其中的多态机制):第一个为accept方法的多态辨析;第二个为visit方法的多态辨析。
3.Visitor模式的最大缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变,因此Visitor模式适用于“Element类层次结构稳定,而其中的操作却经常面临频繁改动”。
行为型设计模式总结:
Template Method模式封装算法结构,支持算法子步骤变化。
Strategy模式注重封装算法,支持算法的变化
State模式注重封装与状态相关的行为,支持状态的变化。
Memento模式注重封装对象状态变化,支持状态保存、回复
Mediator模式注重封装对象间的交互,支持对象交互的变化。
Chain of Responsibility模式注重封装对象责任,支持责任的变化。
Command模式注重将请求封装为对象,支持请求的变化。
Iterator模式注重封装集合对象内部结构,支持集合的变化,(同Composite,大多数语言已实现)
Interpreter模式注重封装特定领域变化,支持领域问题的频繁变化。
Observer模式注重封装对象通知,支持通信对象的变化。
Visitor模式注重封装对象操作变化,支持在运行时为类层次结构动态添加新的操作。
最后:
1.设计模式建立在对系统变化点的基础上进行,哪里有变化点,哪里有应用设计模式。
2.设计模式应该以演化的方式来获得,系统的变化点往往是经过不断演化才能准确定位。
3.不能为了模式而模式,设计模式是一种软件设计的软力量,而非规范标准,不应夸大设计模式的作用。
|