设计模式心得

发布时间:2010-8-25 17:36
分类名称:Design Pattern


原则

单一职责原则 (SRP, Single Responsibility Principle)

就一个类而言,应该仅有一个引起它变化的原因。

 

开放-封闭原则 (OCP, Open-Closed Principle)

一个软件实体应当对扩展开放(Open for extension),对修改关闭(Close for modification)

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

 

依赖倒置原则 (DIP, Dependence Inversion Principle)

里氏替换原则 (LSP, Liskov Substitution Principle)

子类型(subtype)必须能够替换它们的基类型。

抽象不应该依赖细节,细节应该依赖抽象。

要依赖于抽象,不要依赖于具体。

要针对接口编程,不要针对实现编程。

依赖倒置原则使细节和策略都依赖于抽象,抽象的稳定性决定了系统的稳定性。

 

说明:

SRP 增强了类的内聚性。

OCP 增强了系统的可扩展性以及稳定性(在稳定的系统上进行扩展,要比修改系统稳定的多)。

DIP 降低了系统各个部件之间的耦合性,使其能够独立变化,此原则也使得程序变得可扩展。

有了LSP,才能够使得在不需要更改“父类”模块的前提下,来继承父类以及对其功能扩展。

所以LSPDIP目的都是一样的。

 

对封装的认识

封装是任何数据的隐藏。

数据的隐藏

实现的隐藏

类隐藏(抽象类或接口后)

设计隐藏

实例化隐藏

设计模式分为3大类,分类建立在模式的概念性的动机上。

行为型模式用于封装行为的变化。

结构性模式用于将已有的代码集成到新的面向对象中去。

创建型模式用于管理对象创建。

GoF 23种模式

Factory

没有Factory Pattern的时候:

[1].     诸如new Instance此类的代码“遍及各地”。而且在以后要扩展新的类实例的时候,必定好回头来修改原有代码。(虽然这种改动一般不会带来不良后果,但如果系统很庞大,庞大了上千个类,这种修改将是非常浪费时间,而且以后这种工作将会不停的重复)。显然增加了维护成本。

[2].     第一种情况是在有源代码的情况下,如果目前面临的只是个导出的类头文件和对应的二进制动态库,修改程序是不可能的,将无法实现新增功能的“梦想”。也就是我以前编写的代码无法预知以后将会有什么样的类实例来运作,我顶多只能知道当时用的某个类实例。

 

使用Factory模式,可以解决上面的问题。当需要增加新的类来实现新功能的时候,只需要增加新的类,增加对应Factory子类,用Factory子类中的接口new出新增加的类的实例,以前的代码无需任何变动,程序继续正常运作。

 

作用:

1.  将实例化操作作为单独一种“职责”封装,将类的实现和类的实例化解耦。将实例化操作集中处理,将“分散各地”的代码统一管理。

2.  将实例化操作延迟到子类进行(也就是可以在以后进行操作,可扩展)。

 

缺点:

增加了代码量,一个新增的类,得对应一个Factory子类来进行实例化操作。当然,你可以将Factory中的接口内实现一个switch,根据不同的传入参数来实例化不同的类实例,这样就得修改以前的代码,但这种修改将是集中的(虽然违反了封闭开放原则,但原则不一定得遵守,根据不同的环境来灵活运用)。


Factory有几种版本:

抽象工厂

抽象工厂是建立在“抽象”的基础上,有“批量”生产的含义。创建一组相关或者相互依赖的对象。

2010年08月25日 - Dsliu - Dspace

简单工厂

简单工厂就是使用一个工厂类,此类对应某个抽象类所有的子类实例,工厂类内部使用switch语句来实例不同的子类。

2010年08月25日 - Dsliu - Dspace

工厂方法

工厂方法则是每一个要被实例化的子类,都对应一个相应的工厂类。

2010年08月25日 - Dsliu - Dspace

Prototype

定义:

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.

Prototype 模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

Builder

没有Builder的时候:

假如我要构造一辆汽车,汽车的部件很多,多到上千,那么我先干什么部件,再做什么部件,这属于建造步骤。我每一个部件如何制作,这是细节。如果按照过程设计,我肯定会在步骤中融合了各种细节。当我再次造一辆汽车的时候,步骤是稳定的,但是部件制作的细节不同。这样我就要在大量代码中进行大量的代码的查找和替换工作。而且随着汽车种类的增多,维护成本将线性增长。

