Skip to content

大话设计模式

注:代码未经验证。

UML 类图

UML类图

  1. 聚合(Aggregation)表示一种弱“拥有”关系,体现是A对象可以包含B对象,但B对象不是A对象的一部分。
  2. 组合(Composition)是一种强的“拥有”关系,体现了严格的整体与部分的关系部分和整体的生命周期一样。

简单工厂模式

简单工厂模式 工厂模式只是解决对象创建的问题,而且由于工厂本身包含所有的子类,每次维护或扩展都要改动这个工厂,以致代码重新部署。

策略模式

image.png

策略模式(Strategy):它定义了算法家族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。

策略模式是一种定义一系列算法的方法,从概念上来看,所有算法完成的都是相同的工作,只是实现不同,他们可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。

策略模式与简单工厂结合

cpp
// 简单工厂
Super* super = Factory::create(type);
super->doSomeThing();

// 策略模式与简单工厂结合
Context* context = new Context(type);
context->doSomeThing();

简单工厂模式需要客户端认识两个类:工厂类和抽象类,而策略模式与工厂模式结合的用法,客户端只要认识一个 Context 类,降低了耦合。

策略模式总结

优点:

  1. 策略模式的 Strategy 类层次为 Context 定义了一系列可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
  2. 简化单元测试,每个算法都有自己的类,可以通过自己的接口单独测试。
  3. 当不同的行为对齐在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的 Strategy 类中,可以在使用这些行为的类中消除条件语句。

策略模式就是用来封装算法的,但在实践中,可以用它来封装几乎任何类型的规则,只要需要在不同的时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的 Context 对象,这并没有解除客户端需要选择判断的压力,而策略模式与简单工厂结合后,选择具体实现对象的职责也可由 Context 承担,极大的减轻了客户端的职责。

单一职责原则

单一职责原则(Single Responsibility Rrinciple,SRP):就一个类而言,应该仅有一个引起它变化的原因。

如果一个类承担的职责过多,几等于把这些职责耦合在一起,一个职责的变变化可能会削弱或抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭到意想不到的破坏。

软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。

如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多余一个的职责。

开放-封闭原则

开放-封闭原则(The Open-Closed Principle,OCP):软件实体(类、模块、函数等)应该是可以扩展,但是不可修改。

  1. 对于扩展是开放的, Open for extension;
  2. 对于更改是封闭的, Closed for modification。

无论模块多么“封闭”,都会存在一些无法对之封闭的变化。依然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化的封闭做出选择。他必须先猜测出最有可能发生变化的种类,然后构造抽象来隔离那些变化。

在最初编写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化

面对需求,对程序的改动是通过增加新的代码进行的,而不是更改现有的代码,这就是 开闭原则 的精神所在。

我们希望的是在开发工作开展不久就知道可能发生的变化。查明可能发生的变化锁等待的时间越长,要创建正确的抽象就越困难。

开放-封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术硬所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出频繁变化的那部分做出抽象,然而,对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象一样重要。

里氏代换原则

里氏代换原则(Liskov Substitution Principle,LSP):派生类(子类)对象可以在程序中代替其基类(超类)对象。

一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为也没有变化。

依赖倒置原则

依赖倒置原则(Dependency inversion principle,DIP)

  1. 高层模块不应该依赖低层模块,两个都应该依赖抽象;
  2. 抽象不应该依赖细节,细节应该依赖抽象。

要针对接口编程,不要对实现编程。

image.png 依赖倒置可以说是面向对象设计的标志,用哪些语言来编写程序不重要,如果编写时考虑的手术如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之那就是过程化的设计了。

装饰模式

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

装饰模式

Component 是定义一个对象的接口,可以给这些对象动态地添加职责。ConcreteComponent 是定义了一个具体的对象,也可以给这个对象添加一些职责。Decorator 装饰抽象类,继承了 Component,从外来类扩展 Component 类的功能,但对于 Component 类来说,是无需知道 Decorator 的存在的。至于 ConcreteDecorator 就是具体的装饰对象,起到给 Component 添加职责的功能。

代码实现:

cpp
class Component
{
public:
    virtual void operation() = 0;
}

