结构型模式

这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

适配器模式

适配器的作用为:将一个类的接口转换成客户希望的另外一个接口,适配器使得原本由于接口不兼容而不能一起工作的那些类可以一起工作

适配器主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。其分为两种:类适配器和对象适配器模式:

  • 类适配器使用 继承 来适配接口

  • 对象适配器使用 组合 来适配接口

例如:

class Voltage{
   // 中国的通用电压是 220V
   size_t voltage = 220;
public:
   size_t output(){
      return this->voltage;
   }
};

bool PhoneCharging(size_t voltage){
   // 手机充电需要的电压一般为 5V
   static size_t phone_voltage = 5;
   if( voltage == phone_voltage) return true;
   return false;
}


class ClassAdapter: Voltage{
public:
   size_t adapter(){
      size_t voltage = Voltage::output()/44; // 对通用电压进行转换
      return voltage;
   }
};

class ObjectAdapter{
   Voltage vol;
public:
   size_t adapter(){
      return vol.output()/44;
   }
};

// 使用
ClassAdapter classAdapter;
ObjectAdapter objectAdapter;
cout<<boolalpha
   <<"使用类适配器能否给手机充电?"<<PhoneCharging(classAdapter.adapter())
   <<endl
   <<"使用对象适配器能否给手机充电?"<<PhoneCharging(objectAdapter.adapter());

备注

一般而言,我们更加推荐使用 组合 而不是 继承 尤其是 公有继承 。公有继承代表了 is-a 关系,这意味着任何适用于父类对象的方法都适用于子类对象

桥接模式

桥街模式准确的定义为:将抽象部分与它的实现分离,使它们都可以独立地变化

通俗地来说,就是类继承之间只进行接口的继承,而具体实现则放到另外一个类中:

@startuml

class Person{
   + eat()
}

class Child{
   - implChild
   + eat()
}

Person <|.. Child
Child <.. implChild

@enduml

由于实现的方式有多种,桥接模式将实现与接口进行分离,那么接口和实现就可以实现独自变化,进而降低了系统之间的耦合性。这种 impl 手法在 C++ 中还可以用来改进编译速度,提升程序效率。1

过滤器模式

组合模式

组合模式的定义为:将对象组合成树形结构以表示“部分 - 整体”的层次结构,组合模式使得用户对单个对象的组合的使用具有一致性

通过将对象组合到一颗树上,你可以使用根节点来操纵整个树上的元素。比如对整个树上的元素运行一次 display() 函数。

更近一步地,由于根节点、叶节点所执行的职责略有不同,如果整个树上的元素都具有相同的接口,那么称其为 透明模式,其优点是你可以用一套方法控制整个树,如果根节点、叶节点仅根据职责实现接口,则称其为 安全方式

例如:

class Node{
public:
   virtual const char* name() = 0;
   virtual Node * add(Node*) = 0;
   virtual void display() = 0;
};

class Root: public Node{
   list<Node*> children;
   const char* _name;
public:
   explicit Root(const char* name) : _name(name){}
   const char * name() override{
      return _name;
   }
   Node * add(Node* child) override{
      children.push_back(child);
      return child;
   }
   void display() override{
      cout<<_name<<endl;
      for(auto child : children){
            cout<<'-';
            child->display();
      }
   }
};

class Leaf: public Node{
   const char* _name;
public:
   explicit Leaf(const char* name): _name(name){}
   const char * name() override{
      return _name;
   }
   Node * add(Node *) override{
      return nullptr;
   } // 叶节点没有孩子
   void display() override{
      cout<<"-"<<_name<<endl;
   }
};

这里由于叶节点也实现了不需要的接口,因此这里使用了 透明模式。所谓透明,就是指用户看起来他们就像是相同的,用户感知不到他们之间的差别。

// 使用
Root root("root");
auto first = root.add(new Root("first"));
root.add(new Root("second"));
first->add(new Leaf("first_leaf"));
first->add(new Leaf("first_leaf2"));
root.display(); // 通过根节点操纵所有孩子

