设计模式

Gof 模式

    以下的设计模式则是我个人从 Gof 学习中的个人体会与实作,并增加几个导入或衍生的简单模式。

  • Creational 模式

物件的产生需要消耗系统资源,所以如何有效率的产生、管理与操作物件,一直都是值得讨论的课题, Creational 模式即与物件的建立相关,在这个分类下的模式给出了一些指导原则及设计的方向。

  •  
    • Simple Factory 模式
    • Abstract Factory 模式
    • Builder模式
    • 工厂方法模式
    • Prototype模式
    • 单件模式
    • Registry of Singleton 模式
  • Structural 模式

如何设计物件之间的静态结构,如何完成物件之间的继承、实现与依赖关系,这关乎着系统设计出来是否健壮(robust):像是易懂、易维护、易修改、耦合度低等等议题。Structural 模式正如其名,其分类下的模式给出了在不同场合下所适用的各种物件关系结构。

  •  
    • 适配器模式
    • Bridge模式
    • Composite模式
    • 装饰者模式 Decorator
    • 外观模式 Facade
    • FlyWeight模式
    • Proxy模式

 

  • Behavioral 模式

物件之间的合作行为构成了程式最终的行为,物件之间若有设计良好的行为互动,不仅使得程式执行时更有效率,更可以让物件的职责更为清晰、整个程式的动态结构(像是物件调度)更有弹性。

  •  
    • 命令模式
    • Interpreter 模式
    • Iterator模式
    • Mediator 模式
    • Memento模式
    • 观察者模式
    • State模式
    • 策略模式
    • TemplateMethod模式
    • Visitor模式

 

先插一篇英文

Head.First书中列举的设计原则:

1、封装变化。找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。

2、针对接口编程,而不是针对实现编程

举例说明:假设有一个抽象类Animal,有两个具体的实现(Dog与Cat)继承Animal。

image

“针对实现编程”的做法:Dog d = new Dog();d.bark();

“针对接口编程”的做法:Animal animal = new Dog();animal.makeSound();

3、多用组合,少用继承

原因:a、继承会使类无限膨大,可能会使类变得臃肿。

        b、子类可能会继承父类中那些无用甚至有害的方法。

        c、组合比继承更灵活,可以实现在执行中动态改变对象的功能。

4、为了交互对象之间的松耦合设计而努力。

5、类应该对修改关闭,对扩展开放

6、要依赖抽象,不要依赖具体类

解释:不要让“高层组件”依赖“低层组件”,而且,不管“高层组件”还是“低层组件”,两者都应该依赖于抽象。
避免违反该原则的几个方针:
1)、变量不可以持有具体类的引用。
    如果使用new,就会持有具体类的引用,可以使用工厂来避开这种引用。
2)、不要让类派生自具体类。 
    如果派生自具体类,就会依赖具体类,可以派生自抽象或接口。
3)、不要覆盖基类中已实现的方法。
    如果覆盖基类中已实现的方法,那么基类就不是一个真正适合被继承的类。基类中已实现的方法应该被所有子类所共享。

7、最少知识原则。

不要和陌生人说话。

  解释:当你设计一个系统时,不管是任何对象,你都要注意与它交互的类有哪些,并注意它和这些类是如何交互的,尽量避免过多的类耦合在一起,带来维护成本的上升。
这个原则推荐的一些方针:
就任何对象而言,在该对象的方法内,我们只应该调用一定范围的方法
1)、该对象本身
2)、被当作方法的参数而传递进来的对象
3)、此方法所创建或实例化的任何对象
4)、对象的任何组件。

简单的说,就是如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用

 

策略模式

跟不同类型的MM约会,要用不同的策略Strategy,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到MM的芳心。

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

策略模式把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。

结构

image

int main()
{
    Strategy* pStrategy = new ConcreateStrategyA();
    Context*  pContext  = new Context(pStrategy);

    pContext->ContextInterface(); //! 调用pStrategy->Algorithminterface();

    delete pContext;

    return 0;
}

 

Strategy模式是对算法的封装.处理一个问题的时候可能有多种算法,这些算法的接口(输入参数,输出参数等)都是一致的,那么可以考虑采用Strategy模式对这些算法进行封装,在基类中定义一个函数接口就可以了.

 

鸭子模拟器的设计: 公司需要设计一套鸭子模拟器系统,

该系统的第一次需求为:鸭子能够戏水;鸭子能够呱呱叫。根据该需求系统设计如下:

image

这个设计主要用了父类鸭子和子类绿头鸭、红头鸭,这样设计的目的是为了达到代码的复用。

 

过了一段时间,公司希望该系统能够满足新的需求:有些鸭子会飞。

因此该系统需要进行修改,修改后的系统可能如下:

image

该系统在父类中加了“fly()”方法(在父类中加该方法是为了实现代码的复用)。这里就出现了两个问题:

(1)、所有的鸭子都会飞了。

(2)、所有鸭子的叫声都一样,都是“呱呱”叫。

注:这两个问题可以通过子类中方法覆盖来解除,但这样处理不是很好。鸭子的类别越多,这种处理的缺点就越明显。

 

其实这个系统的变化点是鸭子的叫声和鸭子的飞行能力,因此我们很容易想到把鸭子的叫声和鸭子的飞行能力做成接口,把这些变化的地方封装起来,

这样上面的两个问题都可以解决,所以系统可能被修改为下面的样子:

image

MallardDuck和RedheadDuck既会飞又会叫,RubberDuck只会叫不会飞,DecoyDuck不会飞也不会叫。应该说这个系统没有问题了,从表面看这种设计完全符合需求,而且完全符合面向对象的理念,但是当鸭子的类别很多时,你会发现这种设计缺乏代码的复用,这两个独立出来的接口似乎没有任何意义,根本无法减轻工作量,还不如原来的设计呢。所以你可能会想到问题的关键是接口中的方法没有实现,那我们该怎么办呢?

我们的做法是把接口做成类,运用组合的方法来实现需求。考虑到“针对接口编程,而不是针对实现编程”的设计原则,我们的系统可能就会设计成如下的结构,这个结构就是应用了“策略”模式。

image

Duck中组合IquackBehavior和IFlyBehavior(针对这这两个接口实现了类(不同的策略)),由子类去new不同的实现类(策略),而拥有不同的行为。

 

观察者模式

想知道咱们公司最新MM情报吗?加入公司的MM情报邮件组就行了,tom负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦。

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。

观察者模式定义了一种一队多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自
动更新自己。

结构

image

Observer *p1 = new ConcreateObserver;
Observer *p2 = new ConcreateObserver;

