跳转至

设计模式

参考资料

  • 大话设计模式
  • 设计模式: 可复用面向对象软件的基础 TODO
  • 重构: 改善既有代码的设计 TODO

面向对象的设计原则 (SOLID)

单一职责原则 (SRP)

  • 一个类应该只有一个引起它变化的原因
  • 一个模块应该只对一类行为者负责

开闭原则 (OCP)

  • 软件实体 (类, 模块, 函数等等) 应该可以扩展, 但不可修改
  • 如果拓展没有提前预想到, 应该立刻拓展对应抽象

里氏替换原则 (LSP)

  • 子类必须能够替换掉它们的父类, 且不影响程序的正确性
  • 是开闭原则和依赖倒置原则的基础

接口隔离原则 (ISP)

  • 拒绝胖接口, 使用多个专门的接口

依赖倒置原则 (DIP)

  • 高层模块不应该依赖低层模块, 二者都应该依赖抽象
    • 抽象不应该依赖细节, 细节应该依赖抽象
  • 系统中总会有一些违反 DIP 的具体实现
    • 隐藏隔离起来

迪米特法则

  • 又称最少知识原则
  • 如果两个类不直接通信, 则不应当发生相互作用, 如果需要调用另一个类的方法, 则通过第三方间接调用
  • 每个类都应当尽量降低成员的访问权限

合成复用原则

  • 尽量使用对象组合而不是类继承来达到复用的目的

创建型模式

简单工厂模式

  • 不是 GoF \(23\) 种设计模式之一
  • 工厂类: 负责创建产品类的实例
  • 抽象产品类: 定义产品的接口
  • 产品类: 定义具体产品的实现
Product* product = Factory::createProduct(type);
product->use();

工厂方法模式

  • 简单工厂模式违反了开闭原则
  • 定义一个创建对象的接口, 让子类 (具体工厂) 决定实例化哪一个类 (产品), 使得一个类的实例化延迟到其子类
  • 抽象工厂类: 声明创建产品对象的工厂方法
  • 抽象产品类: 定义产品的接口
  • 具体工厂类: 实现工厂方法, 创建具体产品类的实例
  • 具体产品类: 实现抽象产品接口
// 客户端选择具体工厂类
Factory* factoryA = new ConcreteFactoryA();
Product* productA = factoryA->createProduct();
productA->use();

抽象工厂模式

  • 提供一个创建一系列相关或相互依赖对象的接口, 而无需指定它们具体的类
  • 若干个抽象产品类, 每个抽象产品类派生出多个具体产品类
  • 抽象工厂类: 声明创建抽象产品对象的操作接口
  • 具体工厂类: 实现创建一组具体产品对象的操作
  • 不如简单工厂模式 + 依赖注入 + 配置文件
// 客户端选择具体工厂类
AbstractFactory* factory = new ConcreteFactory1();

// 创建一系列相关的产品对象
AbstractProductA* productA = factory->createProductA();
AbstractProductB* productB = factory->createProductB();
productA->use();
productB->use();

// 或者通过配置文件动态选择具体工厂类
AbstractFactory* factory = FactoryProducer::getFactory();
AbstractProductA* productA = factory->createProductA();
AbstractProductB* productB = factory->createProductB();
productA->use();
productB->use();

建造者模式

  • 将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示
  • 用于创建复杂对象, 对象内部构造间的建造顺序是稳定的, 但各个部件的具体实现具有复杂变化
  • 抽象建造者: 声明创建产品各个部件的抽象接口
  • 具体建造者: 实现抽象建造者接口, 构造和装配各个部件
  • 指挥者: 构造一个使用建造者接口的对象, 决定建造的顺序
// 指挥者
Director* director = new Director();

// 具体建造者
Builder* builderA = new ConcreteBuilderA();
Builder* builderB = new ConcreteBuilderB();

// 构造不同的产品
director->construct(builderA);
Product* productA = builderA->getResult();

director->construct(builderB);
Product* productB = builderB->getResult();

原型模式

  • 用原型实例指定创建对象的种类, 并且通过拷贝这些原型创建新的对象 (基于对象)
  • 抽象原型类: 声明一个克隆自身的接口
  • 具体原型类: 实现克隆方法, 返回一个自身的副本
  • 注意深浅拷贝
Prototype* prototype1 = new ConcretePrototype1();
Prototype* clone1 = prototype1->clone();
clone1->setField(field_value);
prototype1->show();
clone1->show();

单例模式

  • 保证一个类仅有一个实例, 并提供一个访问它的全局访问点
  • 饿汉式: 类加载时创建实例, 线程安全, 但不支持延迟加载
  • 懒汉式: 需要时创建实例, 支持延迟加载, 但需要加锁保证线程安全
  • 双重检查锁: 减少加锁开销, 但实现复杂
  • 静态内部类: 利用类加载机制实现延迟加载和线程安全
  • 枚举类: Java 特有, 最简单且线程安全的实现方式