class ConcreteComponent : public Component
{
public:
    void operation() override
    {
        std::cout << "do something." << std::endl;
    }
}

class Decorator : public Component
{
protected:
    Component* component;  // 被装饰的对象
public:
    void setComponent(Component* component)
    {
        component = component;
    }
    void operation() override
    {
        if(component != nullptr)
        {
            component->operation();
        }
    }
}

class ConcreteDecoratorA : public Decorator
{
private:
    std::string addedState;

public:
    void operation() override
    {
        Decorator::operation();
        addedState = "New State";
        std::cout << "具体装饰对象A的操作" << std::endl;
    }
}

class ConcreteDecoratorB : public Decorator
{
public:
    void operation() override
    {
        Decorator::operation();
        addBehavior();
        std::cout << "具体装饰对象B的操作" << std::endl;
    }
private:
    void addBehavior()
    {
    }
}

int main()
{
    ConcreteComponent* c = new ConcreteComponent();
    ConcreteDecoratorA* d1 = new ConcreteDecoratorA();
    ConcreteDecoratorB* d2 = new ConcreteDecoratorB();

    d1->setComponent(c);
    d2->setComponent(d1);
    d2->operation();

    delete d2;
    delete d1;
    delete c;
    return 0;
}

代理模式

代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。

image.png

Subject 定义了 RealSubject 和 Proxy 的共用接口,这样就可以在任何使用 RealSubject 的地方都可以使用 Proxy。RealSubject 类定义 Proxy 所代表的的真实实体。Proxy 类保存一个 RealSubject 引用使得代理可以访问实体,并提供一个与 Subject 的接口相同的接口,这样代理就可以用来代替实体。

工厂方法模式

简单工厂的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件,动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。

但给工厂添加新的功能需要修改 Case 的 分支条件,违背了开闭原则

工厂方法模式(Factory Method) 定义了一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

image.png

工厂方法模式实现时,客户端需要决定实例化哪一个工厂,选择判断的问题仍然存在,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。

cpp
// 简单工厂模式
ProductA* A1 = SimpleFactory::CreateProduct("A");
A1->doSomeThing();
ProductA* A2 = SimpleFactory::CreateProduct("A");
A2->doSomeThing();
ProductA* A3 = SimpleFactory::CreateProduct("A");
A3->doSomeThing();

// 工厂方法模式
IFactory* factory = new ProductAFactory(); // 修改
ProductA* A1 = factory->CreateProduct();
A1->doSomeThing();
ProductA* A2 = factory->CreateProduct();
A2->doSomeThing();
ProductA* A3 = factory->CreateProduct();
A3->doSomeThing();

产品需要替换时,简单工厂模式客户端修改代码时,需要修改三处,将 "A" 替换为 "B"。工厂方法模式只需要修改一处。

工厂方法模式克服了简单工厂违背开闭原则的缺点,又保持了封装对象创建的过程。缺点是每增加一个类都需要加一个产品的工厂类,增加了额外的开发量。

原型模式

原型模式(Prototype) 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

image.png

原型模式其实就是从一个对象再创建另外一个定制的对象,而且不需要知道任何创建的细节。

cpp
// 原型类
class Prototype
{
private:
    std::string _id;
public:
    Prototype(std::string id) : _id(id) {}
    std::string getId()
    {
        return id;
    }
    virtual Prototype* clone() = 0;
}

// 具体原型类
class ConcretePrototype1 : public Prototype
{
public:
    ConcretePrototype1(std::string id) : Prototype(id) {}
    ConcretePrototype1(const ConcretePrototype1&  lhs) : ConcretePrototype1(lhs.id)
    {
        // 拷贝构造函数(深拷贝),存在指针则必须使用深拷贝
    }
    Prototype* clone() override
    {
        return new ConcretePrototype1(*this);
    }
}

// 客户端代码
int main()
{
    ConcretePrototype1* p1 = new ConcretePrototype1("I");
    ConcretePrototype1* c1 = p1->clone();
    delete c1;
    delete p1;
    return 0;
}

模板方法模式

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

image.png