Subject* p = new ConcreateSubject;
p->Attach(p1);
p->Attach(p2);
p->SetState(4);
p->Notify();

p->Detach(p1);
p->SetState(10);
p->Notify();

Observer模式定义的是一种一对多的关系,这里的一就是图中的Subject类,而多则是Obesrver类,当Subject类的状态发生变化的时候通知与之对应的Obesrver类们也去相应的更新状态,同时支持动态的添加和删除Observer对象的功能.Obesrver模式的实现要点是,第一一般subject类都是采用链表等容器来存放Observer对象,第二抽取出Observer对象的一些公共的属性形成Observer基类,而Subject中保存的则是Observer类对象的指针,这样就使Subject和具体的Observer实现了解耦,也就是Subject不需要去关心到底是哪个Observer对放进了自己的容器中.生活中有很多例子可以看做是Observer模式的运用,比方说,一个班有一个班主任(Subject),他管理手下的一帮学生(Observer),当班里有一些事情发生需要通知学生的时候,班主任要做的不是逐个学生挨个的通知而是把学生召集起来一起通知,实现了班主任和具体学生的关系解耦.

 

下面以模拟气象站系统来加以说明。

需求分析

1、气象站能够追踪目前的天气状况,包括温度、湿度、气压、

2、气象站能够提供三种布告板,分别显示目前天气状况、气象统计和简单的预报。

3、布告板上的数据必须实时更新。

4、气象站必须提供一组API,供其他开发人员开发其他的布告板。

设计部分

基于以上需求,该系统可以设计成3部分:

气象站(获取实际气象数据的物理装置)

WeatherData对象(追踪来自气象站的数据,并更新布告板)

布告板(显示目前的天气状况给用户看)。

相应的代码实现部分:

1public void MeasurementsChanged()
2        {
3            temperature = this.GetTemperature();   // 获得温度
4            humidity = this.GetHumidity();         // 获得湿度
5            pressure = this.GetPressure();         // 获得气压 
6
7            MyCurrentConditionsDisplay.Update(temperature, humidity, pressure);     // 更新目前天气状态板
8            MyStatisticsDisplay.Update(temperature, humidity, pressure);            // 更新气象统计板
9            MyForcastDisplay.Update(temperature, humidity, pressure);               // 更新天气预报板            
0        } 

这个类图设计的缺点:

1)、该设计是针对具体实现编程,而非针对接口。

2)、对于每个新的布告板,我们都得修改代码。

3)、我们无法在运动时动态得增加或删除布告板。

4)、我们尚未封装改变的部分。

 

那么如何改正这些缺点呢?

首先我们必须明白这些缺点的根源在哪里。很明显,我们在类图设计时依赖关系错了,应该依赖倒置。CurrrentConditionsDisplay类、StatisticsDisplay类和ForcastDisplay类应该依赖WeatherData类,而不是相反,这样就可以起到解耦的目的。

其次,CurrrentConditionsDisplay类、StatisticsDisplay类和ForcastDisplay类都有一个Update()方法,因此应该提炼一个接口,这样可以实现“针对接口编程”,使代码更加灵活,也方便其他开发人员开发其他的布告板。

进一步思考:

1)、改正这些缺点后,我们的类图已经与观察者模式的结构有点类似了。

2)、我们的气象站系统的最大问题其实就是一对多的依赖引起的,而观察者模式正是解除一对多关系的不二法门,因此我们有必要采用观察者模式。

采用了观察者模式后设计的类图应该是这样:

image

WeatherDatea实现ISubject接口,CurrentConditionsDisplay、ForcastDisplay、StatisticsDisplay实现IObserver接口,ISubject调用IObserver的Update,CurrentConditionsDisplay、ForcastDisplay、StatisticsDisplay调用ISubject的注册观察者RegisterObserver。

相应的代码实现部分:

1public class WeatherData : ISubject
2    {
3        private float temperature;
4        private float humidity;
5        private float pressure;
6        private List<IObserver> myList = new List<IObserver>();
7
8        public void SetWeatherData(float paramTemp, float paramHumidity, float paramPressure)
9        {
0            this.temperature = paramTemp;
1            this.humidity = paramHumidity;
2            this.pressure = paramPressure;
3            MeasurementsChanged();
4        }
5
6        public void MeasurementsChanged()
7        {
8            this.NotifyObservers();
9        }
0       
1        public void RegisterObserver(IObserver paramIObserver)
2        {
3            myList.Add(paramIObserver);
4        }
5
6        public void RemoveObserver(IObserver paramIObserver)
7        {
8            myList.Remove(paramIObserver);
9        }
0
1        public void NotifyObservers()
2        {
3            foreach (IObserver observer in myList)
4            {
5                observer.Update(temperature, humidity, pressure);
6            }
7        }
8    }

 

装饰者模式

Decorator- MM过生日需要送礼物,如果让她自己挑,这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片,在背面写上“最好的的礼物,就是爱你的Fita”,再到街上礼品店买了个像框(卖礼品的MM也很漂亮哦),再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来……,我们都是Decorator,最终都在修饰我这个人呀。

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比生成子类更为灵活。

装饰模式Decorator:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。

山楂、橘子、苹果 3种水果例子说明:
以前是这么卖的:预先把糖葫芦都插好,比如山楂的1个,橘子的2个,苹果的5个,或者山楂的5个,橘子的2个,苹果的1个,然后拿去卖。如果买的人有正好他中意的,他就买。可是还有一些情况是,他要买山楂的2个,橘子的2个,苹果的5个而我没有插这种,那么就没办法,我只有答应下次给他插。但是买的人口味是变化的,我渐渐发现我需要覆盖的组合越来越多,而能卖出去却很少。痛苦!
现在我学会了,把山楂、橘子、苹果 3种水果直接拿到市场上去,买的人需要什么我就当时给他组合。当然这些山楂、橘子、苹果 3种水果并不是原来的样子了,它们每一块我都通了一个孔,这样查起来就方便多了。这个孔就是LZ第一个类图的component。还有每个糖葫芦最上面那颗是略微有所不同的,它只有一个孔,那就是concretcomponent了。

结构

image


1)Component:定义一个对象接口,可以为这个接口动态的添加职责.
2)Decorator:维持一个指向Component的指针,并且有一个和Component一致的接口函数.
3)Component::Operation:这个接口函数由Component声明,因此Component的派生类都需要实现,可以在这个接口函数的基础上给它动态添加职责.
4)Decorator的派生类可以为ConcreateComponent类的对象动态的添加职责

