一、抽象工厂模式(Abstract
Factory Pattern)
解决的问题:
在系统里a,b,c三个组件必须同时使用,但是a的同类 a1和a2这三种方法有共同特点但是是互斥的,b,b1,b2和c,c1,c2和a/a1/a2是一样的。就比如说创建在不同操作系统的视窗环境下都能够运行的系统时,Unix下面有unixButton和
unixText,Win下面也有winButton和winText,unixButton和unixText必须在一个系统unix里面用,而winButton和winText只能在Win下面用。但是winButton和unixButton这两种东西都是有相同的特点的,比如说按下去之后会触发事件,比如说他上面有文字描述等等,但是winButton和unixButton却又是不可以混用的。
那么此问题就可以用抽象工厂很好的解决:
在抽象工厂模式中如何选择使用 winButton ,winText,有具体的工厂类winFactory来负责,因为他们含有选择合适的产品对象的逻辑,所以是与应用系统的商业逻辑紧密相关的。而抽象工厂类来负责定义接口,他才是抽象工厂模式的核心。
而winButton/macButton则是一种产品族,有共同的特点,他们具体特点有抽象产品类或者接口来定义和描述。但是他们具体的实现有具体的产品类负责,这些是客户端最终想要的东西,所以其内部一定充满了应用系统的商业逻辑(触发逻辑/样式逻辑等)。
类图结构:
样例实现:
// CplusplusAbstractFactory.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include<typeinfo> // "AbstractProductA" 草食动物 class Herbivore { }; // "AbstractProductB" 食肉动物 class Carnivore { public: // Methods virtual void Eat( Herbivore *h ) {}; }; // "ProductA1" class Wildebeest : public Herbivore { }; // "ProductA2" class Bison : public Herbivore { }; // "ProductB1" class Lion : public Carnivore { public: // Methods void Eat( Herbivore *h ) { // eat wildebeest printf("Lion eats %s\n",typeid(h).name()); } }; // "ProductB2" class Wolf : public Carnivore { public: // Methods void Eat( Herbivore *h ) { // Eat bison printf("Wolf eats %s\n",typeid(h).name()); } }; // "AbstractFactory" class ContinentFactory { public: // Methods virtual Herbivore* CreateHerbivore() { return new Herbivore(); } virtual Carnivore* CreateCarnivore() { return new Carnivore(); } }; // "ConcreteFactory1" class AfricaFactory : public ContinentFactory { public: // Methods Herbivore* CreateHerbivore() { return new Wildebeest(); } Carnivore* CreateCarnivore() { return new Lion(); } }; // "ConcreteFactory2" class AmericaFactory : public ContinentFactory { public: // Methods Herbivore* CreateHerbivore() { return new Bison(); } Carnivore* CreateCarnivore() { return new Wolf(); } };
// "Client"
class AnimalWorld
{
private:
// Fields
Herbivore* herbivore;
Carnivore* carnivore;
public:
// Constructors
AnimalWorld( ContinentFactory *factory )
{
carnivore = factory->CreateCarnivore();
herbivore = factory->CreateHerbivore();
}
// Methods
void RunFoodChain()
{
carnivore->Eat(herbivore);
}
};int _tmain(int argc, _TCHAR* argv[])
{
// Create and run the Africa animal world
ContinentFactory *africa = new AfricaFactory();
AnimalWorld *world = new AnimalWorld( africa );
world->RunFoodChain();
// Create and run the America animal world
ContinentFactory *america = new AmericaFactory();
world = new AnimalWorld( america );
world->RunFoodChain();
return 0;
}
|
“开放-封闭”原则:
抽象工厂可以很好的应对增加新产品族的问题(即a4/b4/c4),且符合“开放-封闭”原则,但是若是增加新的产品结构的话(即d/d1/d2),就是说a/b/c/d这4中方法必须同时使用了,那就必须修改工厂角色。不符合“开放-封闭”原则。综合来讲,抽象工厂模式以一种倾斜的方式支持增加新的产品,它为新产品族的增加提供方便,而不能为新的产品结构的增加提供这样的方便。
实现要点:
在抽象工厂模式中,选用哪种产品族的问题,需要采用工厂方法或简单工厂模式来配合解决。
抽象工厂模式和工厂方法模式一样,都把对象的创建延迟到了他的子类中。
具体的工厂类可以设计成单例类,他只向外界提供自己唯一的实例。
与其他工厂模式的联系和异同:
抽象工厂模式中的具体工厂负责生产一个产品族的产品。而产品族的增加只需要增加与其对应的具体工厂。
3种工厂模式都是创建型模式,都是创建对象的,但都把产品具体创建的过程给隐藏了。
工厂方法模式是针对一种产品结构,而抽象工厂模式是针对多种产品结构。
适用性:
在以下情况下应当考虑使用抽象工厂模式:
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
这个系统有多于一个的产品族,而系统只消费其中某一产品族。
同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。
系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。
应用场景:
支持多种观感标准的用户界面工具箱(Kit)。
游戏开发中的多风格系列场景,比如道路,房屋,管道等。
二、建造者模式(Builder Pattern)
解决的问题:
我创建的这个对象比较复杂,且该对象里面的成员函数用不同的实现来表示不同的实例,换句话说就是同样的对象构建过程可以有不同的表示。比如我那天去吃过桥米线,他们有不同的套餐,套餐里包含的种类是一样的,都有一碗米线,一份凉菜,一杯饮料。但是不同的套餐里这3样又都不是全部一样的。此时我们就可以用建造者模式。
类图结构:
1.建造者(Builder)角色:给出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体建造者(Concrete
Builder)角色。具体建造者类必须实现这个接口所要求的方法:一个是建造方法,另一个是结果返还方法。此时就是米线店的员工,按照收银员的要求的去准备具体的套餐,放入适当的米线,凉菜和饮料。
2.具体建造者(Concrete Builder)角色:担任这个角色的是于应用程序紧密相关的类,它们在应用程序调用下创建产品实例。这个角色主要完成的任务包括:实现Builder角色提供的接口,一步一步完成创建产品实例的过程。在建造过程完成后,提供产品的实例。是具体的做某个套餐的员工。
3.指导者(Director)角色:担任这个角色的类调用具体建造者角色以创建产品对象。导演者并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者对象。是收银员,他知道我想要什么套餐,他会告诉里面的米线店员工去准备什么套餐。
4.产品(Product)角色:产品便是建造中的复杂对象。指导者角色是于客户端打交道的角色。导演者角色将客户端创建产品的请求划分为对各个零件的建造请求,再将这些请求委派给具体建造者角色。具体建造者角色是做具体建造工作的,但却不为客户端所知。就是最后的套餐,所有东西放到一起端过来。
样例实现:
// CplusplusBuild.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <vector> #include <string> #include <iostream> using namespace std; //product class Food { private:<strong> </strong>vector<string> mFoodName; vector<int> mFoodPrice; public: void add(string foodName,int price) { mFoodName.push_back(foodName); mFoodPrice.push_back(price); } void show() { cout<<"Food List" <<endl; cout<<"-------------------"<<endl; for(int i=0;i<mFoodName.size();i++) { cout<<mFoodName[i]<<" "<<mFoodPrice[i]<<endl; } } }; //builder class Builder { public: virtual void BuildRiceNoodles() {}; virtual void BuildCoolDish(){}; virtual void BuildDrink(){}; virtual Food * getFood(){return NULL;} };//builderA class BuilderA:public Builder { private: Food *food; public: BuilderA(){food = new Food();} void BuildRiceNoodles() { food->add("RiceNoodlesA",20); } void BuildCoolDish() { food->add("CoolDishA",20); } void BuildDrink() { food->add("DrinkA",20); } Food * getFood() { return food; } }; //builderB class BuilderB:public Builder { private: Food *food; public: BuilderB(){food = new Food();} void BuildRiceNoodles() { food->add("RiceNoodlesB",10); } void BuildCoolDish() { food->add("CoolDishB",10); } void BuildDrink() { food->add("DrinkB",10); } Food * getFood() { return food; } }; //director class FoodManager { public: void Construct(Builder * builder) { builder->BuildRiceNoodles(); builder->BuildDrink(); builder->BuildCoolDish(); } }; //clent int _tmain(int argc, _TCHAR* argv[]) { FoodManager *foodManager= new FoodManager(); Builder * builder = new Builder(); // the following code can use simple factory; char ch; cout<<"input your food Type (A or B):"; cin>>ch; if(ch=='A') { builder = new BuilderA(); }else if(ch=='B') { builder = new BuilderB(); } foodManager->Construct(builder); Food * food = builder->getFood(); food->show(); return 0; } |
建造者模式的扩展:
建造者模式在使用过程中可以演化出多种形式:
省略抽象建造者角色
如果系统中只需要一个具体的建造者的话,可以省略掉抽象建造者。这是代码可能如下:
//director class FoodManager { private: BuilderA * builder; public: FoodManager() {builder = new BuilderA();}; void Construct() { builder->BuildRiceNoodles(); builder->BuildDrink(); builder->BuildCoolDish(); } }; |
省略指导者角色
在具体建造者只有一个的情况下,如果抽象建造者角色已经被省略掉,那么还可以省略掉指导者角色,让Builder自己扮演指导者和建造者双重角色。这是代码可能如下:
//builder class Builder { private: Food * food; public: Builder(){food = new Food();} void BuildRiceNoodles() {//..}; void BuildCoolDish(){//..}; void BuildDrink(){//..}; Food * getFood(){return food;} void Construct() { BuildRiceNoodles(); BuildCoolDish(); BuildDrink(); } }; |
同时,客户端也需要进行相应的调整,如下:
//client int _tmain(int argc, _TCHAR* argv[]) { Builder * builder = new Builder(); builder->Construct(); Food *food = builder->getFood(); food->show(); return 0; } |
C#中的StringBuilder就是这样一个例子。
实现要点:
1.建造者模式主要用于“分步骤构建一个复杂的对象”,在这其中“每个步骤”是一个稳定的算法,而复杂对象的各个步骤之间则经常变化。
2.上一篇所说的抽象工厂模式解决“系列对象”的需求变化,而建造者模式解决单个对象里“对象部分”的需求变化。
产品不需要抽象类,特别是由于创建对象的算法复杂而导致使用此模式的情况下或者此模式应用于产品的生成过程,其最终结果可能差异很大,不大可能提炼出一个抽象产品类。
3.创建者中的创建子部件的接口方法不是抽象方法而是空方法,不进行任何操作,具体的创建者只需要覆盖需要的方法就可以,但是这也不是绝对的,特别是类似文本转换这种情况下,缺省的方法将输入原封不动的输出是合理的缺省操作。
适用性:
以下情况应当使用建造者模式:
1、需要生成的产品对象有复杂的内部结构。
2、需要生成的产品对象的属性相互依赖,建造者模式可以强迫生成顺序。
3、 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。
效果
1、建造者模式的使用使得产品的内部表象可以独立的变化。使用建造者模式可以使客户端不必知道产品内部组成的细节。
2、每一个Builder都相对独立,而与其它的Builder无关。
3、可使对构造过程更加精细控制。
4、将构建代码和实现代码分开。
5、建造者模式的缺点在于难于应付“分步骤构建算法”的需求变动。
三、原型模式(Prototype Pattern)
解决的问题:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。这个其实和C++的拷贝构造函数的作用是一致的,实际上就是动态抽取当前对象运行时的状态。
类图结构:
客户(Client)角色:客户类提出创建对象的请求。
抽象原型(Prototype)角色:这是一个抽象角色,通常由一个C#接口或抽象类实现。此角色给出所有的具体原型类所需的接口。在C#中,抽象原型角色通常实现了ICloneable接口。
具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象原型角色所要求的接口。
样例实现:
例子参照wuzhekai1985的简历的例子,代码拷贝如下:
// CplusplusPrototype.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include<iostream> #include<vector> #include<assert.h> using namespace std; //父类 class Resume { protected: char *name; public: Resume() {} virtual ~Resume() {} virtual Resume* Clone() { return NULL; } virtual void Set(char *n) {} virtual void Show() {} }; class ResumeA : public Resume { public: ResumeA(const char *str); //构造函数 ResumeA(const ResumeA &r); //拷贝构造函数 ~ResumeA(); //析构函数 ResumeA* Clone(); //克隆,关键所在 void Show(); //显示内容 }; ResumeA::ResumeA(const char *str) { if(str == NULL) { name = new char[1]; name[0] = '\0'; } else { name = new char[strlen(str)+1]; strcpy(name, str); } } ResumeA::~ResumeA() { delete [] name;} ResumeA::ResumeA(const ResumeA &r) { name = new char[strlen(r.name)+1]; strcpy(name, r.name); } ResumeA* ResumeA::Clone() { return new ResumeA(*this); } void ResumeA::Show() { cout<<"ResumeA name : "<<name<<endl; } class ResumeB : public Resume { public: ResumeB(const char *str); //构造函数 ResumeB(const ResumeB &r); //拷贝构造函数 ~ResumeB(); //析构函数 ResumeB* Clone(); //克隆,关键所在 void Show(); //显示内容 }; ResumeB::ResumeB(const char *str) { if(str == NULL) { name = new char[1]; name[0] = '\0'; } else { name = new char[strlen(str)+1]; strcpy(name, str); } } ResumeB::~ResumeB() { delete [] name;} ResumeB::ResumeB(const ResumeB &r) { name = new char[strlen(r.name)+1]; strcpy(name, r.name); } ResumeB* ResumeB::Clone() { return new ResumeB(*this); } void ResumeB::Show() { cout<<"ResumeB name : "<<name<<endl; } int _tmain(int argc, _TCHAR* argv[]) { Resume *r1 = new ResumeA("A"); Resume *r2 = new ResumeB("B"); Resume *r3 = r1->Clone(); Resume *r4 = r2->Clone(); r1->Show(); r2->Show(); //删除r1,r2 delete r1; delete r2; r1 = r2 = NULL; //深拷贝所以对r3,r4无影响 r3->Show(); r4->Show(); delete r3; delete r4; r3 = r4 = NULL; return 0; }
|
带Prototype Manager的原型模式:
客户(Client)角色:客户端类向原型管理器提出创建对象的请求。
抽象原型(Prototype)角色:这是一个抽象角色,通常由一个C#接口或抽象类实现。此角色给出所有的具体原型类所需的接口。在C#中,抽象原型角色通常实现了ICloneable接口。
具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
原型管理器(Prototype Manager)角色:创建具体原型类的对象,并记录每一个被创建的对象。
代码实现如下:
// CplusplusPrototype.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include<iostream> #include<vector> #include<assert.h> using namespace std; //父类 class Resume { protected: char *name; public: Resume() {} virtual ~Resume() {} virtual Resume* Clone() { return NULL; } virtual void Set(char *n) {} virtual void Show() {} }; class ResumeA : public Resume { public: ResumeA(const char *str); //构造函数 ResumeA(const ResumeA &r); //拷贝构造函数 ~ResumeA(); //析构函数 ResumeA* Clone(); //克隆,关键所在 void Show(); //显示内容 }; ResumeA::ResumeA(const char *str) { if(str == NULL) { name = new char[1]; name[0] = '\0'; } else { name = new char[strlen(str)+1]; strcpy(name, str); } } ResumeA::~ResumeA() { delete [] name;} ResumeA::ResumeA(const ResumeA &r) { name = new char[strlen(r.name)+1]; strcpy(name, r.name); } ResumeA* ResumeA::Clone() { return new ResumeA(*this); } void ResumeA::Show() { cout<<"ResumeA name : "<<name<<endl; } class ResumeB : public Resume { public: ResumeB(const char *str); //构造函数 ResumeB(const ResumeB &r); //拷贝构造函数 ~ResumeB(); //析构函数 ResumeB* Clone(); //克隆,关键所在 void Show(); //显示内容 }; ResumeB::ResumeB(const char *str) { if(str == NULL) { name = new char[1]; name[0] = '\0'; } else { name = new char[strlen(str)+1]; strcpy(name, str); } } ResumeB::~ResumeB() { delete [] name;} ResumeB::ResumeB(const ResumeB &r) { name = new char[strlen(r.name)+1]; strcpy(name, r.name); } ResumeB* ResumeB::Clone() { return new ResumeB(*this); } void ResumeB::Show() { cout<<"ResumeB name : "<<name<<endl; } class ResumeManager { private: vector<Resume *> mResume; public: ResumeManager() { } void add(Resume * resume) { mResume.push_back(resume); } Resume * get(int index) const { assert(index>=0 && index<mResume.size()); return mResume[index]; } }; int _tmain(int argc, _TCHAR* argv[]) { ResumeManager *manager = new ResumeManager(); Resume *r1 = new ResumeA("A"); Resume *r2 = new ResumeB("B"); manager->add(r1); manager->add(r2); manager->get(0)->Show(); manager->get(1)->Show(); Resume *r3 = manager->get(0)->Clone(); Resume *r4 = manager->get(1)->Clone(); //删除r1,r2 delete r1; delete r2; r1 = r2 = NULL; //深拷贝所以对r3,r4无影响 r3->Show(); r4->Show(); delete r3; delete r4; r3 = r4 = NULL; return 0; }
|
实现要点:
1.使用原型管理器,体现在一个系统中原型数目不固定时,可以动态的创建和销毁。
2.实现克隆操作,在.NET中可以使用Object类的MemberwiseClone()方法来实现对象的浅表拷贝或通过序列化的方式来实现深拷贝,在C++中就是拷贝构造函数的作用。
3.Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有稳定的接口。
效果:
1.它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。
2. Prototype模式允许客户只通过注册原型实例就可以将一个具体产品类并入到系统中,客户可以在运行时刻建立和删除原型。
3.减少了子类构造,Prototype模式是克隆一个原型而不是请求工厂方法创建一个,所以它不需要一个与具体产品类平行的Creater类层次。
4.Portotype模式具有给一个应用软件动态加载新功能的能力。由于Prototype的独立性较高,可以很容易动态加载新功能而不影响老系统。
5.产品类不需要非得有任何事先确定的等级结构,因为Prototype模式适用于任何的等级结构
6.Prototype模式的最主要缺点就是每一个类必须配备一个克隆方法。而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事。
适用性:
1. 当一个系统应该独立于他的产品创建、构成和表示时,需要使用原型模式
2. 当要实例化的类是在运行时刻指定时,如通过动态装载
3. 为了避免创建一个与产品类层次平行的工厂类层次时
4. 当一个类的实例只能有几个不同状态组合中的一种时,建立相应数目的原型并克隆他们可能比每次用合适的状态手工实例化该类更方便一些。
四、适配器模式(Adapter Pattern)
解决的问题:
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作。比如说我的hp笔记本,美国产品,人家美国的电压是110V的,而我们中国的电压是220V,要在中国能使用,必须找个变压器转一下电压才可以。这个变压器就是个适配器。
适配器模式有类适配器和对象适配器两种模式,我们将分别讨论。
类适配器:
由图中可以看出,Adaptee类没有Request方法,而客户期待这个方法。为了使客户能够使用Adaptee类,提供一个中间环节,即类Adapter类,Adapter类实现了Target接口,并继承自Adaptee,Adapter类的Request方法重新封装了Adaptee的SpecificRequest方法,实现了适配的目的。
因为Adapter与Adaptee是继承的关系,所以这决定了这个适配器模式是类的。
该适配器模式所涉及的角色包括:
目标(Target)角色:这是客户所期待的接口。因为C#不支持多继承,所以Target必须是接口,不可以是类。
源(Adaptee)角色:需要适配的类。
适配器(Adapter)角色:把源接口转换成目标接口。这一角色必须是类
简单实现:
#include<iostream> using namespace std; // "ITarget" class Target { public: // Methods virtual void Request(){}; }; // "Adaptee" class Adaptee { public: // Methods void SpecificRequest() { cout<<"Called SpecificRequest()"<<endl; } }; // "Adapter" class Adapter : public Adaptee, public Target { public: // Implements ITarget interface void Request() { // Possibly do some data manipulation // and then call SpecificRequest this->SpecificRequest(); } };int main() { // Create adapter and place a request Target *t = new Adapter(); t->Request(); return 0; } |
对象适配器:
从图中可以看出:客户端需要调用Request方法,而Adaptee没有该方法,为了使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例,从而将客户端与Adaptee衔接起来。由于Adapter与Adaptee是委派关系,这决定了这个适配器模式是对象的。
该适配器模式所涉及的角色包括:
目标(Target)角色:这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
源(Adaptee)角色:需要适配的类。
适配器(Adapter)角色:通过在内部包装(Wrap)一个Adaptee对象,把源接口转换成目标接口。
简单实现:
#include<iostream> using namespace std; // "ITarget" class Target { public: // Methods virtual void Request(){}; }; // "Adaptee" class Adaptee { public: // Methods void SpecificRequest() { cout<<"Called SpecificRequest()"<<endl; } }; // "Adapter" class Adapter : public Target { private: Adaptee *adaptee; public: Adapter() { adaptee = new Adaptee(); } // Implements ITarget interface void Request() { // Possibly do some data manipulation // and then call SpecificRequest adaptee->SpecificRequest(); } };int main() { // Create adapter and place a request Target *t = new Adapter(); t->Request(); return 0; } |
缺省适配器:
缺省适配器模式是一种特殊的适配器模式,但这个适配器是由一个抽象类实现的,并且在抽象类中要实现目标接口中所规定的所有方法,但很多方法的实现都是“平庸”的实现,也就是说,这些方法都是空方法。而具体的子类都要继承此抽象类。
简单实现:
#include<iostream> using namespace std;class Target { public: virtual void f1(){}; virtual void f2(){}; virtual void f3(){}; }; class DefaultAdapter : public Target { public: void f1() { } void f2() { } void f3() { } }; class MyInteresting :public DefaultAdapter { public: void f3(){ cout<<"呵呵,我就对f3()方法感兴趣,别的不管了!"<<endl; } }; int main() { // Create adapter and place a request Target *t = new MyInteresting(); t->f3(); return 0; } |
实现要点:
1.Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
2.Adapter模式有对象适配器和类适配器两种形式的实现结构,但是类适配器采用“多继承”的实现方式,带来了不良的高耦合,所以一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神。
3.Adapter模式的实现可以非常的灵活,不必拘泥于GOF23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。
4.Adapter模式本身要求我们尽可能地使用“面向接口的编程”风格,这样才能在后期很方便的适配。
使用场景:
在以下各种情况下使用适配器模式:
1.系统需要使用现有的类,而此类的接口不符合系统的需要。
2.想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
3.(对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。
|