0 迭代器模式和适配器模式组合模式
这几个模式放在一起讲的原因在于:都在向外界提供统一的借口
适配器模式是在项目已经开始,新加入的对象需要和原有对象有一致的接口。
而迭代器模式应用则比较狭隘,只是为了遍历容器中的对象,向外提供统一的接口。
组合模式在于不同种类的某种事物向外提供统一的接口。
1 适配器模式
1.0 需求
通常都是某个类A已经在系统中使用了很长时间,使用该类的代码也很多。后来突然来了一个类B,类B具有和类A同样的功能,但是实现不同,而且向外提供的接口不同。也就是说类A和类B不派生自同一个类。
因此适配器模式解决的问题是,如何处理两个功能相同,但是提供接口的类,让其向外提供统一的接口。
方法很简单,新增一个类C,让类C关联(组合)类B,同时继承类A的基类。类C中的接口通过关联的类B实例实现一样的功能。
适配器模式的最终目标:已有的代码能够复用
要注意:之前的类A一定是一个派生类,也就是有一个基类。如果没有基类,这个模式行不通。如果之前的类没有一个基类。那么后面的类智能使用相同的方法调用。但是之前的代码不能够复用,因为需要的参数不能够转换。重载类型转换也是不可以的。
1.1 实现
class ABCompiler { public: virtual void do()=0; };
// 已有的使用很久的类,不能够变更 class Gnu :public ABCompiler { public: void do(Path,Op);//选项和 do 方法结合在一起 }; // 新来了一个能够提供一样功能的类 class Clang { public: void compile(); void add_op(); // 编译选项需要额外添加 private: Op op; }; class CompilerClang :public ABCompiler { public: //内部实现和 gnu 相同的功能 virtual void do(Path path,Op op) { clang.add_op(op); clang.compile(Path); } private: Clang clang; //组合的方式,在 CompilerClang 创建的时候创建,销毁的时候销毁。 };
1.2 缺点
之前的类,一定要存在继承体系,不然已有的使用类的代码不能够工作。
也就是说要符号依赖倒置和里氏替换原则,才能够使用适配器模式。
2 迭代器模式
2.0 需求
存在不同的容器,vector和map,两者之间遍历元素的方式不同。一种是数组,一种是树。
而迭代器模式,就是封装两者遍历元素的行为,向外提供统一的接口:
- ++下一个元素
- --上一个元素
因此迭代器模式可以理解为重载容器的++和--运算符,但是直接重载容器类的++和--肯定不行,因为不能做到代码复用,也就是说使用vector的代码不能用在map上。
因此,新增一个Iterator的虚基类,有next、end和begin三个接口。然后为不同的容器新增一个具体的迭代器类,继承Iterator虚基类。
然后不同容器关联(组合)这个迭代器类的实例。
2.1 现状
迭代器模式,现在已经很少自己实现了。一般是有语言或是标准库提供。因为迭代器一般使用在容器上的。而容器又是标准库提供的。
c++标准库的迭代器也不是使用迭代器模式实现的。
而是使用模板+函数重载实现的。
3 组合模式
3.0 需求
在某些情况下,用到分支和节点。也就是子节点和叶节点,操作不同种类的节点之间出现的情况不同。
例如,linux下各种文件类型,打开普通文件和目录文件的结果是不同的。
因此需要一种设计模式,能够统一的访问这些资源。也就是说以统一的接口访问节点。
并且节点之间存在包含的关系。
这其实和适配器模式类似,都是需要提供统一的接口
3.1 实现
既然有统一的接口,那就需要一个基类提供接口,然后节点继承基类,覆写方法,实现多态。
节点之间存在包含的关系,类内关联一个容器,存储其子节点
//提供统一的方式 class Node { public: virtual void add(Node*)=0; virtual void remove(Node*)=0; virtual Node* show(int index)=0; }; class left:public Node { //。。。。 private: vector<Node*> _list; }; class Branch:public Node { //。。。 private: vector<Node*> _list; };
如果基类提供节点所有可能的操作,而一个节点不应该包含某个操作,在编译的时候会报错,因为纯虚函数没有定义。
如果基类只提供各个节点都有的功能,不同类型的节点类自行添加方法,那么也是在编译期报错。