懒汉式单例模式

  • 错误的实现
    • 将构造函数设为私有, 提供一个用于获取实例的静态方法, 一个静态指针指向唯一实例
      • 只适用单线程环境, 通过判断指针是否为空来判断是否需要创建实例, 可能会出现线程安全问题
    • 加一把同步锁, 可以但是效率低, 因为后续实例化的尝试也需要加锁才能判断是否需要创建实例
  • 勉强的实现
    • 加锁,前后判断指针是否为空
  • 最佳实现
    • 静态方法的静态局部变量 (进程生命周期)
    • C++ 11 保证静态局部变量的初始化是线程安全的, 且只会执行一次
    • 会自动调用析构函数, 不会内存泄漏
Singleton::getInstance().doSomething();

结构型模式

适配器模式

  • 将一个类的接口转换成客户希望的另一个接口, 使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
  • 类适配器模式: 通过多重继承实现, 不展开
  • 对象适配器模式
    • 适配器类: 实现目标接口, 内部持有一个需要适配的对象实例, 将客户端的请求转换为适配对象的调用
    • 目标接口: 定义客户端所期望的接口
Target* adapter = new Adapter(new Adaptee());
adapter->request(); // 内部调用适配者的方法

桥接模式

  • 将抽象部分与它的实现部分分离, 使它们都可以独立地变化
  • 把一个事物多维度的变化部分分离出来, 使得各个变化部分都可以独立扩展, 组合起来工作
  • 抽象类: 定义抽象部分的接口, 持有实现部分的引用
  • 具体抽象类: 扩展抽象部分的接口
  • 实现类接口: 定义实现部分的接口
  • 具体实现类: 实现实现部分的接口
// 创建抽象
Abstraction* abstraction = new RefinedAbstraction(new ConcreteImplementor1A(), new ConcreteImplementor2A()); // 注入两个实现部分

abstraction->operation(); // 持有两个实现部分的引用, 调用它们的方法

组合模式

  • 将对象组合成树形结构以表示部分 - 整体的层次结构, 使得用户对单个对象和组合对象的使用具有一致性
  • 组件抽象类: 声明叶子和容器的共同接口
  • 容器类: 复合节点, 实现共同操作, 并实现子组件的相关操作
  • 叶子类: 叶子节点, 实现共同操作
  • 透明与安全
    • 透明: 组件接口声明所有方法, 叶子和容器都实现, 但叶子不需要实现子组件相关方法
    • 安全: 组件接口只声明公共方法, 子组件相关方法只在容器类中声明和实现
// 创建组合结构
Component* root = new Composite();
Component* leaf1 = new Leaf();
Component* leaf2 = new Leaf();
root->add(leaf1);
root->add(leaf2);

leaf1->operation(); // 只影响叶子节点
root->operation(); // 影响整个组合结构

装饰模式

  • 动态地给一个对象添加一些额外的职责, 就增加功能来说, 装饰模式比生成子类更为灵活
  • 组件类: 抽象类, 定义操作的接口
  • 具体组件类: 继承组件类, 实现具体操作
  • 装饰抽象类: 继承组件类, 持有一个组件类的实例, 代理执行具体组件类的方法
  • 具体装饰类: 继承装饰抽象类, 重写具体操作, 在调用父类方法前后添加额外职责
  • 每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中
  • 如果类的某些职责只是特定情况下的行为, 使用装饰模式简化类的实现
Component* finalObj = new DecoratorB(new DecoratorA(new ConcreteComponent()));
finalObj->Operation();

外观模式

  • 为子系统中的一组接口提供一个一致的高层接口, 使得子系统更易使用
  • 适合分层结构
Facade* facade = new Facade();
facade->operation1(options);
facade->operation2(options);
facade->operation3(options);

享元模式

  • 运用共享技术有效地支持大量细粒度的对象
  • 享元工厂: 管理享元对象的创建和共享
  • 抽象享元类: 声明享元对象的接口
  • 具体享元类: 实现享元接口, 并存储内部状态
  • 外部状态: 由客户端维护, 不存储在享元对象中, 每次使用享元对象时传入
FlyweightFactory* factory = new FlyweightFactory();

Flyweight* flyweight1 = factory->getFlyweight("state1");
Flyweight* flyweight2 = factory->getFlyweight("state2");

flyweight1->operation(extrinsic_state1);
flyweight2->operation(extrinsic_state2);