Builder模式是将一个复杂对象的构建和表示分离,使得同样的构建可以创建不同的表示。这里的构建就是车的构造流程(固定的),表示就是车部件的制造细节(这种表示并不是针对车的,但是部件的不同表示最终还是反应了车的表示,即细节的不同影响了全局的变化)。

作用:

是为了将构建复杂对象的过程和它的部件解耦. 注意: 是解耦过程和部件.

 

说明:

虽然过程和部件解耦了,但是俩着仍然有耦合,一旦部件增加,必然导致过程发生变化。但维护成本很低,远远小于不使用此模式的成本。

// 制造部件

class Builder {

public:

// 创建部件A 比如创建汽车车轮

virtual void buildPartA() = 0;

// 创建部件B 比如创建汽车方向盘

virtual void buildPartB() = 0;

// 创建部件C 比如创建汽车发动机

virtual void buildPartC() = 0;

// 返回最后组装成品结果 (返回最后装配好的汽车)

// 成品的组装过程不在这里进行,而是转移到下面的Director 类中

进行.

// 从而实现了解耦过程部件

virtual Product getResult() = 0;

};

 

// 制造过程

class Director {

protected:

Builder builder;

 

Director( Builder builder )

{

this.builder = builder;

}

 

// 将部件partA partB partC 最后组成复杂对象

// 这里是将车轮 方向盘和发动机组装成汽车的过程

void construct()

{

builder.buildPartA();

builder.buildPartB();

builder.buildPartC();

}

}

 

class ConcreteBuilder : public Builder {

protected:

Part partA, partB, partC;

virtual void buildPartA() {

//这里是具体如何构建partA 的代码

};

 

virtual void buildPartB() {

//这里是具体如何构建partB 的代码

};

 

virtual void buildPartC() {

//这里是具体如何构建partB 的代码

};

virtual Product getResult() {

//返回最后组装成品结果

};

};

 

// 使用

Builder * pBuilder = new ConcreteBuilder();

Director *pDirector = new Director(pBuilder);

pDirector->construct();

Product product = pBuilder->GetResult();

 

Singleton

创建一个唯一的全局变量(当然在C++中,可以直接可以定义全局变量)。此实现有几个关键点,见代码:

class Singleton
{
public:
         static Singleton* Instance()
         {
                   if (_instance == NULL)
                   {
                            // 如果是多线程,不锁定的话有可能产生多个实例。
                            // 双重检测锁定(Double-Checked Locking)。
                            EnterCriticalSection(&cs);
                            if (_instance == NULL)
                            {
                                     _instance = new Singleton();
                            }
                            LeaveCriticalSection(&cs);
                   }
                   return _instance;
         }

protected:
         Singleton();       // 将构造函数设置为protected,则此类不能被实例化(我们也这种目的)。

private:
         static Singleton* _instance;
};

Singleton模式经常和FactoryAbstractFactory)模式在一起使用,因为系统中工厂对象一般来说只要一个

 

Facade

为子系统中的一组接口提供一个一致的界面.

Facade 实际上是个理顺系统间关系,降低系统间耦合度的一个常用的办法

 

设计模式心得 - Dsliu - Dspace

Proxy

为其他对象提供一种代理以控制对这个对象的访问.

为什么要使用Proxy

1.         授权机制 不同级别的用户对同一对象拥有不同的访问权利,Jive 论坛系统中,就使用Proxy 进行授权机制控制,访问论坛有两种人:注册用户和游客(未注册用户),Jive 中就通过类似ForumProxy 这样的代理来控制这两种用户对论坛的访问权限.

2.         某个客户端不能直接操作到某个对象,但又必须和那个对象有所互动.

举例两个具体情况:

[1].     如果那个对象是一个是很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,打开文档必须很迅速,不能等待大图片处理完成,这时需要做个图片Proxy 来代替真正的图片.

[2].     如果那个对象在Internet 的某个远端服务器上,直接操作这个对象因为网络速度原因可能比较慢,那我们可以先用Proxy 来代替那个对象.总之原则是,对于开销很大的对象,只有在使用它时才创建,这个原则可以为我们节省很多宝贵的Java 内存. 所以,有些人认为Java 耗费资源内存,我以为这和程序编制思路也有一定的关系.

 

Jive 论坛系统为例,访问论坛系统的用户有多种类型:注册普通用户 论坛管理者 系统管