int main()
{
    // 初始化一个Component对象
    Component* pComponent = new ConcreateComponent();
    // 采用这个Component对象去初始化一个Decorator对象,
    // 这样就可以为这个Component对象动态添加职责
    Decorator* pDecorator = new ConcreateDecorator(pComponent);

    pDecorator->Operation();

    delete pDecorator;

    system("pause");

    return 0;
}

 

下面我们以星巴兹(Starbuzz)的订单系统为例加以说明。

需求分析:

1)、星巴兹的饮料(Beverage)种类繁多,主要有HouseBlend、DarkRoast、Decaf、Espresso。

2)、星巴兹的调料很多,主要有Steamed Milk、Soy、Mocha、Whip。

3)、星巴兹的饮料价格是根据饮料的基础价和所加入的调料的价格相加得到。

错误设计:

根据以上的简单分析,第一种类图设计出炉:

image

其中getDescription()用来描述饮料,cost()用来计算价格。

显而易见,这个类图设计的最大缺点就是类太多,系统难以维护。所以我们需要另外的解决方案,而且新方案必须避免“类爆炸”。

此时我们想到了实例变量和继承。先从Beverage基类下手,加上实例变量代表是否加上调料(Steamed Milk、Soy、Mocha、Whip等),Beverage基类的cost()计算调料的价钱,而各种具体的饮料(HouseBlend、DarkRoast、Decaf、Espresso等)的cost()将把基础饮料的价钱和调料的价钱相加得到饮料的价钱。

由此可以设计出第二种类图。

image

对这个类图设计的评价:如果需求不再变化,那么这个类图设计没有错;但是需求发生了变化,这个设计就会难以招架。

 

经过进一步的分析,我们发现部分需求被我们遗漏了。

新增加的需求:

1)、调料的价格可能发生变化。

2)、调料的种类可能发生变化。

3)、饮料的种类可能增加,不只HouseBlend、DarkRoast、Decaf、Espresso四种。

4)、顾客可能在一种饮料里加双份的同种饮料。

显然,第二种类图设计难以满足新的需求,而且对新增加的饮料而言,有可能存在不适合的调料。例如,茶子类将继承hasWhip()(加奶泡)等方法。

因此,我们应该对类图设计进行改进。此时我们想到了装饰者模式,那么如何应用装饰者模式呢?

以装饰者模式构造饮料订单:

1)、以DarkRoast对象开始。image

2)、顾客想要Mocha,所以建立一个Mocha对象,并用它将DarkRoast对象包装起来。image

3)、顾客也想要Whip,所以需要建立一个Whip装饰者,并用它将Mocha对象包起来。image

4)、现在,该是为顾客算钱的时候了。通过最外圈装饰者(Whip)的cost()就可以办得到。Whip的cost()会先委托它装饰的对象(Mocha)计算出价钱,然后再加上Whip的价钱。image

 

根据以上的分析,应用装饰者模式,我们可以打造一个全新的类图。

第三种类图设计(正确的类图):

image

部分代码为:

1public class DarkRoast : Beverage
2    {
3        public DarkRoast()
4        {
5            description = "Dark Roast";
6        }    
7        public override double Cost()
8        {
9            return 1.22;
0        }
1    }
2public class Mocha : CondimentDecorator
3    {
4        Beverage myBeverage;
5        public Mocha(Beverage paramBeverage)
6        {
7            this.myBeverage = paramBeverage;
8        }
9
0        public override string GetDescription()
1        {
2            return myBeverage.GetDescription() + ",Mocha";
3        } 
4    
5        public override double Cost()
6        {
7            return 0.5 + myBeverage.Cost();
8        }
9    }
0class StarbuzzCoffee
1    {
2        static void Main(string[] args)
3        {
4            Beverage myBeverage = new Espresso();
5            Console.WriteLine(myBeverage.GetDescription() + " $" + myBeverage.Cost());
6
7            Beverage myBeverage2 = new DarkRoast();
8            myBeverage2 = new Mocha(myBeverage2);
9            myBeverage2 = new Mocha(myBeverage2);
0            myBeverage2 = new Whip(myBeverage2);
1            Console.WriteLine(myBeverage2.GetDescription() + " $" + myBeverage2.Cost());
2
3            Console.ReadLine();
4        }
5    }

装饰模式的适用情况:

1)、需要扩展一个类的功能,或给一个类增加附加责任。

2)、需要动态地给一个对象增加功能,这些功能可以再动态地撤销。

3)、需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。

使用装饰模式主要有以下的优点:

1)、装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。

2)、通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

使用装饰模式主要有以下的缺点:

由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。

 

工厂方法模式

请MM去麦当劳吃汉堡,不同的MM有不同的口味,要每个都记住是一件烦人的事情,我一般采用Factory Method模式,带着MM到服务员那儿,说“要一个汉堡”,具体要什么样的汉堡呢,让MM直接跟服务员说就行了。

意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。

工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产
品类应当被实例化这种细节。

结构

image

 

实例:下面我们以Pizza店的例子来谈谈“工厂方法模式”的来龙去脉,我们学习的思路是“原始设计-->简单工厂-->工厂方法”。

情景分析:假如你有一个Pizza店,那么你的Pizza订单可能会写成如下代码(这段代码写在PizzaStore类里面):

1public Pizza OrderPizza()
2        {
3            Pizza pizza = new Pizza();
4            pizza.Prepare();
5            pizza.Bake();
6            pizza.Cut();
7            pizza.Box();
8            return pizza;
9        }
0

如果你的Pizza店的Pizza有很多类,那么你的Pizza订单会如何写呢,可能会写成如下:

1class PizzaStore
 2    {
 3        public Pizza OrderPizza(String paramPizzaType)
 4        {
 5            Pizza pizza = null;
 6            if (paramPizzaType.Equals("cheese"))
 7            {
 8                pizza = new CheesePizza();
 9            }
10            else if (paramPizzaType.Equals("greek"))
11            {
12                pizza = new GreekPizza();
13            }
14            else if (paramPizzaType.Equals("pepperon"))
15            {
16                pizza = new PepperoniPizza();
17            }
18            pizza.Prepare();
19            pizza.Bake();
20            pizza.Cut();
21            pizza.Box();
22            return pizza;
23        }
24
25        // 省略        
26    }
27

上面的代码有一个问题:当Pizza的种类发生改变(或增加或减少)时,这段代码必须重写。那么这段代码应该如何改进呢?其实我们只要封装变化点就行了,即代码中的“if,else”语句。封装的过程如下:
1)、创建SimplePizzaFactory类。
2)、在SimplePizzaFactory类中创建CreatPizza方法。
3)、把“if,else”语句写道CreatPizza方法中。
相应的代码如下:

1public class SimplePizzaFactory
2    {
3        public Pizza CreatPizza(String paramPizzaType)
4        {
5            Pizza pizza = null;
6            if (paramPizzaType.Equals("cheese"))
7            {
8                pizza = new CheesePizza();
9            }
0            else if (paramPizzaType.Equals("greek"))
1            {
2                pizza = new GreekPizza();
3            }
4            else if (paramPizzaType.Equals("pepperon"))
5            {
6                pizza = new PepperoniPizza();
7            }
8            return pizza;
9        }
0    }
1

这样做的好处:
1)、可以很好的达到代码重用。如果其他类想得到Pizza,都可以调用SimplePizzaFactory的CreatPizza()方法。
2)、维护方便。如果Pizza的创建需要改变,只需要改变CreatPizza()方法就行了,其他调用的地方不需要发生改变。
值得改进的地方:可以把CreatPizza()写成静态方法。
改成静态方法的优点:不需要通过创建对象来使用类里的方法。
缺点:不能通过继承来改变创建对象的行为。

小结:
上面的代码其实是简单工厂模式的应用(简单工厂不是一个真正的模式)。
简单工厂(Simple Factory)模式的意图:Simple Factory模式根据提供给它的数据,返回几个可能类中的一个类的实例。通常它返回的类都有一个公共的父类和公共的方法。
结构:

image

工厂类角色Creator (SimplePizzaFactory):工厂类在客户端的直接控制下(Create方法)创建产品对象。
抽象产品角色Product (Pizza):定义简单工厂创建的对象的父类或它们共同拥有的接口。可以是一个类、抽象类或接口。
具体产品角色ConcreteProduct (CheesePizza, GreekPizza,PepperoniPizza):定义工厂具体加工出的对象。
客户端Client(PizzaStore):负责调用SimplePizzaFactory的Create方法。
其实SimplePizzaFactory的CreatPizza()方法并没有符合面向对象的开闭原则,它没有对修改进行关闭,即当Pizza的种类改变时,这个方法必须重写,只不过改动的工作量减少了,只需要改动一处,其他调用的地方不需要变动而已。

 

新的需求:
1)、Pizza店有了加盟店,加盟店生产Pizza的流程应该一成不变。
2)、每个加盟店可能要提供不同风味的Pizza(比如纽约、芝加哥、加州)。


按照简单工厂的方法,我们可以这样做:创建NYPizzaFactory、ChicagoPizzaFactory、CaliforniaPizzaFactory类。相应的代码如下:
1NYPizzaFactory nyFactory = new NYPizzaFactory(); // 创建具有纽约风味的Pizza工厂
2            PizzaStore nyStore = new PizzaStore(nyFactory);  // 建立Pizza店,经营纽约风味的Pizza
3            nyStore.OrderPizza("cheese");                    // 订购具有纽约风味的CheesePizza

如果需求不发生改变,那么用简单工厂就已经足够了,但是一旦需求发生了变化,简单工厂就不能适应了。

变化后的需求:
1)、有些加盟店采用自创的流程,比如烘烤的做法有些差异(Bake方法改变)、不要切片(Cut方法不要)、采用其他公司的盒子(Box方法改变)等。
2)、Pizza的种类有可能发生改变(或增加或减少)。

我们的做法很简单,具体操作:
1)、把SimpleFactory的CreatPizza()方法写回到PizzaStore里面。
2)、抽象PizzaStore,用NYPizzaStore、ChicagoPizzaStore等继承PizzaStore。
3)、具体的PizzaStore和具体的Pizza进行一一对应。

 

相工厂方法模式的特点
1)、核心的工厂类不负责所有产品的创建,而是将具体创建工作交给子类去做,可以允许系统在不修改工厂角色的情况下引进新产品。
2)、当系统扩展需要添加新的产品对象时,Factory Method仅仅需要添加一个具体对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好的符合了“开放-封闭”原则。而Simple Factory 在添加新产品对象后不得不修改工厂方法,扩展性不好。

 

单件模式

背景:
有一些对象其实我们只需要一个,比方说:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表(registry)的对象、日志对象,充当打印机、显卡等设备的驱动程序的对象。事实上,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序的行为异常、资源使用过量,或者是不一致的结果。因此,我们设计这种类时必须确保只有一个实例,单件模式应运而生。

单件模式的意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

// 使用“双检锁”机制。具体代码如下:

 1  public class Singleton
 2    {
 3        static Singleton uniqueInstance = null;  // 用一个静态变量来记录Singleton类的唯一实例
 4        private static Object obj = new object();
 5
 6        private Singleton()   // 构造器必须私有
 7        {
 8        }
 9
10        public static Singleton Instance
11        {
12            get
13            {
14                if (null == uniqueInstance)         // 先判断uniqueInstance是否为空,如果为空,再进行加锁 
15                {
16                    lock (obj)                      // 使用加锁,避免两个线程同时进入
17                    {
18                        if (null == uniqueInstance) // 如果uniqueInstance为空,说明对象没有被创建
19                        {
20                            uniqueInstance = new Singleton();
21                        }                        
22                    }
23                }
24                return uniqueInstance;
25            }
26        }
27    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

命令模式

俺有一个MM家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送过来一个COMMAND。

背景:有时候我们需要对方法进行封装,通过对这些封装的方法进行调用,我们可以很好的处理一些事情。比如,记录日志,或者重复使用这些封装实现撤销功能。

意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。

命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独
立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。

结构

image

解析:
把命令封装在一个类中,就是这里的Command基类,同时把接收对象也封装在一个类中就是这里的Receiver类中,由调用这个命令的类也就是这里的Invoker类来调用.其实,如果弄清楚了Command模式的原理,就会发现其实它和注册回调函数的原理是很相似的,而在面向过程的设计中的回调函数其实和这里的Command类的作用是一致的.采用Command模式解耦了命令的发出者和命令的执行者.

具体的过程:

1)、客户创建命令。

2)、客户将命令的执行者封装进命令对象里。

3)、命令的请求者调用命令。

4)、命令的执行者执行命令。

Receiver* pReceiver = new Receiver();
  Command*  pCommand  = new ConcreateComand(pReceiver);
  Invoker*  pInvoker  = new Invoker(pCommand);

  pInvoker->Invoke();

  delete pInvoker;