代理模式

  • 为其他对象提供一种代理以控制对这个对象的访问
  • 接口: 声明真实主题和代理主题的共同接口
  • 真实类: 实现接口, 定义了代理所代表的真实对象
  • 代理类: 实现接口, 内部包含对真实主题的引用, 控制对真实主题的访问
  • 用途 (存根 / 装饰)
    • 远程代理: 为一个对象在不同的地址空间提供局部代表
    • 虚拟代理: 针对需要创建开销很大的对象, 延迟其创建和初始化
    • 保护代理: 控制对原始对象的访问, 提供不同级别的使用权限
    • 智能指引: 在访问对象时执行一些附加操作
// 代理负责创建真实主题对象并控制对它的访问
Subject* proxy = new Proxy();
proxy->Request();

// 注入真实主题对象
RealSubject* real_subject = new RealSubject();
Subject* proxy1 = new Proxy(real_subject, option1);
Subject* proxy2 = new Proxy(real_subject, option2);
proxy1->Request();
proxy2->Request();

行为型模式

观察者模式 (发布 - 订阅模式)

  • 定义对象间的一种一对多的依赖关系, 让多个观察者对象同时监听某一个主题对象, 该主题对象在状态发生变化时, 会通知所有观察者对象, 使它们能够自动更新自己
  • 我们不希望为了一致性而让对象之间产生强耦合, 这时可以使用观察者模式
  • 抽象主题类: 声明注册, 注销和通知观察者方法
  • 具体主题类: 实现抽象主题类, 状态发生变化时通知
  • 抽象观察者类: 声明更新接口
  • 具体观察者类: 实现更新接口, 以便在得到主题状态变更通知时更新自身状态
  • 可以结合函数式编程使用回调函数作为观察者
  • 为了解决悬空指针问题, 必然引入循环引用, 需要使用弱引用
// 创建主题和观察者
Subject* subject = new ConcreteSubject();
Observer* observer1 = new ConcreteObserver();
Observer* observer2 = new ConcreteObserver();

// 注册观察者
subject->attach(observer1);
subject->attach(observer2);

// 改变主题状态, 自动通知观察者
subject->setState(new_state); // 内部调用观察者的 update 方法

模版方法模式

  • 定义一个操作中的算法骨架, 将一些步骤延迟到子类中, 使得子类可以不改变算法结构即可重定义该算法的某些特定步骤
  • 父类定义模版方法, 由一系列基本方法组成
    • 一些基本方法在父类中实现
    • 抽象方法由子类实现
    • 钩子方法有默认实现, 子类可选择性重写
// 客户端用父类的引用调用模版方法
AbstractClass* subclass1 = new ConcreteClass1();
AbstractClass* subclass2 = new ConcreteClass2();
subclass1->templateMethod(); // 内部多态调用子类方法
subclass2->templateMethod();

命令模式

  • 将一个请求封装为一个对象, 从而可用不同的请求对客户进行参数化, 对请求排队或记录请求日志, 以及支持可撤销的操作
  • 用于支持命令的撤销操作, 请求排队, 日志记录等
  • 命令接口: 声明执行命令的接口
  • 具体命令类: 实现命令接口, 绑定一个接收者对象, 调用接收者的相关操作来实现命令
  • 接收者类: 知道如何实施与执行一个请求相关的操作
  • 请求者类: 调用命令对象执行请求
Receiver* receiver = new Receiver(); // 创建接收者
Command* command = new ConcreteCommand(receiver); // 创建具体命令并绑定接收者
Invoker* invoker = new Invoker(); // 创建请求者并设置命令
invoker->setCommand(command);
invoker->executeCommand(); // 调用命令的 execute 方法

状态模式

  • 允许一个对象在其内部状态改变时改变它的行为, 对象看起来似乎修改了它的类
  • 如果一个对象的状态非常复杂, 并且依赖于它的状态进行不同的行为, 那么可以使用状态模式将不同状态的行为局部化到不同的类中
  • 抽象状态类: 声明与上下文的一个接口以封装与特定状态相关的行为
  • 具体状态类: 定义与特定状态相关的行为以及状态转换
  • 上下文类: 维护一个具体状态类的实例, 定义客户端感兴趣的接口
Context* context = new Context(new ConcreteStateA());
context->operation1(); // 由当前状态决定具体行为
context->operation2();
context->operation3();

职责链模式

  • 使多个对象都有机会处理请求, 从而避免请求的发送者和接收者之间的耦合关系, 将这些对象连成一条链, 并沿着这条链传递该请求, 直到有一个对象处理它为止
  • 抽象处理者类: 定义处理请求的接口, 以及后继链
  • 具体处理者类: 实现抽象处理者类, 处理它所负责的请求, 如果不能处理则将请求转发给后继者
Handler* handler1 = new ConcreteHandler1();
Handler* handler2 = new ConcreteHandler2();
Handler* handler3 = new ConcreteHandler3();

handler1->setNext(handler2);
handler2->setNext(handler3);

Request* request = new Request(request_type);
handler1->handleRequest(request); // 从链头开始处理请求

