五、适配器模式(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.(对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。
六、组合模式(Composite Pattern)
解决的问题:
我们PC用到的文件系统,其实就是我们数据结构里的树形结构,我们处理树中的每个节点时,其实不用考虑他是叶子节点还是根节点,因为他们的成员函数都是一样的,这个就是组合模式的精髓。他模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
注明:树形结构里的叶子节点也有左右孩子,只不过他的孩子都是空。
概述
组合模式的实现根据所实现接口的区别分为两种形式,分别称为安全模式和透明模式。组合模式可以不提供父对象的管理方法,但组合模式必须在合适的地方提供子对象的管理方法(诸如:add、remove、getChild等)。
透明方式
作为第一种选择,在Component里面声明所有的用来管理子类对象的方法,包括add()、remove(),以及getChild()方法。这样做的好处是所有的构件类都有相同的接口。在客户端看来,树叶类对象与合成类对象的区别起码在接口层次上消失了,客户端可以同等同的对待所有的对象。这就是透明形式的组合模式。
这个选择的缺点是不够安全,因为树叶类对象和合成类对象在本质上是有区别的。树叶类对象不可能有下一个层次的对象,因此add()、remove()以及getChild()方法没有意义,是在编译时期不会出错,而只会在运行时期才会出错或者说识别出来。
安全方式
第二种选择是在Composite类里面声明所有的用来管理子类对象的方法。这样的做法是安全的做法,因为树叶类型的对象根本就没有管理子类对象的方法,因此,如果客户端对树叶类对象使用这些方法时,程序会在编译时期出错。
这个选择的缺点是不够透明,因为树叶类和合成类将具有不同的接口。
这两个形式各有优缺点,需要根据软件的具体情况做出取舍决定。
类图结构及样例实现:
这里给出安全方式的组合模式的类图结构和样例实现,透明方式就是在叶子节点的add()/remove()/GetChild()均有实现,不过是无意义的实现。大部分应用都是基于透明模式的,因为这样代码可以重用。
安全方式的组合模式:
这种形式涉及到三个角色:
抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。在安全式的合成模式里,构件角色并不是定义出管理子对象的方法,这一定义由树枝构件对象给出。
树叶构件(Leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。
树枝构件(Composite)角色:代表参加组合的有下级子对象的对象。树枝对象给出所有的管理子对象的方法,如add()、remove()、getChild()等。
样例实现:
//Menu.h #include <string> class Menu { public: virtual ~Menu(); virtual void Add(Menu*); virtual void Remove(Menu*); virtual Menu* GetChild(int); virtual void Display() = 0; protected: Menu(); Menu(std::string); std::string m_strName; }; //Menu.cpp #include "stdafx.h" #include "Menu.h" Menu::Menu() { } Menu::Menu(std::string strName) : m_strName(strName) { } Menu::~Menu() { } void Menu::Add(Menu* pMenu) {} void Menu::Remove(Menu* pMenu) {} Menu* Menu::GetChild(int index) { return NULL; } //SubMenu.h #include "Menu.h" class SubMenu : public Menu { public: SubMenu(); SubMenu(std::string); virtual ~SubMenu(); void Display(); }; //SubMenu.cpp #include "stdafx.h" #include "SubMenu.h" #include <iostream> using namespace std; SubMenu::SubMenu() { } SubMenu::SubMenu(string strName) : Menu(strName) { } SubMenu::~SubMenu() { } void SubMenu::Display() { cout << m_strName << endl; } //CompositMenu.h #include "Menu.h" #include <vector> class CompositMenu : public Menu { public: CompositMenu(); CompositMenu(std::string); virtual ~CompositMenu(); void Add(Menu*); void Remove(Menu*); Menu* GetChild(int); void Display(); private: std::vector<Menu*> m_vMenu; }; //CompositMenu.cpp #include "stdafx.h" #include "CompositMenu.h" #include <iostream> using namespace std; CompositMenu::CompositMenu() { } CompositMenu::CompositMenu(string strName) : Menu(strName) { } CompositMenu::~CompositMenu() { } void CompositMenu::Add(Menu* pMenu) { m_vMenu.push_back(pMenu); } void CompositMenu::Remove(Menu* pMenu) { m_vMenu.erase(&pMenu); } Menu* CompositMenu::GetChild(int index) { return m_vMenu[index]; } void CompositMenu::Display() { cout << "+" << m_strName << endl; vector<Menu*>::iterator it = m_vMenu.begin(); for (; it != m_vMenu.end(); ++it) { cout << "|-"; (*it)->Display(); } } #include "stdafx.h" #include "Menu.h" #include "SubMenu.h" #include "CompositMenu.h" int main(int argc, char* argv[]) { Menu* pMenu = new CompositMenu("国内新闻"); pMenu->Add(new SubMenu("时事新闻")); pMenu->Add(new SubMenu("社会新闻")); pMenu->Display(); pMenu = new CompositMenu("国际新闻"); pMenu->Add(new SubMenu("国际要闻")); pMenu->Add(new SubMenu("环球视野")); pMenu->Display(); return 0; } |
实现要点:
1.组合模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
2.将“客户代码与复杂的对象容器结构”解耦是组合模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复内部实现结构——发生依赖关系,从而更能“应对变化”。
3.组合模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。这里有可能违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。
4.组合模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。
5. 客户端尽量不要直接调用树叶类的方法,而是借助其父类(Component)的多态性完成调用,这样可以增加代码的复用性。
使用场景:
以下情况下适用组合模式:
1.你想表示对象的部分-整体层次结构。
2.你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
七、装饰者模式(Decorator Pattern)
解决的问题:
我们在装饰新家的时候买了几幅抽象画,买回来之后发现有些加上色彩艳丽的边框更适合我们,而有的加上玻璃罩之后更能符合我们的使用。那我们来怎么解决这个问题呢?他需要动态的给别的对象增加额外的职责,这就是装饰者模式的目的。
我们可以通过继承的方式来给原对象增加新功能,但是装饰者模式采用组合的方式比生成子类更加灵活。
类图及样例实现:
在装饰模式中的各个角色有:
抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
具体装饰(Concrete Decorator)角色:负责给构件对象"贴上"附加的责任。
#include <string> #include <iostream> #include <memory> using namespace std; //抽象类Tank class Tank { public: virtual void shot()=0; virtual void run()=0; public: virtual ~Tank() { cout<<"in the destructor of Tank"<<endl; } }; //具体类 T50 class T50:public Tank { public: void shot() { cout<<"Tank T50 shot()"<<endl; } void run() { cout<<"Tank T50 run()"<<endl; } public: virtual ~T50() { cout<<"In the destructor of T50"<<endl; } }; //具体类T75 class T75:public Tank { public: void shot() { cout<<"Tank T75 shot()"<<endl; } void run() { cout<<"Tank T75 run()"<<endl; } public: virtual ~T75() { cout<<"In the destructor of T75"<<endl; } }; //抽象类,Decorator class Decorator:public Tank { protected: Tank* tank; public: Decorator(Tank* tank):tank(tank) {} //具体的坦克的装饰类 virtual ~Decorator() { cout<<"In the destructor of Decorator"<<endl; } public: void shot() { tank->shot(); } void run() { tank->run(); } }; class InfraredDecorator: public Decorator { private: string infrared;//这就是所谓的addAtrribute public: InfraredDecorator(Tank* tank):Decorator(tank) {} virtual ~InfraredDecorator() { cout<<"in the destructor of InfraredDecorator"<<endl; } public: void set_Infrared(const string &infrared) { this->infrared=infrared; } string get_infrared() const { return infrared; } void run() { tank->run(); set_Infrared("+Infrared"); cout<<get_infrared()<<endl; } void shot() { tank->shot(); } }; class AmphibianDecorator:public Decorator { private: string amphibian; public: AmphibianDecorator(Tank* tank):Decorator(tank) {} ~AmphibianDecorator() { cout<<"in the destructor of AmphibianDecorator"<<endl; } public: void set_amphibian(const string &hibian) { this->amphibian=hibian; } string get_amphibian() const { return amphibian; } public: void run() { tank->run(); set_amphibian("+amphibian"); cout<<get_amphibian()<<endl; } void shot() { tank->shot(); } }; int main(int argc, char **argv) { //给T50增加红外功能 Tank* tank1(new T50); Tank* pid1(new InfraredDecorator(tank1)); pid1->shot(); cout<<endl; pid1->run(); cout<<endl; cout<<endl<<"---------------"<<endl; //给t75增加红外、两栖功能 Tank* tank2(new T75); tank2->run(); Tank* pid2(new InfraredDecorator(tank2)); Tank* pad2(new AmphibianDecorator(pid2)); pad2->shot(); cout<<endl; pad2->run(); cout<<endl; cout<<endl<<"--------------"<<endl; //动态撤销其他装饰 ? tank2->run(); Tank * tank3(tank2); tank3->run(); return 0; } |
八、外观模式(Facade Pattern)
概述
想想我们小时候玩的四驱车,里面的构造很复杂,马达,舵机,电池组等等,而我们控制它却非常简单,只要打开电池开关,他就可以跑。我们其实不用知道它里面是如何工作,只要知道拨动开关它就可以工作就行了,这个开关其实就四驱车给我们的一个友好的组件,使得我们可以很方便的控制它。
外观模式其实定义了一个高层接口,该接口为子系统中的一组接口提供一个一致的界面,使得这一子系统更加容易使用。
类图和样例
在这个对象图中,出现了两个角色:
外观(Facade)角色:客户端可以调用这个角色的方法。此角色知晓相关的(一个或者多个)子系统的功能和责任。在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。
子系统(subsystem)角色:可以同时有一个或者多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。每一个子系统都可以被客户端直接调用,或者被外观角色调用。子系统并不知道外观的存在,对于子系统而言,外观仅仅是另外一个客户端而已。
#include<iostream> using namespace std; class Scanner { public: void Scan() { cout<<"词法分析"<<endl; } }; class Parser { public: void Parse() { cout<<"语法分析"<<endl; } }; class GenMidCode { public: void GenCode() { cout<<"产生中间代码"<<endl; } }; class GenMachineCode { public: void GenCode() { cout<<"产生机器码"<<endl;} }; //高层接口 Fecade class Compiler { public: void Run() { Scanner scanner; Parser parser; GenMidCode genMidCode; GenMachineCode genMacCode; scanner.Scan(); parser.Parse(); genMidCode.GenCode(); genMacCode.GenCode(); } }; //client int main() { Compiler compiler; compiler.Run(); return 0; } |
要点
1.Facade模式对客户屏蔽了子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
2.Facade模式实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。
3.如果应用需要,它并不限制它们使用子系统类。因此你可以在系统易用性与通用性之间选择。
4. 在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类。当然这并不意味着在整个系统里只能有一个外观类,而仅仅是说对每一个子系统只有一个外观类。或者说,如果一个系统有好几个子系统的话,每一个子系统有一个外观类,整个系统可以有数个外观类。
5. 外观模式的用意是为子系统提供一个集中化和简化的沟通管道,而不建议向子系统加入新的行为。
6. 外观模式注重的是简化接口,它更多的时候是从架构的层次去看整个系统,而并非单个类的层次。
适用性和优缺点
适用性
1.为一个复杂子系统提供一个简单接口。
2.提高子系统的独立性。
3.在层次化结构中,可以使用Facade模式定义系统中每一层的入口。
优点
1. 松散耦合
外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护。即要点2.
2. 简单易用
外观模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观交互就可以了,相当于外观类为外部客户端使用子系统提供了一站式服务。
3. 更好的划分访问层次
通过合理使用Facade,可以帮助我们更好的划分访问的层次。有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到外观中,这样既方便客户端使用,也很好的隐藏了内部的细节。
缺点
过多的或者是不太合理的Facade也容易让人迷惑,到底是调用Facade好呢,还是直接调用模块好。 |