补充
1)NoCommand模式
代码:
1public class NoCommand : Command
2    {
3        public void Execute()
4        {
5        }
6    }
用途:当你不想返回一个有意义的对象时,就可以用空对象。客户也可以将处理null的责任转移给空对象。举例来说,遥控器出厂时可以用NoCommand对象对他进行初始化。
2)、宏命令
代码:
1public class MacroCommand:Command
2    {
3        List<Command> commands;
4
5        public MacroCommand(List<Command> commands)
6        {
7            this.commands = commands;
8        }
9
10        public void Execute()
11        {
12            foreach (Command command in commands)
13            {
14                command.Execute();
15            }
16        }
17    }
用途:可以请求一次,执行多个命令。例如,按下一个按钮,实现打开电灯、电风扇、电视等功能。
应用:
1)、队列请求
想象有一个工作队列;你在某一端添加命令,然后另一端则是线程。线程进行下面的动作:从队列中取出一个命令,调用他的execute()方法,等待这个命令完成,然后将此命令丢弃,再取出下一个命令...此时,工作队列类和进行计算的对象之间完全解耦。当时线程可能进行财务运算,下一刻可能读取网络数据。
2)、日志请求
这需要我们将所有的动作(命令)记录在日志中,并能在系统死机后,对这些命令对象重新加载,成批的依次调用这些对象的execute()方法,恢复到之前的状态。比方说,对于电子表格的应用,我们可能想要实现的错误恢复方式是将电子表格的操作记录在日志中,而不是每次电子表格一有变化就记录整个电子表格。

 

适配器模式

在朋友聚会上碰到了一个美女Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友kent了,他作为我和Sarah之间的Adapter,让我和Sarah可以相互交谈了。

软件开发中经常遇到的问题:
假设已有一个软件系统,你希望它能和一个新的厂商类库搭配使用,但是这个新厂商所设计出来的接口,不同于旧厂商的接口。如果你不想改变现有的代码,也不能改变厂商的代码,那么你该如何解决这个问题。方法其实很简单,就是写一个类,将新厂商的的接口转成你所期望的接口。如图

image

这就是适配器模式。

意图:将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

结构:对象适配器 组合方式

image

 

类适配器 继承方式

image

 

适配器模式解析:

image

从上面的图中可以看出客户使用适配器的过程:

1)、客户通过目标接口调用适配器的方法对适配器发出请求。

2)、适配器使用被适配者接口把请求转化成被适配者的一个或多个调用接口。

3)、客户接收到调用的结果,但并未察觉这是适配器在起转换作用。

应该注意的地方:

1)、适配器需要做的工作与目标接口的大小成正比,接口越大,需要做的工作越多。

2)、根据实际需要我们可以让适配器实现好几个接口。一句话,要灵活使用模式。

类适配器的代码与对象适配器的代码类似,可以参考结构图仿照着写出来,这里不赘述。

下面说说对象适配器与类似配器的区别:

1)、对象适配器使用组合,更富有弹性;类适配器使用继承。

2)、对象适配器可以适配被适配者的子类;类适配器只能覆盖被适配者的行为。

适配器模式的适用性:

1)、你想使用一个已经存在的类,而它的接口不符合你的需求。

2)、你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。

3)、你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。

 

外观模式

我有一个专业的Nikon相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但MM可不懂这些,教了半天也不会。幸好相机有Facade设计模式,把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样MM也可以用这个相机给我拍张照片了。

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

门面模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。每一个子系统只有一个门面类,而且此门面类
只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个门面类。

结构

image

 

例子:

假设你有一套杀手级的家庭影院系统,内含DVD播放器、投影仪、自动屏幕、环绕立体声等。那么当你想看一部DVD时,需要做哪些事情呢(用最土的方式)?

1、将灯光调暗;

2、放下屏幕;

3、打开投影仪;

4、将投影仪的输入切换到DVD;

5、将投影仪设置成宽屏模式;

6、打开功放;

7、将功放的输入设置为DVD;

8、将功放设置为环绕立体声;

9、将功放音量调到中;

10、打开DVD播放器;

11、开始播放DVD。

我想如果每次看DVD时都要进行这样一番折腾,那我宁愿不看了。上面的操作暴露了哪些问题呢?

1、操作麻烦。具体体现在打开DVD、关闭DVD、改听CD或者广播。

2、升级麻烦(就是耦合度太大)。如果我要升级系统,那么我还必须重新学习另一套操作流程。

那么这些问题如何解决呢?

使用外观模式,新建一个门户类(或管理类),暴露出一些方法,在这些方法里对这些子系统进行调用(这里的子系统具体指灯光、屏幕、投影仪、功放、DVD等)。具体代码很简单,略。

外观模式的优点:

1、对接口进行了简化,方便客户使用。

2、可以实现客户与子系统解耦,易于维护。

缺点:

1、多了一个包装类,可能导致复杂度和开发时间的增加,并降低运行时的性能。

值得注意的地方:

1、外观模式对接口进行了简化,但这并不意味着对子系统进行彻底封装。如果有必要,这些子系统的接口还可以继续暴露给客户,这就是所谓的高级功能(或称为自定义)。

2、外观模式不能新增功能,但他可以将某些功能按次序执行。例如先打开DVD,后播放DVD。

3、子系统与外观不是一对一关系,是多对多关系。一个子系统可以拥有多个外观,一个外观可以调用多个子系统。

4、外观模式与适配器模式的区别不是包装的类的多少,而是意图不一样。适配器模式也可以包装很多类,但他的意图是改变接口,符合客户的期望;外观模式是将接口进行简化,方便使用。

 

TemplateMethod模式


作用:
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板模式使用的原则------好莱坞原则 低层不调用高层 高层会调用低层。

UML结构图:

image

抽象基类:
1)AbstractClass:抽象基类,定义算法的轮廓

解析:
TemplateMethod 的关键在于在基类中定义了一个算法的轮廓,但是算法每一步具体的实现留给了派生类.但是这样也会造成设计的灵活性不高的缺点,因为轮廓已经定下来了要想改变就比较难了,这也是为什么优先采用聚合而不是继承的原因.

 

Iterator模式


作用:
提供一种方法顺序访问一个聚合对象中各个元素,,而又不需暴露该对象的内部表示.

UML结构图:

image

解析:
在STL的实现中,所有的迭代器(Iterator)都必须遵照一套规范, 这样算法(Iterator的使用者)和容器(Iterator的提供者)就能很好的进行合作,而且不必关心对方是如何事先的,简而言之,Iterator就是算法和容器之间的一座桥梁.

 

抽象基类Iterator可以看做是前面提到的Iterator的规范,它提供了所有Iterator需要遵守的规范也就是对外的接口,而它的派生类ConcreateIterator则是ConcreateAggregate容器的迭代器,它遵照这个规范对容器进行迭代和访问操作.