cpp
class AbstractClass
{
public:
    void TemplateMethod()
    {
        PrimitiveOperation1();
        PrimitiveOperation2();
    }
    virtual void PrimitiveOperation1() = 0;
    virtual void PrimitiveOperation2() = 0;
}

class ConcreteClassA : public AbstractClass
{
public:
    void PrimitiveOperation1() override
    {
        std::cout << "A do Operation1. " << std::endl;
    }
    void PrimitiveOperation1() override
    {
        std::cout << "A do Operation2. " << std::endl;
    }
}
class ConcreteClassB : public AbstractClass
{
public:
    void PrimitiveOperation1() override
    {
        std::cout << "B do Operation1. " << std::endl;
    }
    void PrimitiveOperation1() override
    {
        std::cout << "B do Operation2. " << std::endl;
    }
}

int main()
{
    AbstractClass* a = new ConcreteClassA();
    a->TemplateMethod();
    AbstractClass* b = new ConcreteClassB();
    b->TemplateMethod();

    delete a;
    delete b;
    return 0;
}

模板方法模式是通过把不变的行为搬移到超类,去除子类中的重复代码来体现它的优势。模板方法模式提供了一个很好的代码复用平台。

当不变的和可变的行为在方法的子类实现中混合在一起的时候没不变的行为就会在子类中重复出现。通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复的不变行为的纠缠。

迪米特法则

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

  1. 前提:在类的结构设计上,每一个类都应当尽量降低成员的访问权限。
  2. 根本思想:强调了类之间的松耦合

类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。

外观模式

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

image.png

外观模式的应用场景:

  1. 在设计初期,应该有意识地将不同的两个层分离,层与层之间建立外观 Facade;
  2. 在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加外观 Facade 可以提供一个简单的接口,减少它们的依赖;
  3. 在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,但它包含非常重要的功能,新的开发需求必须要依赖于它。此时可以为新系统开发一个外观 Facade 类,来提供粗糙或高度复杂的遗留代码的比较清晰简答的接口,让新系统与 Facade 对象交互, Facade 与遗留代码交互所有复杂的工作。

建造者模式

建造者模式(Builder) 建造者模式讲一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

image.png

  1. Builder:为创建一个 Product 对象的各个部件指定的抽象接口;
  2. ConcreteBuilder:具体的建造者,实现 Builder 的接口,构造和装配各个部件;
  3. Product:具体的产品;
  4. Director:根据用户需求构建使用 Builder 接口的对象。

建造者模式主要用于常见一些复杂的对象,这些对象内部构件间的建造顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化。

建造者模式的好处是使得建造代码与表示代码分离,由于建造者隐藏了该产品是如何组装的,所以锁需要改变一个产品的内部表示,只需要再定义一个具体的建造者就可以了。

观察者模式

观察者模式(Observer)又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题的对象,这个主题对象在状态发生变化时会通知所有的观察者对象,使他们能够自动的更新自己。

image.png

当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象待改变时,应该考虑观察者模式。

当一个抽象的模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使他们各自独立的改变和复用。

观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象而不是依赖于具体。从而使各自的变化都不会影响另一边的变化。

事件委托实现

委托是一种引用犯法的类型。一旦为委托分配了方法,委托将于该方法具有完全相同的行为。委托方法的使用可以向其他任何方法一样,具有参数和返回值,委托可以看做是对函数的抽象,是函数的“类”,委托的实例将代表一个具体的函数。

委托对象所搭载的所有方法必须具有相同的原型和形式,也就是说拥有相同的参数列表和返回值。

在观察者模式中,可以去掉抽象观察者类,在 Subject 中添加 std::list<std::function<T>> 成员,在 Notify() 中依次执行。

抽象工厂模式

抽象工厂模式(Abstract Factory) 提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的 类。

image.png

抽象工厂模式的优点:

  1. 具体类分离。具体产品类在具体工厂的实现中进行了分离和归类。
  2. 易于更换产品族。当客户想要改变整个产品族时,只需要切换具体工厂即可。
  3. 利于产品一致性。当产品族的各个产品需要在一起执行时,抽象工厂可以确保客户只操作同系列产品,而不会进行跨品牌的组合。

抽象工厂模式的缺点:

不利于添加新种类产品。每加一个新的种类,如多一个项链类型的产品,那每一个具体工厂都要进行代码的扩展,且破坏了原先规定的结构,违反了开闭原则。

状态模式

状态模式(State),当一个对象的内在状态发生改变时允许改变其行为,这个对象看起来像是改变了其类。

状态模式主要解决的谁当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。

image.png

状态模式的好处是将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。

状态模式将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在与某个 ConcreteState 中,所以通过定义新的子类几合一很容易的增加新的状态和转换。

状态模式通过把各种状态转移逻辑分布到 State 的子类之间,来减少相互间的依赖。

当一个对象的行为取决于它的状态,并且他必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式了。

适配器模式

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

系统的数据和行为都正确,但接口不符时,我们应该考虑使用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。

image.png

备忘录模式

备忘录模式(Memento) 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。

image.png

备忘录模式将要保存的细节封装在 Memento 中,更改保存的细节也不会影响到客户端。

如果在某个系统中使用命令模式时,需要实现命令的撤销功能,那么命令模式可以使用备忘录模式来存储可撤销操作的状态。

组合模式

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

image.png

  1. 透明方式:叶节点和枝节点对于外界来说没有区别,都实现了 Add()Remove() ,它们具有完全一致的行为接口。但问题也很明显, 叶节点本身不具有 Add()Remove() 功能,这些方法的实现是没有意义的。
  2. 安全方式:在 Component 接口中不去声明 Add()Remove() 方法,这样的方法不够透明,叶节点和枝节点不具有相同的接口,客户端调用需要做响应的判断。

需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,可以考虑用组合模式。

组合模式定义了包含基本对象和组合对象的类层次结构。基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中,任何用到基本对象的地方都可以使用组合对象了。

组合模式让客户可以一致的使用组合结构和单个对象。

迭代器模式

迭代器模式(Iterator) 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

  1. 当需要访问一个聚集对象,而且不管这个聚集对象是什么都需要遍历的时候,应该考虑迭代器模式;
  2. 当需要对聚集有多种遍历方式时,可以考虑用迭代器;

image.png

抽象迭代器类:

cpp
template <typename T>
class Iterator
{
public:
    virtual void First() = 0;
    virtual void next() = 0;
    virtual bool IsDone() = 0;
    virtual T CurrentItem() const = 0;
protected:
    Iterator();
}

抽象聚集类:

cpp
template <typename T>
class Aggregate
{
public:
    virtual Iterator<T>* CreateIterator() const = 0;
    virtual long Count() = 0;
    virtual T& Get(long index) = 0;
}

另一个办法是定义一个一般的 mixin 类 Traversable,它定义了一个用于创建迭代器的接口,聚合类通过混入(继承)Traversable 来支持多态迭代。

具体的迭代器类

cpp
template <typename T>
class ConcreteIterator : public Iterator<T>
{
public:
    ConcreteItorator(const Aggregate<T>* aggregate)
        : _aggregate(aggregate), _current(0) {}
    void First() override
    {
        _current = 0;
    }
    void Next() override
    {
        _current++;
    }
    bool IsDone() override const
    {
        return _current >= _aggregate->Count();
    }
    T CurrentItem() override const
    {
        if(IsDone())
        {
            throw IteratorOutOfBounds;
        }
        return _aggregate->Get(_current);
    }
private:
    const Aggregate<T>* _aggregate;
    long _current;
}

具体的聚集类:

cpp
template<typename T>
class ConcreteAggregate : public Aggregate<T>
{
public:
    Iterator<T>* CreateIterator() override const
    {
        return new ConcreteItorator<T>(this);
    }
    // 其他接口 ...
}

注意: CreateIterator 返回的是一个动态分配的迭代器对象,使用完成后必须删除这个迭代器,否则会造成内存泄漏。

为了方便客户,可以提供一个 IteratorPtr 作为迭代器的代理,IteratorPtr 在栈上分配,保证在 Iterator 对象离开其作用域时清除它。

cpp
template<typename T>
class IteratorPtr
{
public:
    IteratorPtr(Iterator<T>* i) : _i(i) { }
    ~IteratorPtr() { delete _i; }