解释器模式

  • 给定一个语言, 定义它的文法的一种表示, 并定义一个解释器, 该解释器使用该表示来解释语言中的句子
  • 抽象表达式类: 声明一个抽象的解释方法
  • 终结符表达式类: 实现与文法中的终结符相关的解释操作
  • 非终结符表达式类: 实现与文法中的非终结符相关的解释操作
  • 环境类: 包含解释器之外的一些全局信息
// 构建抽象语法树
AbstractExpression* expression = new NonterminalExpression(
    new TerminalExpression("a"),
    new TerminalExpression("b")
);
Context* context = new Context();
expression->interpret(context); // 解释表达式

中介者模式

  • 用一个中介对象来封装一系列的对象交互, 中介者使各对象不需要显式地相互引用, 从而使其耦合松散, 而且可以独立地改变它们之间的交互
  • 将复杂图形结构的对象互相引用转化为星型结构, 降低类间的耦合度
  • 抽象中介者类: 声明各同事对象通信的接口
  • 具体中介者类: 实现抽象中介者接口, 协调各同事对象之间的交互
  • 同事类: 每个同事类都只知道中介者类, 不知道其他同事类, 通过中介者与其他同事通信
  • 可悲的是, 中介者会变得非常复杂
Mediator* mediator = new ConcreteMediator();
Colleague* colleague1 = new ConcreteColleague1(mediator);
Colleague* colleague2 = new ConcreteColleague2(mediator);

mediator->setColleague1(colleague1);
mediator->setColleague2(colleague2);

colleague1->send(message); // 通过中介者发送消息
colleague2->send(message);

访问者模式

  • 表示一个作用于某对象结构中的各元素的操作, 它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作
  • 将数据结构与作用于数据结构的操作分离
  • 抽象访问者类: 声明一组访问具体元素类的接口
  • 具体访问者类: 实现抽象访问者接口, 定义对每个具体元素类的访问操作
  • 抽象元素类: 声明一个接受访问者对象的接口
  • 对象结构: 由多个元素对象组成的结构, 可以是复合对象
Element* elementA = new ConcreteElementA();
Element* elementB = new ConcreteElementB();

Visitor* visitor = new ConcreteVisitor();
elementA->accept(visitor); // 由具体元素类调用访问者的方法
elementB->accept(visitor);

策略模式

  • 定义了算法家族, 分别封装起来, 让它们之间可以互相替换, 算法的变化不会影响使用算法的客户
  • 抽象策略类: 声明一个公共接口, 所有支持的算法都以此接口进行编程
  • 具体策略类: 实现抽象策略接口的具体算法
  • 上下文类: 维护一个策略类的引用, 用于调用具体算法
    • 现代常注入匿名函数而非类
  • 策略模式封装了变化, 避免使用大量的条件判断语句
  • 也可以结合简单工厂思想使用
    • 直接在上下文类中使用简单工厂思想创建具体算法实例
// 结合简单工厂模式的策略模式
Context* context = new Context(strategy_type);
context->executeStrategy();

// 结合函数式编程的策略模式
Context* context = new Context([](int a, int b) { return a + b; });
context->executeStrategy();

备忘录模式

  • 在不破坏封装性的前提下, 捕获一个对象的内部状态, 并在该对象之外保存这个状态, 以便以后将对象恢复到原先保存的状态
  • 发起人: 负责创建备忘录并存储自身状态, 也负责使用备忘录恢复自身状态
  • 备忘录: 负责存储发起人的内部状态, 并防止除发起人以外的其他对象访问备忘录
  • 管理者: 负责保存好备忘录, 并在需要时提供备忘录给发起人
// 发起人创建备忘录并保存状态
Originator* originator = new Originator();
originator->setState("State1");
Caretaker* caretaker = new Caretaker(originator->createMemento());

// 发起人恢复状态
originator->restoreMemento(caretaker->getMemento());

迭代器模式

  • 提供一种方法顺序访问一个聚合对象中的各个元素, 而又不暴露该对象的内部表示
  • 抽象迭代器类: 定义访问和遍历元素的接口
  • 具体迭代器类: 实现抽象迭代器接口, 维护对聚合对象的引用和当前遍历位置
  • 抽象聚合类: 声明创建迭代器对象的接口
  • 具体聚合类: 实现创建具体迭代器对象的接口, 并存储元素
  • C++ 会选择重载运算符实现迭代器
// 创建聚合对象并添加元素
Aggregate* aggregate = new ConcreteAggregate1();
aggregate->add("Element1");
aggregate->add("Element2");

// 创建迭代器对象
Iterator* iterator = aggregate->createIterator();

// 使用迭代器遍历元素
while (iterator->hasNext()) {
    string element = iterator->next();
    // 处理元素
}