int main()
{
    Aggregate* pAggregate = new ConcreateAggregate(4);
    Iterater*  pIterater  = new ConcreateIterater(pAggregate);

    for (; false == pIterater->IsDone(); pIterater->Next())
    {
        std::cout << pIterater->CurrentItem() << std::endl;
    }

    return 0;
}

 

composite & Decorator

在composite pattern里面,能独立完成功能的是 component. 当然,有两种componet 一种是leaf component, 一种是 composite componet.
在Decorator pattern里面,能够独立完成功能的是 concreteComponent, decorator不可以单独完成功能。例如JDK 里面 IO 库的 设计, BufferredInputStream 实际上是不可以单独完成 InputStream 的功能的。
也就是说, composite pattern里面 有用的是 component, 而 Decroator pattern 里面 有用的是 concreteComponent。
还有一个区别就是multiplicity, 在 decorator pattern里面, decorator 和 component的对应关系 是 一个 decorator 对应一个 componnet。 而 composite pattern里面 一个composite可以对应多个componnet。

 

Composite模式

MM今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店,你自己挑。”“这件T恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。”“喂,买了三件了呀,我只答应送一件礼物的哦。”“什么呀,T恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。”“……”,MM都会用Composite模式了


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

UML结构图:

image

抽象基类:
1)Component:为组合中的对象声明接口,声明了类共有接口的缺省行为(如这里的Add,Remove,GetChild函数),声明一个接口函数可以访问Component的子组件.

接口函数:
1)Component::Operatation:定义了各个组件共有的行为接口,由各个组件的具体实现.
2)Component::Add添加一个子组件
3)Component::Remove::删除一个子组件.
4)Component::GetChild:获得子组件的指针.

解析:
Component模式是为解决组件之间的递归组合提供了解决的办法,它主要分为两个派生类,其中的Leaf是叶子结点,也就是不含有子组件的结点,而Composite是含有子组件的类.举一个例子来说明这个模式,在UI的设计中,最基本的控件是诸如Button,Edit这样的控件,相当于是这里的Leaf组件,而比较复杂的控件比如List则可也看做是由这些基本的组件组合起来的控件,相当于这里的Composite,它们之间有一些行为含义是相同的,比如在控件上作一个点击,移动操作等等的,这些都可以定义为抽象基类中的接口虚函数,由各个派生类去实现之,这些都会有的行为就是这里的Operation函数,而添加,删除等进行组件组合的操作只有非叶子结点才可能有,所以虚拟基类中只是提供接口而且默认的实现是什么都不做.

 

 

Leaf *pLeaf1 = new Leaf();
Leaf *pLeaf2 = new Leaf();

Composite* pComposite = new Composite;
pComposite->Add(pLeaf1);
pComposite->Add(pLeaf2);
pComposite->Operation();
pComposite->GetChild(2)->Operation();

 

State模式

跟MM交往时,一定要注意她的状态哦,在不同的状态时她的行为会有不同,比如你约她今天晚上去看电影,对你没兴趣的MM就会说“有事情啦”,对你不讨厌但还没
喜欢上的MM就会说“好啊,不过可以带上我同事么?”,已经喜欢上你的MM就会说“几点钟?看完电影再去泡吧怎么样?”,当然你看电影过程中表现良好的话,也可以把MM的状态从不讨厌不喜欢变成喜欢哦。


作用: 允许一个对象在其内部状态改变时改变它的行为.

UML结构图:

image

解析:
State模式主要解决的是在开发中时常遇到的根据不同的状态需要进行不同的处理操作的问题,而这样的问题,大部分人是采用switch-case语句进行处理的,这样会造成一个问题:分支过多,而且如果加入一个新的状态就需要对原来的代码进行编译.State模式采用了对这些不同的状态进行封装的方式处理这类问题,当状态改变的时候进行处理然后再切换到另一种状态,也就是说把状态的切换责任交给了具体的状态类去负责.同时,State模式和Strategy模式在图示上有很多相似的地方,需要说明的是两者的思想都是一致的,只不过封装的东西不同:State模式封装的是不同的状态,而Stategy模式封装的是不同的算法.

State *pState = new ConcreateStateA();
Context *pContext = new Context(pState);
pContext->Request();

 

 

Proxy模式

跟MM在网上聊天,一开头总是“hi,你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?”这些话,真烦人,写个程序做为我的Proxy吧,凡是接收到这些话
都设置好了自动的回答,接收到其他的话时再通知我回答,怎么样,酷吧。


作用:
为其他对象提供一种代理以控制对这个对象的访问。

解决直接访问某些对象是出现的问题,如:访问远程的对象

代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不
想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象
,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。

* 房子的代理是房产商
* 房产商的代理是中介
* 买家直接找中介买房子,来改变房子的拥有权

UML结构图:

image

抽象基类:
1)Subject:定义了Proxy和RealSubject的公有接口,这样就可以在任何需要使用到RealSubject的地方都使用Proxy.

解析:
Proxy其实是基于这样一种时常使用到的技术-某个对象直到它真正被使用到的时候才被初始化,在没有使用到的时候就暂时用Proxy作一个占位符.这个模式实现的要点就是Proxy和RealSubject都继承自Subject,这样保证了两个的接口都是一致的.

 

Prototype模式

跟MM用QQ聊天,一定要说些深情的话语了,我搜集了好多肉麻的情话,需要时只要copy出来放到QQ里面就行了,这就是我的情话prototype了。
作用:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少产品类,产
品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。

UML结构图:

image

抽象基类:
1)Prototype:虚拟基类,所有原型的基类,提供Clone接口函数

接口函数:
1)Prototype::Clone函数:纯虚函数,根据不同的派生类来实例化创建对象.

解析:
Prototype模式其实就是常说的"虚拟构造函数"一个实现,C++的实现机制中并没有支持这个特性,但是通过不同派生类实现的Clone接口函数可以完成与"虚拟构造函数"同样的效果.举一个例子来解释这个模式的作用,假设有一家店铺是配钥匙的,他对外提供配制钥匙的服务(提供Clone接口函数),你需要配什么钥匙它不知道只是提供这种服务,具体需要配什么钥匙只有到了真正看到钥匙的原型才能配好.也就是说,需要一个提供这个服务的对象,同时还需要一个原型(Prototype),不然不知道该配什么样的钥匙.

Prototype* pPrototype1 = new ConcreatePrototype1();
Prototype* pPrototype2 = pPrototype1->Clone();

Prototype* pPrototype3 = new ConcreatePrototype2();
Prototype* pPrototype4 = pPrototype3->Clone();

delete pPrototype1;
delete pPrototype2;
delete pPrototype3;
delete pPrototype4;

 

FlyWeight模式