    Iterator<T>* operator->() {return _i;}
    Iterator<T>& operator*() {return *_i;}
    IteratorPtr(const IteratorPtr&) = delete;
    IteratorPtr& operator=(IteratorPtr&) = delete;
    
// private:
//    // 为避免重复的 delete 操作不允许拷贝和赋值
//    IteratorPtr(const IteratorPtr&) { }
//    IteratorPtr& operator=(IteratorPtr&) { }
private:
    Iterator<T>* _i;
}

将成员函数设为私有(private)是“旧”的做法。构造函数仍然存在,但它是私有(private)的,只能从另一个成员函数中调用。

delete 删除 构造函数。它不是编译器生成的,根本就不存在。(需要注意的是并非所有编译器都支持这种语法,所以可移植性是一个问题。)

单例模式

单例模式(Singleton) 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

image.png

Meyers' Singleton:

cpp
class Singleton
{
public:
    static Singleton& getInstance() // 全局访问点
    {
        static Singleton inst;
        return inst;
    }
    // 不允许拷贝和赋值
    Singleton(const Singleton&) = delete; 
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() {}
}
  1. 单线程下,正确。
  2. C++11 及以后的版本(如 C++14)的多线程下,正确。
  3. C++11 之前的多线程下,不一定正确。 原因在于在 C++11 之前的标准中并没有规定 local static 变量的内存模型,所以很多编译器在实现 local static 变量的时候仅仅是进行了一次 check,但是在C++11却是线程安全的,这是因为新的C++标准规定了当一个线程正在初始化一个变量的时候,其他线程必须得等到该初始化完成以后才能访问它。

不过有些编译器在 C++11之前的版本就支持这种模型,例如 g++,从 g++4.0开始,meyer's singleton 就是线程安全的,不需要 C++11。其他的编译器就需要具体的去查相关的官方手册了。

桥接模式

对象的继承关系是在编译时就定义好了,所以无法运行时改变从父类继承的实现。子类的实现与他的父类有非常紧密的依赖关系,以至于父类实现中的任何变化都会导致子类发生变化。当需要复用子类时,如果继承下来的实现不适合解决新的问题,则父类必须被重写或被其他适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

合成/聚合复用原则(CARP) 尽量使用合成/聚合,尽量不要使用类的继承。

image.png

合成/聚合复用原则的好处是,优先使用对象的合成/聚合将有助于你保持每个类被封装,并集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。

桥接模式(Bridge) 将抽象部分与它的实现分离,使他们都可以独立的变化。

将抽象部分与它的实现分离,这并不是说,让抽象类与其派生类分离,因为这样没有任何意义。实现指的是抽象类和它的派生类用来实现自己的对象。

也就是说,实现系统可能有多种角度分类,每一种分类都有可能变化,那么久把这种多角度分离出来让他们独立的变化,减少他们之间的耦合。

image.png

命令模式

命令模式(Command) 将一个请求封装为一个对象,从而使你可以用不同的请求对客户端进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

image.png

Command 类:

cpp
class Command
{
public:
    virtual void Execute() = 0;
}

ConcreteCommand 类,将一个接受者对象绑定与一个动作,调用接受者相应的操作,以实现Execute。

cpp
class ConcreteCommand : public Command
{
public:
    ConcreteCommand(Receiver* receiver) : _receiver(receiver) { }
    void Execute() override 
    {
        _receiver->Action();
    }
private:
    Receiver* _receiver;
}

Invoker 类,负责管理命令子序列,提供添加和删除子命令的操作。

cpp
class Invoker
{
public:
    void Add(Command* c)
    {
        _cmds->push(c);
    }
    void Remove(Command* c)
    {
        _cmds->remove(c);
    }
    void ExecuteCmds() override 
    {
        for(auto & c: cmds)
        {
            c->Exectue();
        }
    }
private:
    std::list<Command*> _cmds;
}

Receiver 类,知道如何实施与一个请求相关的操作,任何类都能成为一个接受者。

cpp
class Receiver
{
public:
    void Action();
}