理者 游客,注册普通用户才能发言;论坛管理者可以管理他被授权的论坛;系统管理者可以

管理所有事务等,这些权限划分和管理是使用Proxy 完成的.

 

Adapter

常见环境:

实际上在软件系统设计和开发中,这种问题也会经常遇到:我们为了完成某项工作购买了一个第三方的库来加快开发。这就带来了一个问题:我们在应用程序中已经设计好了接口,与这个第三方提供的接口不一致,为了使得这些接口不兼容的类(不能在一起工作)可以在一起工作了,Adapter模式提供了将一个类(第三方库)的接口转化为客户(购买使用者)希望的接口。

 

将两个不兼容的类纠合在一起使用,属于结构型模式,需要有Adaptee(被适配者)

Adaptor(适配器)两个身份.

 

使用方式

1.       组合(composition

2.       继承(inheritance

 

Composite

将对象以树形结构组织起来,以达成“部分-整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性.

Composite 比较容易理解,想到Composite 就应该想到树形结构图。组合体内这些对象都有共同接口,当组合体一个对象的方法被调用执行时,Composite 将遍历(Iterator)整个树形结构,寻找同样包含这个方法的对象并实现调用执行。可以用牵一动百来形容。所以Composite 模式使用到Iterator 模式,和Chain of Responsibility 模式类似。

 

Composite 好处:

1.         使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关系自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。

2.         更容易在组合体内加入对象部件. 客户端不必因为加入了新的对象部件而更改代码。

 

Decorator

动态给一个对象添加一些额外的职责,就象在墙上刷油漆.使用Decorator 模式相比用生成

子类方式达到功能的扩充显得更为灵活.(使用组合而不是继承)

 

为什么使用Decorator?

我们通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了,是静态的.

使用Decorator 的理由是:这些功能需要由用户动态决定加入的方式和时机.Decorator 提供了"即插即用"的方法,在运行期间决定何时增加何种功能.

 

Bridge

没有Bridge的时候:

n  代码有冗余(每次都要重新实现)

n  紧耦合(抽象和实现)

n  类的成倍递增(类爆炸)

 

作用:

将抽象与其实现解耦,使他们都可以独立地变化。

 

缺点:

如果抽象有一个新的具体变化,就要修改其对应的实现。

 

Flyweight

避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类).

面向对象语言的原则就是一切都是对象,但是如果真正使用起来,有时对象数可能显得很庞大,比如,字处理软件,如果以每个文字都作为一个对象,几千个字,对象数就是几千,无疑耗费内存,那么我们还是要"求同存异",找出这些对象群的共同点,设计一个元类,封装可以被共享的类,另外,还有一些特性是取决于应用(context),是不可共享的,这也Flyweight 中两个重要概念内部状态intrinsic 和外部状态extrinsic 之分.

说白点,就是先捏一个的原始模型,然后随着不同场合和环境,再产生各具特征的具体模型,很显然,在这里需要产生不同的新对象,所以Flyweight 模式中常出现Factory 模式.Flyweight 的内部状态是用来共享的,Flyweight factory 负责维护一个Flyweightpool(模式池)来存放内部状态的对象.

Flyweight 模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度.应用场合很多:比如你要从一个数据库中读取一系列字符串,这些字符串中有许多是重复的,那么我们可以将这些字符串储存在Flyweight (pool).

 

Template

定义一个操作中算法的骨架,将一些步骤的执行延迟到其子类中.

例如,微软的MFC,里面就是用了大量的Template模式,在MFC框架中大量的虚函数,将许多步骤可以延迟到以后进行。

 

Memento

memento 是一个保存另外一个对象内部状态拷贝的对象.这样以后就可以将该对象恢复到原先保存的状态.

Memento 模式的缺点是耗费大,如果内部状态很多,再保存一份,无意要浪费大量内存.

 

Obeserve

又称发布-订阅(Publish/Subscribe)模式。定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使它们能够自动更新自己。
设计模式心得 - Dsliu - Dspace\

 

Chain of responsibility

Chain of Responsibility(CoR) 是用一系列类(classes)试图处理一个请求request,这些类之间是一个松散的耦合,唯一共同点是在他们之间传递request. 也就是说,来了一个请求,A 类先处理,如果没有处理,就传递到B 类处理,如果没有处理,就传递到C 类处理,就这样象一个链条(chain)一样传递下去。