每天跟MM发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上MM的名字就可以发送了,再不用一个字一个字敲了。共享的句子就是Flyweight,MM的名字就是提取出来的外部特征,根据上下文情况使用。

作用: 运用共享技术有效地支持大量细粒度的对象。

享元模式:FLYWEIGHT在拳击比赛中指最轻量级。享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在
享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。将可以共享的状态和不可以共享的状态从常规类
中区分开来,将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的
数量。

UML结构图:

image

解析:
Flyweight模式在大量使用一些可以被共享的对象的时候经常使用.比如,在QQ聊天的时候很多时候你懒得回复又不得不回复的时候,一般会用一些客套的话语敷衍别人,如"呵呵","好的"等等之类的,这些简单的答复其实每个人都是提前定义好的,在使用的时候才调用出来.Flyweight就是基于解决这种问题的思路而产生的,当需要一个可以在其它地方共享使用的对象的时候,先去查询是否已经存在了同样的对象,如果没有就生成之有的话就直接使用.因此,Flyweight模式和Factory模式也经常混用.

Flyweight* FlyweightFactory::GetFlyweight(const STATE& key)
{
    std::list<Flyweight*>::iterator iter1, iter2;

    for (iter1 = m_listFlyweight.begin(), iter2 = m_listFlyweight.end();
         iter1 != iter2;
         ++iter1)
    {
        if ((*iter1)->GetIntrinsicState() == key)
        {
            std::cout << "The Flyweight:" << key << " already exits"<< std::endl;
            return (*iter1);
        }
    }

    std::cout << "Creating a new Flyweight:" << key << std::endl;
    Flyweight* flyweight = new ConcreateFlyweight(key);
    m_listFlyweight.push_back(flyweight);
}

 

Builder模式

 

作用:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

UML结构图:

image

适用于以下情况:

1)当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。

2)当构造过程必须允许被构造的对象有不同的表示时。

抽象基类:
1)Builder:这个基类是全部创建对象过程的抽象,提供构建不同组成部分的接口函数

接口:
1)Builder::BuildPartA,Builder::BuildPartB:是对一个对象不同部分的构建函数接口,Builder的派生类来具体实现.
另外还有一个需要注意的函数,就是Director::Construct函数,这个函数里面通过调用上面的两个接口函数完成对象的构建--也就是说各个不同部分装配的过程都是一致的(同样的调用的Construct函数),但是不同的构建方式会有不同的表示(根据Builder的实际类型来决定如何构建,也就是多态)

解析:
Builder模式是基于这样的一个情况:一个对象可能有不同的组成部分,这几个部分的不同的创建对象会有不同的表示,但是各个部分之间装配的方式是一致的.比方说一辆单车,都是由车轮车座等等的构成的(一个对象不同的组成部分),不同的品牌生产出来的也不一样(不同的构建方式).虽然不同的品牌构建出来的单车不同,但是构建的过程还是一样的(哦,你见过车轮长在车座上的么?).
也就是说,Director::Construct函数中固定了各个组成部分的装配方式,而具体是装配怎样的组成部分由Builder的派生类实现.

实现:
Builder模式的实现基于以下几个面向对象的设计原则:1)把变化的部分提取出来形成一个基类和对应的接口函数,在这里不会变化的是都会创建PartA和PartB,变化的则是不同的创建方法,于是就抽取出这里的Builder基类和BuildPartA,BuildPartB接口函数 2)采用聚合的方式聚合了会发生变化的基类,就是这里Director聚合了Builder类的指针.

 

Builder&Brige

Builder模式是对创建物品的过程进行的封装,Brige模式是对实现方式的封装.

这么一说,好像简单了一些,其实隐藏在这两个模式之后的原理都是一样的.首先,把变化的部分抽取出来形成一个抽象类;其次,把这个抽象类中不变的操作抽取出来形成虚函数也就是常说的接口;再次,把这个抽象类以聚合指针或者引用的方式聚合在需要实用它们的类中,因为在C++中只有引用和指针才能有多态的行为.

虽然是不同的模式,原理还是一致的.类似上面那样抽取变化和不变部分形成接口和抽象类从而形成模式的方法几乎在任何一个模式中都有体现,Statgy模式是对算法的封装,Observer是对对象的封装,Factory是对不同的创建的封装,Iterator是对不同迭代器的封装等等.

起初看设计模式的时候觉得很多模式都是一样的,后来逐渐开窍了又觉得似乎还是有区别的,现在如果再以面向对象的基本的原则来看各个模式的实现其实又是一样的了.--只是,这个从不懂到开窍到慢慢明白的过程我花去了一年多的时间.

 

Bridge模式

作用:
将抽象部分与它的实现部分分离,使它们都可以独立地变化。

UML结构图:

image

抽象基类:
1)Abstraction:某个抽象类,它的实现方式由Implementor完成.
2)Implementor:实现类的抽象基类,定义了实现Abastraction的基本操作,而它的派生类实现这些接口.

接口函数:
1)Implementor::OperationImpl:定义了为实现Abstraction需要的基本操作,由Implementor的派生类实现之,而在Abstraction::Operation函数中根据不同的指针多态调用这个函数.

解析:
Bridge用于将表示和实现解耦,两者可以独立的变化.在Abstraction类中维护一个Implementor类指针,需要采用不同的实现方式的时候只需要传入不同的Implementor派生类就可以了.

Bridge的实现方式其实和Builde十分的相近,可以这么说:本质上是一样的,只是封装的东西不一样罢了.两者的实现都有如下的共同点:抽象出来一个基类,这个基类里面定义了共有的一些行为,形成接口函数(对接口编程而不是对实现编程),这个接口函数在Buildier中是BuildePart函数在Bridge中是OperationImpl函数;其次,聚合一个基类的指针,如Builder模式中Director类聚合了一个Builder基类的指针,而Brige模式中Abstraction类聚合了一个Implementor基类的指针(优先采用聚合而不是继承);而在使用的时候,都把对这个类的使用封装在一个函数中,在Bridge中是封装在Director::Construct函数中,因为装配不同部分的过程是一致的,而在Bridge模式中则是封装在Abstraction::Operation函数中,在这个函数中调用对应的Implementor::OperationImpl函数.就两个模式而言,Builder封装了不同的生成组成部分的方式,而Bridge封装了不同的实现方式.

因此,如果以一些最基本的面向对象的设计原则来分析这些模式的实现的话,还是可以看到很多共同的地方的.