优点:

  1. 较容易的设计一个命令队列;
  2. 在需要的情况下,可以较容易的将命令计入日志;
  3. 允许接收请求的一方否决请求;
  4. 可以容易的实现撤销和重做;
  5. 增加新的具体命令较为容易。

敏捷开发的原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。、

责任链模式

责任链模式(Chain of Responsibility) 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。

image.png

cpp
#include <iostream>

// 抽象处理者
class Handler {
public:
    virtual ~Handler() {}
    virtual void handleRequest(int request) = 0;
    void setSuccessor(Handler* successor) {
        successor_ = successor;
    }

protected:
    Handler* successor_;
};

// 具体处理者A
class ConcreteHandlerA : public Handler {
public:
    void handleRequest(int request) override {
        if (request >= 0 && request < 10) {
            std::cout << "ConcreteHandlerA: handling request " << request << std::endl;
        } else if (successor_ != nullptr) {
            successor_->handleRequest(request);
        }
    }
};

// 具体处理者B
class ConcreteHandlerB : public Handler {
public:
    void handleRequest(int request) override {
        if (request >= 10 && request < 20) {
            std::cout << "ConcreteHandlerB: handling request " << request << std::endl;
        } else if (successor_ != nullptr) {
            successor_->handleRequest(request);
        }
    }
};

// 具体处理者C
class ConcreteHandlerC : public Handler {
public:
    void handleRequest(int request) override {
        if (request >= 20 && request < 30) {
            std::cout << "ConcreteHandlerC: handling request " << request << std::endl;
        } else if (successor_ != nullptr) {
            successor_->handleRequest(request);
        }
    }
};

int main() {
    Handler* handler1 = new ConcreteHandlerA();
    Handler* handler2 = new ConcreteHandlerB();
    Handler* handler3 = new ConcreteHandlerC();
    handler1->setSuccessor(handler2);
    handler2->setSuccessor(handler3);

    handler1->handleRequest(5);
    handler1->handleRequest(15);
    handler1->handleRequest(25);

    delete handler1;
    delete handler2;
    delete handler3;
    return 0;
}

中介者模式

尽管将一个系统分割成许多对象通常可以增加其复用性,但是对象间相互连接的激增又会降低其可复用性。大量的连接使得一个对象不可能在没有其他对象的支持下工作,系统表现为一个不可分割的整体,所以,对系统的行为进行任何较大的改动就十分困难了。

中介者模式(Mediator) 用一个中介对象来封装一系列对象的交互。中介者使各对象不需要显示地相互引用,从而时期耦合松散,而且可以独立的改变他们之间的交互。

image.png

Mediator 类,抽象中介者类:

cpp
class Mediator
{
public:
    virtual void send(const std::string& msg, Colleague* colleague) = 0;
}

Colleague 类 抽象同事类:

cpp
class Colleague
{
public:
    Colleague(Mediator* mediator) : _mediator(mediator) { }

protected:
    Mediator* _mediator;
}

ConcreteColleague 类,具体同事类:

cpp
class ConcreteColleague1 : public Colleague
{
public:
    ConcreteColleaguee(Mediator* mediator) : Colleague(mediator) { }

    void send(const std::string& msg)
    {
        _mediator->send(msg, this);
    }
    
    void notify(const std::string& msg)
    {
        std::cout << "ConcreteColleague1 recv: " << msg <<std::endl;
    }
}

class ConcreteColleague2 : public Colleague
{
public:
    ConcreteColleaguee(Mediator* mediator) : Colleague(mediator) { }

    void send(const std::string& msg)
    {
        _mediator->send(msg, this);
    }
    
    void notify(const std::string& msg)
    {
        std::cout << "ConcreteColleague2 recv: " << msg <<std::endl;
    }
}

ConcreteMediator 类,具体中介者类:

cpp
class ConcreteMediator : public Mediator
{
public:
    ConcreteMediator(Colleague* colleague1, Colleague* colleague2)
        : _pColleague1(colleague1), _pColleague2(colleague2) { }
    
    void send(const std::string& msg, Colleague* colleague)
    {
        if(colleague == _pColleague1)
        {
            _pColleague2->notify(msg);
        }
        else
        {
            _pColleague1->notify(msg);
        }
    }
private:
    ConcreteColleague1* _pColleague1; 
    ConcreteColleague2* _pColleague2; 
}