结果:

root
-first
--first_leaf
--first_leaf2
-second

装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

装饰器动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。其用法类似与组合,但是相比组合,导出的接口更少。

例如:2

class Pancake{
public:
   virtual const string description() = 0;
   virtual int cost() = 0;
};

class MeatPancake : public Pancake{
   const string name = "肉煎饼";
public:
   const string description() override{
      return name;
   }
   int cost() override{
      return 1;
   }
};

class PancakeDecorator: public Pancake{
   Pancake* decorated;
public:
   explicit PancakeDecorator(Pancake* cake): decorated(cake){}
   const string description() override{
      return decorated->description();
   }
   int cost() override{
      return decorated->cost();
   }
   virtual ~PancakeDecorator() = 0{

   }
};

class Egg: public PancakeDecorator{
public:
   explicit Egg(Pancake* cake): PancakeDecorator(cake){}
   const string description() override{
      return PancakeDecorator::description() + "加蛋";
   }
   int cost() override{
      return PancakeDecorator::cost() + 1.5;
   }
};

class Potato: public PancakeDecorator{
public:
   explicit Potato(Pancake* cake): PancakeDecorator(cake){}
   const string description() override{
      return PancakeDecorator::description() + "加土豆";
   }
   int cost() override{
      return PancakeDecorator::cost() + 2;
   }
};
//
Pancake *cake = new MeatPancake;
cake = new Egg(cake);
cake = new Potato(cake);
cout << cake->description() << endl
   << cake->cost();

你可能会发现,实际上这是一种“横向类型转换”,为了保证代码的正确性,所有被访问的接口必须是虚有的,因此通过装饰器的基类来限定可访问的接口,并做为子类做一些初始化工作。

外观模式

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

class SubSystemOne {
public:
   void MethodOne() {
      cout << "MethodOne" << endl;
   }
};
class SubSystemTwo {
public:
   void MethodTwo() {
      cout << "SubSystemTwo" << endl;
   }
};

class Facade {
   SubSystemOne one;
   SubSystemTwo two;

public:
   void MethodA() {
      one.MethodOne();
   }
   void MethodTwo() {
      two.MethodTwo();
   }
};
// 使用
Facade facade;
facade.MethodA();
facade.MethodTwo();

享元模式

享元模式用于创建大量重复的对象,比如围棋棋子,通过区分其常用状态(内部状态)和不常用状态(外部状态)将其进行划分。

typedef pair<size_t, size_t> Coords;
enum class Color {
   black, white
};

class Go {
   Color color;
   vector<Coords> coords;

public:
   Go(Color color): color(color){}
   void setCoords(Coords coord) {
      coords.push_back(coord);
   }
};

class GoFactory {
   unordered_map<Color, Go> gos;
public:
   Go getGo(Color color) {
      if (gos.find(color) == gos.end()) {
            Go go(color);
            gos.insert(pair<Color, Go>(color, go));
      }
      return gos.at(color);
   }
};
// 使用
GoFactory factory;
auto white = factory.getGo(Color::white);
white.setCoords(Coords(10,20));
white.setCoords(Coords(10,25));
white.setCoords(Coords(10, 30));
auto black = factory.getGo(Color::black);
black.setCoords(Coords(5,15));
black.setCoords(Coords(5,25));
black.setCoords(Coords(5,35));

代理模式

代理模式用于为其他对象提供一种代理以控制这个对象的访问。主要用途有:

  • 远程代理:为远程对象提供本地代理,以提供在本地访问的方法

  • 虚拟代理:根据需要创建开销很大的对象,通过代理储存真实对象

  • 安全代理:用于控制访问真实对象时的权限

  • 智能引用:比如智能指针

1

Effective C++ 第三版 条款31:将文件间的编译依赖依存关系降到最低

2

装饰器模式(Decorator) C++ - luStar - 博客园