int main()
{
    ConcreateImplementorA *pImplA = new ConcreateImplementorA();
    Abstraction *pAbstraction1 = new Abstraction(pImplA);
    pAbstraction1->Operation();

    ConcreateImplementorB *pImplB = new ConcreateImplementorB();
    Abstraction *pAbstraction2 = new Abstraction(pImplB);
    pAbstraction2->Operation();

    delete pAbstraction1;
    delete pAbstraction2;

    system("pause");

    return 0;
}

 

 

Visitor模式

情人节到了,要给每个MM送一束鲜花和一张卡片,可是每个MM送的花都要针对她个人的特点,每张卡片也要根据个人的特点来挑,我一个人哪搞得清楚,还是找花店老板和礼品店老板做一下Visitor,让花店老板根据MM的特点选一束花,让礼品店老板也根据每个人特点选一张卡,这样就轻松多了


作用: 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作.


作用于某个对象群中各个对象的操作. 它可以使你在不改变这些对象本身的情况下,定义作用于这些对象的新操作.在Java中,Visitor模式实际上是分离了collection结构中的元素和对这些元素进行操作的行为.

UML结构图:

image

解析:
Visitor模式把对结点的访问封装成一个抽象基类,通过派生出不同的类生成新的访问方式.在实现的时候,在visitor抽象基类中声明了对所有不同结点进行访问的接口函数,如图中的VisitConcreateElementA函数等,这样也造成了Visitor模式的一个缺陷--新加入一个结点的时候都要添加Visitor中的对其进行访问接口函数,这样使得所有的Visitor及其派生类都要重新编译了,也就是说Visitor模式一个缺点就是添加新的结点十分困难.另外,还需要指出的是Visitor模式采用了所谓的"双重分派"的技术,拿上图来作为例子,要对某一个结点进行访问,首先需要产生一个Element的派生类对象,其次要传入一个Visitor类派生类对象来调用对应的Accept函数,也就是说,到底对哪种Element采用哪种Visitor访问,需要两次动态绑定才可以确定下来,具体的实现可以参考下面实现代码中的Main.cpp部分是如何调用这些类的.

int main()
{
    Visitor *pVisitorA = new ConcreateVisitorA();
    Element *pElement  = new ConcreateElementA();

    pElement->Accept(*pVisitorA);

    delete pElement;
    delete pVisitorA;

    return 0;
}

 

单分派(single dispatch)的含义比较好理解,单分派(single dispatch)就是说我们在选择一个方法的时候仅仅需要根据消息接收者(receiver)的运行时型别(Run time type)。实际上这也就是我们经常提到的多态的概念(当然C++中的函数重载也是Sigle dispatch的一种实现方式)。举一个简单的例子,我们有一个基类B,B有一个虚方法f(可被子类override),D1和D2是B的两个子类,在D1和D2中我们覆写(override)了方法f。这样我们对消息f的调用,需要根据接收者A或者A的子类D1/D2的具体型别才可以确定具体是调用A的还是D1/D2的f方法。

double dispatch(双分派)则在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时型别(Run time type),还要根据参数的运行时型别(Run time type)。当然如果所有参数都考虑的话就是multi-dispatch(多分派)。也举一个简单的例子,同于上面单分派中例子,A的虚方法f带了一个C型别的参数,C也是一个基类,C有也有两个具体子类E1和E2。这样,当我们在调用消息f的时候,我们不但要根据接收者的具体型别(A、D1、D2),还要根据参数的具体型别(C、E1、E2),才可以最后确定调用的具体是哪一个方法f。

遗憾的是,当前的主流面向对象程序设计语言(例如C++/Java/C#等)都并不支持双分派(多分派),仅仅支持单分派。为了支持双分派(多分派),一个权宜的方法就是借助RTTI和if语言来人工确定一个对象的运行时型别,并使用向下类型转换(downcast)来实现。一个常见的例子就是,我们取得对象的RTTI信息,然后if对象是某个具体类,则执行一部分操作,else属于另外的类则执行另外的操作…..。然而我们知道,RTTI一是占用较多的时间和空间,并且不是很安全(经常可能在downcast中出现exception)。

在Visitor模式的实现中,Element的Accept操作则是一个双分派的操作。

void ConcreteElementA::Accept(Visitor* vis)

{

vis->VisitConcreteElementA(this);

cout<<"visiting ConcreteElementA..."<<endl;

}

要具体确定是哪一个Accept操作,至少需要两个方面的信息:一是接受消息者(Element或其子类)的具体型别;二是参数Visitor的具体型别(Visitor或其子类)。而这里的双分派实际上是通过以下的方式实现的:1)在Element类层次里面,通过多态实现(也就是单分派);2)在Visitor类层次中,我们根据所有的Element具体类定义对应的visit操作,也就是VisitConcreteElementA()和VisitConcreteElementB()操作。当然可以通过上面给出RTTI的方式实现,但是这种方式我们并不提倡。

因此,Visitor模式实际上提供了对于支持单分派语言的双分派策略。关于double dispatch(双分派)、multi-dispatch(多分派)、多方法(multi-methods)的知识在很多的英文资料中可以找到(中文的基本很难找到),感兴趣可以进一步深入了解和研究。

 

Memento模式

同时跟几个MM聊天时,一定要记清楚刚才跟MM说了些什么话,不然MM发现了会不高兴的哦,幸亏我有个备忘录,刚才与哪个MM说了什么话我都拷贝一份放到备忘录里面保存,这样可以随时察看以前的记录啦。

备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而
可以在将来合适的时候把这个对象还原到存储起来的状态。


作用:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态.

UML结构图:

image

解析:
Memento模式中封装的是需要保存的状态,当需要恢复的时候才取出来进行恢复.原理很简单,实现的时候需要注意一个地方:窄接口和宽接口.所谓的宽接口就是一般意义上的接口,把对外的接口作为public成员;而窄接口反之,把接口作为private成员,而把需要访问这些接口函数的类作为这个类的友元类,也就是说接口只暴露给了对这些接口感兴趣的类,而不是暴露在外部.下面的实现就是窄实现的方法来实现的.

 

// 创建一个原发器
   Originator* pOriginator = new Originator("old state");
   pOriginator->PrintState();

   // 创建一个备忘录存放这个原发器的状态
   Memento *pMemento = pOriginator->CreateMemento();
   
   // 更改原发器的状态
   pOriginator->SetState("new state");
   pOriginator->PrintState();

   // 通过备忘录把原发器的状态还原到之前的状态
   pOriginator->RestoreState(pMemento);
   pOriginator->PrintState();

   delete pOriginator;
   delete pMemento;

 

部分来源:http://www.cppblog.com/converse/category/2256.html?Show=All

posted @ 2009-07-21 14:41  辛勤耕耘  阅读(919)  评论(0编辑  收藏  举报