享元模式

享元模式(Flyweight) 运用共享技术有效地支持大量细粒度的对象。

image.png

cpp
#include <iostream>
#include <unordered_map>

// 抽象享元类
class Flyweight {
public:
    virtual void operation(int extrinsicState) = 0;
};

// 具体享元类
class ConcreteFlyweight : public Flyweight {
public:
    void operation(int extrinsicState) override {
        std::cout << "具体享元对象,外部状态:" << extrinsicState << std::endl;
    }
};

// 享元工厂类
class FlyweightFactory {
private:
    std::unordered_map<char, Flyweight*> flyweights;

public:
    Flyweight* getFlyweight(char key) {
        if (flyweights.find(key) == flyweights.end()) {
            flyweights[key] = new ConcreteFlyweight();
        }
        return flyweights[key];
    }
};

int main() {
    FlyweightFactory factory;

    // 获取或创建享元对象
    Flyweight* fw1 = factory.getFlyweight('A');
    fw1->operation(1);

    // 再次获取相同的享元对象
    Flyweight* fw2 = factory.getFlyweight('A');
    fw2->operation(2);

    // 获取或创建另一个享元对象
    Flyweight* fw3 = factory.getFlyweight('B');
    fw3->operation(3);

   return 0;
}

享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类的实例来表示数据。如果发现这个实例除了几个参数外基本上都是相同的,有时就能够很大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将他们传递进来,就可以通过共享大幅度减少单个实例的数目。

  1. 如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;
  2. 对象的大多数状态是可以是外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑享元模式。

解释器模式

解释器模式(Interpreter) 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题中的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

image.png

cpp
#include <iostream>
#include <string>
#include <unordered_map>

// 抽象表达式类
class Expression {
public:
    virtual bool interpret(const std::unordered_map<std::string, bool>& context) = 0;
};

// 终结符表达式类
class TerminalExpression : public Expression {
private:
    std::string variable;

public:
    TerminalExpression(const std::string& variable) : variable(variable) {}

    bool interpret(const std::unordered_map<std::string, bool>& context) override {
        return context.at(variable);
    }
};

// 非终结符表达式类
class AndExpression : public Expression {
private:
    Expression* expression1;
    Expression* expression2;

public:
    AndExpression(Expression* expression1, Expression* expression2)
        : expression1(expression1), expression2(expression2) {}

    bool interpret(const std::unordered_map<std::string, bool>& context) override {
        return expression1->interpret(context) && expression2->interpret(context);
    }
};

// 非终结符表达式类
class OrExpression : public Expression {
private:
    Expression* expression1;
    Expression* expression2;

public:
    OrExpression(Expression* expression1, Expression* expression2)
        : expression1(expression1), expression2(expression2) {}

    bool interpret(const std::unordered_map<std::string, bool>& context) override {
        return expression1->interpret(context) || expression2->interpret(context);
    }
};

// 客户端代码
int main() {
    // 构建上下文环境,变量名和对应的布尔值
    std::unordered_map<std::string, bool> context = {{"A", true}, {"B", false}, {"C", true}};

    // 构建解释器表达式树
    Expression* expression1 = new TerminalExpression("A");
    Expression* expression2 = new TerminalExpression("B");
    Expression* expression3 = new TerminalExpression("C");

    Expression* andExpression = new AndExpression(expression1, expression2);
    Expression* orExpression = new OrExpression(andExpression, expression3);

    // 解释并输出结果
    bool result = orExpression->interpret(context);
    std::cout << "解释器模式结果:" << result << std::endl;

    // 释放资源
    delete orExpression;
    delete andExpression;
    delete expression3;
    delete expression2;
    delete expression1;

   return 0;
}

访问者模式

访问者模式(Visitor) 表示一个作用于某个对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

image.png

cpp
#include <iostream>
#include <vector>

// 前向声明
class ConcreteElementA;
class ConcreteElementB;

// 抽象访问者类
class Visitor {
public:
    virtual void visit(ConcreteElementA* element) = 0;
    virtual void visit(ConcreteElementB* element) = 0;
};

// 具体访问者类
class ConcreteVisitor : public Visitor {
public:
    void visit(ConcreteElementA* element) override {
        std::cout << "具体访问者访问元素 A" << std::endl;
        // 对元素 A 进行操作
    }

    void visit(ConcreteElementB* element) override {
        std::cout << "具体访问者访问元素 B" << std::endl;
        // 对元素 B 进行操作
    }
};

// 抽象元素类
class Element {
public:
    virtual void accept(Visitor* visitor) = 0;
};

// 具体元素类 A
class ConcreteElementA : public Element {
public:
    void accept(Visitor* visitor) override {
        visitor->visit(this);
    }

    // 元素 A 特有的操作函数
    void operationA() {
        std::cout << "执行元素 A 的操作" << std::endl;
    }
};

// 具体元素类 B
class ConcreteElementB : public Element {
public:
    void accept(Visitor* visitor) override {
        visitor->visit(this);
    }

     // 元素 B 特有的操作函数
     void operationB() {
         std::cout << "执行元素 B 的操作" << std::endl;
     }
};

// 对象结构类
class ObjectStructure {
private:
    std::vector<Element*> elements;

public:
    void addElement(Element* element) {
        elements.push_back(element);
    }

    void removeElement(Element* element) {
        // 从 elements 中移除指定元素
    }

    void accept(Visitor* visitor) {
        for (auto element : elements) {
            element->accept(visitor);
        }
    }
};

// 客户端代码
int main() {
    ObjectStructure objectStructure;

    ConcreteElementA elementA;
    ConcreteElementB elementB;

    objectStructure.addElement(&elementA);
    objectStructure.addElement(&elementB);

    ConcreteVisitor visitor;
    
    objectStructure.accept(&visitor);

   return 0;
}
  1. 优点

    访问者模式适用于数据结构相对稳定的系统,它把数据结构和作用于结构之上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。

    访问者模式的目的就是要把处理从数据结构分离出来。有比较稳定的数据结构,又有又有易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算法操作的增加变得容易。

    访问者模式的优点是增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。

  2. 缺点

    访问者模式的缺点是使增加新的数据结构变得困难了。

设计模式总结

创造型模式

  1. 抽象工厂模式:提供一个创建一系列或相关依赖对象的接口,而无需指定他们具体的类;
  2. 建造者模式:将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示;
  3. 工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂模式使得一个类的实例化延迟到其子类;
  4. 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象;
  5. 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

创作者模式隐藏了这些类的实例是如何被创建和被放在一起,整个系统关于这些对象所知道的是由抽象类所定义的接口。这样,创作型模式在创建了什么、谁创建它、它是怎么被创建的、以及何时被创建这些方面提供了很大的灵活性。

结构型模式

  1. 适配器模式:将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作;
  2. 桥接模式:将抽象部分与它的实现分离,使它们都可以独立的变化;
  3. 组合模式:将对象组合成树形结构以表示“整体-部分”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性;
  4. 装饰模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类更加灵活;
  5. 外观模式:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用;
  6. 享元模式:运用共享技术有效地支持大量细粒度额定对象;
  7. 代理模式:为其他对象提供一种代理以控制对这个对象的访问;

行为型模式

  1. 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它对象都得到通知并自动更新;
  2. 模板方法模式:定义一个操作的算法骨架,而将一些步骤延迟到子类,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤;
  3. 命令模式:将一个请求封装为一个对象,从而使你可以用不同的请求对客户端进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作;
  4. 状态模式:允许一个对象在其内部状态改变时改变它的行为,让对象看起来似乎修改了它的类;
  5. 责任链模式:使得多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止;
  6. 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子;
  7. 中介者模式:用一个中介对象来封装一系列对象的交互,中介者使得各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互;
  8. 访问者模式:表示一个作用于某对象结构中的个元素的操作,访问者使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作;
  9. 策略模式:定义一系列的算法,把它们一个个地封装起来,并且使他们可以相互替换,策略模式使得算法可以独立于它的客户而变化;
  10. 备忘录模式:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态;
  11. 迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对对象的内部表示。

基于 VitePress 构建