文章

设计模式高频总结二

解释策略模式和状态模式之间的区别,以及在什么情况下使用它们

策略模式和状态模式的概念


策略模式(Strategy Pattern)

定义:策略模式是一种行为型设计模式,它定义了一系列算法,并将每种算法封装起来,使它们可以互相替换,从而使得算法的变化不会影响使用算法的客户端。

核心思想

  • 将具体的行为实现(算法)抽象化,客户端可以在运行时动态选择行为。
  • 强调 行为的可替换性

适用场景

  1. 系统中有多种算法可以实现相同的功能。
  2. 希望客户端在运行时选择不同的算法。
  3. 避免在上下文类中写多个 if-elseswitch 语句。

状态模式(State Pattern)

定义:状态模式是一种行为型设计模式,它允许一个对象在其内部状态发生改变时,改变其行为。看起来就像是对象的类发生了变化一样。

核心思想

  • 将状态与行为绑定,每种状态封装不同的行为。
  • 强调 对象行为的变化,而非行为的选择。

适用场景

  1. 对象的行为依赖于其状态,并且可以根据状态改变行为。
  2. 需要避免大量的 if-elseswitch 判断逻辑来管理状态切换。
  3. 状态的切换逻辑复杂且需要可扩展性。

策略模式 vs 状态模式

特性策略模式状态模式
目的让算法(行为)可以互相替换根据对象的内部状态动态改变其行为
关注点解决多个算法的选择问题解决对象在不同状态下的行为变化问题
模式结构通常由上下文类和一组策略类组成通常由上下文类、多个状态类及状态转移逻辑组成
客户端控制客户端可以动态选择策略状态的变化由上下文或状态类自行控制
可替换性策略可以互相替换,不会影响上下文状态之间通常不可替换,强调行为随状态变化
扩展性添加新策略很方便添加新状态需要在上下文类或状态类中添加逻辑

实现对比

1. 策略模式示例

场景:实现一个支付系统,支持不同的支付方式(如信用卡、支付宝、微信支付)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <memory>

// 策略接口
class PaymentStrategy {
public:
    virtual void pay(int amount) = 0;
    virtual ~PaymentStrategy() {}
};

// 具体策略:信用卡支付
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " using Credit Card." << std::endl;
    }
};

// 具体策略:支付宝支付
class AlipayPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " using Alipay." << std::endl;
    }
};

// 具体策略:微信支付
class WeChatPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " using WeChat." << std::endl;
    }
};

// 上下文类
class PaymentContext {
    std::unique_ptr<PaymentStrategy> strategy;
public:
    void setStrategy(std::unique_ptr<PaymentStrategy> s) {
        strategy = std::move(s);
    }

    void executePayment(int amount) {
        if (strategy) {
            strategy->pay(amount);
        } else {
            std::cout << "No payment strategy set!" << std::endl;
        }
    }
};

int main() {
    PaymentContext context;

    context.setStrategy(std::make_unique<CreditCardPayment>());
    context.executePayment(100); // 输出:Paid 100 using Credit Card.

    context.setStrategy(std::make_unique<AlipayPayment>());
    context.executePayment(200); // 输出:Paid 200 using Alipay.

    return 0;
}

分析

  • 支付方式(算法)可以随时切换。
  • 每种支付方式是独立的,互不依赖,扩展新支付方式很简单。

2. 状态模式示例

场景:实现一个简单的文档编辑系统,文档有三种状态(草稿、审核中、发布),每种状态下行为不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <memory>

// 状态接口
class State {
public:
    virtual void handleRequest() = 0;
    virtual ~State() {}
};

// 具体状态:草稿状态
class DraftState : public State {
public:
    void handleRequest() override {
        std::cout << "Document is in Draft state." << std::endl;
    }
};

// 具体状态:审核状态
class ReviewState : public State {
public:
    void handleRequest() override {
        std::cout << "Document is in Review state." << std::endl;
    }
};

// 具体状态:发布状态
class PublishedState : public State {
public:
    void handleRequest() override {
        std::cout << "Document is Published." << std::endl;
    }
};

// 上下文类
class Document {
    std::unique_ptr<State> state;
public:
    void setState(std::unique_ptr<State> s) {
        state = std::move(s);
    }

    void process() {
        if (state) {
            state->handleRequest();
        } else {
            std::cout << "No state set!" << std::endl;
        }
    }
};

int main() {
    Document doc;

    doc.setState(std::make_unique<DraftState>());
    doc.process(); // 输出:Document is in Draft state.

    doc.setState(std::make_unique<ReviewState>());
    doc.process(); // 输出:Document is in Review state.

    doc.setState(std::make_unique<PublishedState>());
    doc.process(); // 输出:Document is Published.

    return 0;
}

分析

  • 每种状态都有独立的行为逻辑。
  • 状态切换由上下文类(Document)控制。

使用策略模式的场景

  1. 系统中有多个算法需要动态选择。
  2. 客户端需要透明地使用不同算法。
  3. 需要避免使用大量的 if-elseswitch-case

例如

  • 支付系统支持不同支付方式。
  • 不同的排序算法(快速排序、归并排序、冒泡排序)。

使用状态模式的场景

  1. 对象需要根据内部状态动态改变行为。
  2. 状态切换复杂且有逻辑关系。
  3. 需要避免 if-else 来管理状态。

例如

  • 文档编辑系统中,不同状态对应不同操作(草稿、审核、发布)。
  • TCP 连接的不同状态(连接中、已连接、断开连接)。

总结

  • 策略模式:适用于算法或行为可以互相替换且独立的场景。
  • 状态模式:适用于对象行为依赖于内部状态变化的场景。

两者的关键区别在于:

  • 策略模式强调算法替换
  • 状态模式强调对象状态驱动的行为变化

什么是迭代器模式和组合模式?它们可以一起使用吗?

迭代器模式和组合模式的概念


迭代器模式(Iterator Pattern)

定义: 迭代器模式是一种行为型设计模式,它提供了一种方法顺序访问聚合对象中的各个元素,而无需暴露其内部实现。

核心思想

  • 将遍历行为与聚合对象分离。
  • 提供一个统一的接口,让不同的聚合结构都可以被遍历。

适用场景

  1. 需要遍历复杂的数据结构,但不希望暴露数据结构的内部细节。
  2. 支持多种遍历方式(如前序、中序、后序遍历)。

组合模式(Composite Pattern)

定义: 组合模式是一种结构型设计模式,它将对象组合成树形结构以表示“部分-整体”的层次结构,使客户端可以以一致的方式处理单个对象和对象组合。

核心思想

  • 将叶子节点和容器节点统一看作是组件。
  • 允许客户端忽略组合对象与单个对象的区别,统一处理它们。

适用场景

  1. 希望客户端可以透明地操作单个对象和组合对象。
  2. 对象的结构呈现树形关系,例如文件系统、公司组织架构。

迭代器模式和组合模式的区别

特性迭代器模式组合模式
目的顺序访问聚合对象中的元素,不暴露其内部结构。以树形结构表示对象的部分-整体关系,统一处理单个对象和组合对象。
关注点数据结构的遍历行为结构化组织和管理对象的层次关系
结构通常包括一个聚合类和一个迭代器类包括组件接口、叶子类和组合类

迭代器模式和组合模式可以一起使用吗?

是的,迭代器模式和组合模式可以一起使用。 组合模式常常用于构建树形结构,而迭代器模式可以用于遍历该树形结构。例如,在文件系统中:

  • 组合模式用于表示目录和文件的树形结构。
  • 迭代器模式用于遍历目录及其子目录中的所有文件。

代码示例

以下是一个结合迭代器模式和组合模式的示例:文件系统


组合模式

场景:文件系统包含两种类型的节点:

  1. 文件(叶子节点)
  2. 目录(组合节点),可以包含文件和子目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <vector>
#include <memory>
#include <string>

// 组件基类
class FileSystemNode {
public:
    virtual void print() const = 0; // 打印节点信息
    virtual ~FileSystemNode() {}
};

// 叶子节点:文件
class File : public FileSystemNode {
    std::string name;
public:
    File(const std::string& name) : name(name) {}
    void print() const override {
        std::cout << "File: " << name << std::endl;
    }
};

// 组合节点:目录
class Directory : public FileSystemNode {
    std::string name;
    std::vector<std::shared_ptr<FileSystemNode>> children;
public:
    Directory(const std::string& name) : name(name) {}

    void add(const std::shared_ptr<FileSystemNode>& child) {
        children.push_back(child);
    }

    void print() const override {
        std::cout << "Directory: " << name << std::endl;
        for (const auto& child : children) {
            child->print();
        }
    }

    const std::vector<std::shared_ptr<FileSystemNode>>& getChildren() const {
        return children;
    }
};

迭代器模式

场景:遍历文件系统的所有节点(包括文件和目录)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stack>

// 文件系统迭代器
class FileSystemIterator {
    std::stack<std::shared_ptr<FileSystemNode>> nodes;

public:
    explicit FileSystemIterator(const std::shared_ptr<FileSystemNode>& root) {
        nodes.push(root);
    }

    bool hasNext() const {
        return !nodes.empty();
    }

    std::shared_ptr<FileSystemNode> next() {
        if (!hasNext()) {
            throw std::runtime_error("No more elements.");
        }

        auto current = nodes.top();
        nodes.pop();

        // 如果当前节点是目录,将其子节点入栈
        if (auto dir = std::dynamic_pointer_cast<Directory>(current)) {
            const auto& children = dir->getChildren();
            for (auto it = children.rbegin(); it != children.rend(); ++it) {
                nodes.push(*it);
            }
        }

        return current;
    }
};

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int main() {
    // 创建文件和目录
    auto root = std::make_shared<Directory>("root");
    auto dir1 = std::make_shared<Directory>("dir1");
    auto dir2 = std::make_shared<Directory>("dir2");

    auto file1 = std::make_shared<File>("file1.txt");
    auto file2 = std::make_shared<File>("file2.txt");
    auto file3 = std::make_shared<File>("file3.txt");

    // 构建文件系统结构
    root->add(file1);
    root->add(dir1);
    dir1->add(file2);
    dir1->add(dir2);
    dir2->add(file3);

    // 打印整个结构
    std::cout << "File System Structure:" << std::endl;
    root->print();

    // 使用迭代器遍历文件系统
    std::cout << "\nIterating over File System:" << std::endl;
    FileSystemIterator iterator(root);
    while (iterator.hasNext()) {
        auto node = iterator.next();
        node->print();
    }

    return 0;
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
File System Structure:
Directory: root
File: file1.txt
Directory: dir1
File: file2.txt
Directory: dir2
File: file3.txt

Iterating over File System:
Directory: root
File: file1.txt
Directory: dir1
File: file2.txt
Directory: dir2
File: file3.txt

分析

  1. 组合模式
    • 用于表示文件和目录的树形层次结构。
    • 客户端可以透明地处理单个文件或目录。
  2. 迭代器模式
    • 用于遍历文件系统的所有节点(包括文件和目录)。
    • 通过封装遍历逻辑,隐藏文件系统的内部实现。
  3. 结合使用
    • 组合模式提供了灵活的结构化表示。
    • 迭代器模式为组合结构提供了统一的遍历接口。

总结

  • 组合模式:处理层次结构,统一叶子和组合对象的接口。
  • 迭代器模式:提供统一的接口,遍历聚合对象的元素。

两者结合使用,可以有效解决复杂树形结构的表示和遍历问题,是文件系统、用户界面等场景中常见的模式组合。

解释桥接模式和适配器模式之间的区别,并说明在哪种情况下选择使用桥接或适配器

桥接模式和适配器模式的概念


桥接模式(Bridge Pattern)

定义: 桥接模式是一种结构型设计模式,它将 抽象部分实现部分 分离,使它们可以独立变化。通过这种方式,可以在不修改抽象和实现的情况下扩展系统。

核心思想

  • 抽象和实现分离,形成两个独立的变化维度。
  • 通过组合代替继承,减少类的复杂度。

适用场景

  1. 一个类需要多个维度的变化(如抽象维度和实现维度)。
  2. 不希望因为某个维度的变化导致类的数量爆炸。
  3. 需要动态切换实现。

适配器模式(Adapter Pattern)

定义: 适配器模式是一种结构型设计模式,它将一个类的接口转换为客户端期望的另一个接口,使得原本由于接口不兼容而无法一起工作的类能够协同工作。

核心思想

  • 解决接口不兼容问题,提供适配层。
  • 让原本不兼容的类可以与客户端协作。

适用场景

  1. 系统需要使用已有类,但这些类的接口与当前需求不兼容。
  2. 使用第三方库或遗留代码,需要适配接口。

桥接模式和适配器模式的区别

特性桥接模式适配器模式
目的将抽象部分与实现部分分离,使它们可以独立扩展。将已有类的接口转换为客户端期望的接口。
关系抽象部分和实现部分通过组合建立关联。客户端和适配器通过适配层建立关联。
应用场景需要在多个维度上扩展类的功能。接口不兼容但需要协同工作。
设计动机减少类的数量,避免继承的复杂性。解决现有代码或库的接口不兼容问题。
实现方式抽象部分和实现部分各自定义接口,独立变化。在客户端和已有类之间增加一个适配层。

如何选择桥接模式或适配器模式

  1. 使用桥接模式的场景
    • 系统需要在多个维度上扩展功能,例如形状和颜色的组合。
    • 抽象部分和实现部分是相对独立的,可以单独扩展。
    • 希望减少类的数量,避免使用继承导致的类爆炸。
  2. 使用适配器模式的场景
    • 需要复用现有类,但其接口与目标接口不兼容。
    • 集成第三方库或遗留代码,需要调整接口。
    • 解决运行时接口转换需求。

代码示例

1. 桥接模式

场景:实现一个图形系统,图形有多种形状(如圆形、矩形)和不同的渲染方式(如矢量渲染和光栅渲染)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <memory>

// 实现接口
class Renderer {
public:
    virtual void renderCircle(float radius) = 0;
    virtual ~Renderer() {}
};

// 矢量渲染实现
class VectorRenderer : public Renderer {
public:
    void renderCircle(float radius) override {
        std::cout << "Drawing a circle with radius " << radius << " using Vector rendering." << std::endl;
    }
};

// 光栅渲染实现
class RasterRenderer : public Renderer {
public:
    void renderCircle(float radius) override {
        std::cout << "Drawing a circle with radius " << radius << " using Raster rendering." << std::endl;
    }
};

// 抽象部分
class Shape {
protected:
    std::shared_ptr<Renderer> renderer;
public:
    Shape(const std::shared_ptr<Renderer>& renderer) : renderer(renderer) {}
    virtual void draw() = 0;
    virtual ~Shape() {}
};

// 具体抽象部分:圆形
class Circle : public Shape {
    float radius;
public:
    Circle(const std::shared_ptr<Renderer>& renderer, float radius)
        : Shape(renderer), radius(radius) {}

    void draw() override {
        renderer->renderCircle(radius);
    }
};

int main() {
    auto vectorRenderer = std::make_shared<VectorRenderer>();
    auto rasterRenderer = std::make_shared<RasterRenderer>();

    Circle circle1(vectorRenderer, 5.0f);
    circle1.draw(); // 使用矢量渲染绘制圆形

    Circle circle2(rasterRenderer, 10.0f);
    circle2.draw(); // 使用光栅渲染绘制圆形

    return 0;
}

分析

  • 图形的 抽象部分Shape(如 Circle)。
  • 渲染方式的 实现部分Renderer(如 VectorRendererRasterRenderer)。
  • 抽象和实现可以独立扩展,例如新增形状或新增渲染方式。

2. 适配器模式

场景:客户端需要统一的绘图接口,但现有的绘图工具接口不兼容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <memory>

// 目标接口
class DrawingAPI {
public:
    virtual void drawCircle(float x, float y, float radius) = 0;
    virtual ~DrawingAPI() {}
};

// 已有的绘图工具 A
class DrawingToolA {
public:
    void draw(float x, float y, float radius) {
        std::cout << "Tool A: Drawing a circle at (" << x << ", " << y 
                  << ") with radius " << radius << std::endl;
    }
};

// 已有的绘图工具 B
class DrawingToolB {
public:
    void render(float x, float y, float radius) {
        std::cout << "Tool B: Rendering a circle at (" << x << ", " << y 
                  << ") with radius " << radius << std::endl;
    }
};

// 适配器:将 Tool A 适配为目标接口
class ToolAAdapter : public DrawingAPI {
    DrawingToolA toolA;
public:
    void drawCircle(float x, float y, float radius) override {
        toolA.draw(x, y, radius);
    }
};

// 适配器:将 Tool B 适配为目标接口
class ToolBAdapter : public DrawingAPI {
    DrawingToolB toolB;
public:
    void drawCircle(float x, float y, float radius) override {
        toolB.render(x, y, radius);
    }
};

// 客户端
class Shape {
    std::shared_ptr<DrawingAPI> api;
public:
    Shape(const std::shared_ptr<DrawingAPI>& api) : api(api) {}

    void draw(float x, float y, float radius) {
        api->drawCircle(x, y, radius);
    }
};

int main() {
    auto toolAAdapter = std::make_shared<ToolAAdapter>();
    auto toolBAdapter = std::make_shared<ToolBAdapter>();

    Shape shapeA(toolAAdapter);
    shapeA.draw(5, 5, 10); // 使用 Tool A 绘制

    Shape shapeB(toolBAdapter);
    shapeB.draw(10, 10, 20); // 使用 Tool B 绘制

    return 0;
}

分析

  • ToolAAdapterToolBAdapter 是适配层,将已有工具的接口转换为统一的目标接口。
  • 客户端可以透明地使用适配器与不同的绘图工具协作。

总结

  1. 桥接模式:解决抽象和实现的分离问题,适用于多维度变化的场景。
    • 示例:形状和渲染方式的组合。
  2. 适配器模式:解决接口不兼容问题,适用于整合已有类的场景。
    • 示例:统一不同绘图工具的接口。

选择模式时,关注问题的核心是扩展性(桥接)还是兼容性(适配器)

介绍责任链模式和命令模式的概念,并说明它们如何解耦发送者和接收者

责任链模式和命令模式的概念


责任链模式(Chain of Responsibility Pattern)

定义: 责任链模式是一种行为型设计模式,它将请求沿着一条处理链进行传递,直到链上的某个对象处理该请求为止。通过这种方式,可以让多个对象都有机会处理请求,从而实现请求的解耦。

核心思想

  • 将请求的发送者和处理者解耦。
  • 让多个处理者有机会处理请求,但请求处理的具体逻辑对发送者是透明的。

适用场景

  1. 多个对象可以处理同一个请求,具体处理者在运行时确定。
  2. 请求处理者之间需要形成一种链式传递关系。
  3. 希望避免请求的发送者和接收者之间的强耦合。

命令模式(Command Pattern)

定义: 命令模式是一种行为型设计模式,它将请求封装为一个命令对象,并将其传递给调用方,从而将请求的发送者和具体的执行者解耦。

核心思想

  • 将请求封装为一个独立的对象。
  • 通过命令对象将请求的发送者和接收者分离。

适用场景

  1. 需要对请求进行排队、记录、撤销或重做操作。
  2. 请求发送者和接收者需要完全解耦。
  3. 系统需要支持参数化的请求或延迟执行请求。

责任链模式和命令模式的解耦方式

特性责任链模式命令模式
解耦方式请求沿着责任链传递,发送者和处理者之间无直接联系。请求被封装为命令对象,发送者只需调用命令接口,无需知道接收者。
请求处理机制请求的处理顺序由责任链中的对象决定。请求的执行由命令对象中指定的接收者完成。
适用重点动态决定谁来处理请求,强调请求的分发机制。动态绑定请求和处理者,强调请求的封装和执行。

实现示例

1. 责任链模式

场景:一个简单的日志系统,日志请求可以按级别传递到不同的处理者(如控制台日志、文件日志、邮件日志)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <memory>
#include <string>

// 抽象处理者
class Logger {
protected:
    std::shared_ptr<Logger> nextLogger;

public:
    void setNextLogger(const std::shared_ptr<Logger>& next) {
        nextLogger = next;
    }

    void logMessage(int level, const std::string& message) {
        if (canHandle(level)) {
            write(message);
        } else if (nextLogger) {
            nextLogger->logMessage(level, message); // 传递给下一个处理者
        }
    }

    virtual ~Logger() {}
protected:
    virtual bool canHandle(int level) = 0;
    virtual void write(const std::string& message) = 0;
};

// 具体处理者:控制台日志
class ConsoleLogger : public Logger {
protected:
    bool canHandle(int level) override {
        return level <= 1; // 处理低优先级日志
    }

    void write(const std::string& message) override {
        std::cout << "Console Logger: " << message << std::endl;
    }
};

// 具体处理者:文件日志
class FileLogger : public Logger {
protected:
    bool canHandle(int level) override {
        return level == 2; // 处理中等优先级日志
    }

    void write(const std::string& message) override {
        std::cout << "File Logger: " << message << std::endl;
    }
};

// 具体处理者:邮件日志
class EmailLogger : public Logger {
protected:
    bool canHandle(int level) override {
        return level >= 3; // 处理高优先级日志
    }

    void write(const std::string& message) override {
        std::cout << "Email Logger: " << message << std::endl;
    }
};

int main() {
    auto consoleLogger = std::make_shared<ConsoleLogger>();
    auto fileLogger = std::make_shared<FileLogger>();
    auto emailLogger = std::make_shared<EmailLogger>();

    // 构建责任链
    consoleLogger->setNextLogger(fileLogger);
    fileLogger->setNextLogger(emailLogger);

    // 发送日志请求
    consoleLogger->logMessage(1, "This is a low-priority message.");
    consoleLogger->logMessage(2, "This is a medium-priority message.");
    consoleLogger->logMessage(3, "This is a high-priority message.");

    return 0;
}

输出

1
2
3
Console Logger: This is a low-priority message.
File Logger: This is a medium-priority message.
Email Logger: This is a high-priority message.

2. 命令模式

场景:一个简单的遥控器系统,可以发出打开和关闭灯光的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <memory>

// 命令接口
class Command {
public:
    virtual void execute() = 0;
    virtual ~Command() {}
};

// 接收者
class Light {
public:
    void turnOn() {
        std::cout << "Light is ON" << std::endl;
    }

    void turnOff() {
        std::cout << "Light is OFF" << std::endl;
    }
};

// 具体命令:打开灯光
class LightOnCommand : public Command {
    Light& light;

public:
    explicit LightOnCommand(Light& light) : light(light) {}

    void execute() override {
        light.turnOn();
    }
};

// 具体命令:关闭灯光
class LightOffCommand : public Command {
    Light& light;

public:
    explicit LightOffCommand(Light& light) : light(light) {}

    void execute() override {
        light.turnOff();
    }
};

// 调用者
class RemoteControl {
    std::shared_ptr<Command> command;

public:
    void setCommand(const std::shared_ptr<Command>& cmd) {
        command = cmd;
    }

    void pressButton() {
        if (command) {
            command->execute();
        }
    }
};

int main() {
    Light light;

    auto lightOn = std::make_shared<LightOnCommand>(light);
    auto lightOff = std::make_shared<LightOffCommand>(light);

    RemoteControl remote;

    // 打开灯光
    remote.setCommand(lightOn);
    remote.pressButton(); // 输出:Light is ON

    // 关闭灯光
    remote.setCommand(lightOff);
    remote.pressButton(); // 输出:Light is OFF

    return 0;
}

分析

  • 命令对象(LightOnCommandLightOffCommand)将请求与接收者(Light)解耦。
  • 调用者(RemoteControl)只需与命令对象交互,而无需关心具体接收者的实现。

责任链模式和命令模式的对比

特性责任链模式命令模式
解耦程度发送者和多个处理者解耦。发送者和单个接收者解耦。
处理机制请求沿责任链传递,可能有多个处理者处理请求。请求被封装为命令对象,由一个接收者执行。
适用场景请求的处理者动态决定,且可能有多个处理者参与处理。请求需要封装、排队或延迟执行。

总结

  • 责任链模式:用于处理链式请求的场景,每个处理者可以选择处理请求或将其传递给下一个处理者。
  • 命令模式:用于请求封装、参数化执行或记录请求的场景。

选择哪种模式取决于系统需求:

  • 如果请求可能由多个处理者动态处理,选择 责任链模式
  • 如果需要将请求封装为对象以便于扩展、记录或延迟执行,选择 命令模式

单例模式实现区别

单例模式的实现方式及其区别

单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。 不同实现方式的主要区别在于线程安全性懒加载、以及实现复杂度


常见单例模式实现

1. 饿汉式单例(Eager Initialization)

特点

  • 在类加载时就创建实例。
  • 线程安全(因为静态实例在类加载时初始化)。
  • 不支持懒加载(即:无论是否使用,实例都会创建)。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

class Singleton {
private:
    static Singleton instance; // 静态实例
    Singleton() {}             // 私有构造函数

public:
    static Singleton& getInstance() {
        return instance;
    }
};

// 初始化静态实例
Singleton Singleton::instance;

int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();
    std::cout << (&s1 == &s2) << std::endl; // 输出 1,表示是同一个实例
    return 0;
}

优点

  1. 实现简单。
  2. 线程安全。

缺点

  1. 无法实现懒加载(资源可能被浪费)。

2. 懒汉式单例(Lazy Initialization)

特点

  • 实例在第一次访问时创建。
  • 默认情况下不是线程安全的,需要额外的同步机制。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

class Singleton {
private:
    static Singleton* instance; // 静态指针
    Singleton() {}              // 私有构造函数

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton(); // 第一次访问时创建实例
        }
        return instance;
    }
};

// 初始化静态成员
Singleton* Singleton::instance = nullptr;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    std::cout << (s1 == s2) << std::endl; // 输出 1
    return 0;
}

优点

  1. 实现了懒加载。
  2. 内存仅在需要时分配。

缺点

  1. 默认情况下不是线程安全的。
  2. 在多线程环境下可能会产生多个实例。

3. 线程安全的懒汉式单例

特点

  • 使用互斥锁(std::mutex)保证线程安全。
  • 适合多线程环境。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <mutex>

class Singleton {
private:
    static Singleton* instance; // 静态指针
    static std::mutex mutex;    // 静态互斥锁
    Singleton() {}              // 私有构造函数

public:
    static Singleton* getInstance() {
        std::lock_guard<std::mutex> lock(mutex); // 加锁
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

// 初始化静态成员
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    std::cout << (s1 == s2) << std::endl; // 输出 1
    return 0;
}

优点

  1. 线程安全。
  2. 实现了懒加载。

缺点

  1. 每次访问都需要加锁,性能可能较低。

4. 双重检查锁(Double-Checked Locking)

特点

  • 优化了线程安全的懒汉式单例,减少了锁的开销。
  • 需要确保编译器支持内存屏障(C++11 及以上支持)。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <mutex>

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mutex;
    Singleton() {}

public:
    static Singleton* getInstance() {
        if (instance == nullptr) { // 第一次检查
            std::lock_guard<std::mutex> lock(mutex);
            if (instance == nullptr) { // 第二次检查
                instance = new Singleton();
            }
        }
        return instance;
    }
};

// 初始化静态成员
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    std::cout << (s1 == s2) << std::endl; // 输出 1
    return 0;
}

优点

  1. 线程安全。
  2. 仅在第一次访问时加锁,性能较高。

缺点

  1. 实现复杂。
  2. 对编译器支持有一定要求(C++11 及以上)。

5. 使用 C++11 的 std::call_once

特点

  • 使用 std::call_oncestd::once_flag,可以高效且线程安全地实现单例。
  • std::call_once 保证初始化代码只执行一次。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <mutex>

class Singleton {
private:
    static Singleton* instance;
    static std::once_flag initFlag;
    Singleton() {}

public:
    static Singleton* getInstance() {
        std::call_once(initFlag, []() { instance = new Singleton(); });
        return instance;
    }
};

// 初始化静态成员
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    std::cout << (s1 == s2) << std::endl; // 输出 1
    return 0;
}

优点

  1. 线程安全。
  2. 代码简洁,高效。

缺点

  1. 需要支持 C++11 及以上标准。

6. C++11 局部静态变量

特点

  • C++11 标准保证了函数内的局部静态变量在初始化时是线程安全的。
  • 是实现单例模式最简单的方式之一。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

class Singleton {
private:
    Singleton() {}

public:
    static Singleton& getInstance() {
        static Singleton instance; // 局部静态变量
        return instance;
    }
};

int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();
    std::cout << (&s1 == &s2) << std::endl; // 输出 1
    return 0;
}

优点

  1. 简单易用。
  2. 线程安全。
  3. 延迟初始化。

缺点

  1. 无法控制销毁时机(由静态变量的生命周期决定)。

实现方式的对比

实现方式线程安全性懒加载实现复杂度性能
饿汉式单例简单
懒汉式单例简单高(无锁情况下)
线程安全的懒汉式单例中等低(每次加锁)
双重检查锁较复杂
std::call_once简单
局部静态变量最简单

选择建议

  • C++11 及以上:推荐使用 局部静态变量std::call_once
  • 需要控制懒加载:选择 线程安全的懒汉式单例双重检查锁
  • 多线程性能敏感:选择 双重检查锁局部静态变量

策略模式实现

策略模式的实现

策略模式是一种行为型设计模式,它定义了一系列算法,并将每种算法封装起来,使它们可以互相替换,且使得算法的变化不会影响使用算法的客户端。


策略模式的结构

  1. 策略接口(Strategy) 定义所有策略的公共接口。
  2. 具体策略(Concrete Strategy) 实现策略接口,定义具体算法。
  3. 上下文(Context) 持有对策略对象的引用,使用策略接口来调用具体策略。

实现:支付系统示例

假设我们需要设计一个支付系统,支持多种支付方式(如信用卡支付、支付宝支付和微信支付),可以通过策略模式实现。


代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <iostream>
#include <memory>
#include <string>

// 策略接口
class PaymentStrategy {
public:
    virtual void pay(int amount) = 0; // 定义支付接口
    virtual ~PaymentStrategy() {}
};

// 具体策略:信用卡支付
class CreditCardPayment : public PaymentStrategy {
private:
    std::string cardNumber;

public:
    CreditCardPayment(const std::string& cardNumber) : cardNumber(cardNumber) {}

    void pay(int amount) override {
        std::cout << "Paid " << amount << " using Credit Card (Card Number: " << cardNumber << ")." << std::endl;
    }
};

// 具体策略:支付宝支付
class AlipayPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " using Alipay." << std::endl;
    }
};

// 具体策略:微信支付
class WeChatPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " using WeChat." << std::endl;
    }
};

// 上下文类
class PaymentContext {
private:
    std::shared_ptr<PaymentStrategy> strategy; // 持有策略对象

public:
    void setStrategy(const std::shared_ptr<PaymentStrategy>& newStrategy) {
        strategy = newStrategy;
    }

    void executePayment(int amount) {
        if (strategy) {
            strategy->pay(amount); // 调用策略的支付方法
        } else {
            std::cout << "No payment strategy set!" << std::endl;
        }
    }
};

// 主函数
int main() {
    PaymentContext context;

    // 使用信用卡支付
    context.setStrategy(std::make_shared<CreditCardPayment>("1234-5678-9876-5432"));
    context.executePayment(100); // 输出:Paid 100 using Credit Card (Card Number: 1234-5678-9876-5432).

    // 使用支付宝支付
    context.setStrategy(std::make_shared<AlipayPayment>());
    context.executePayment(200); // 输出:Paid 200 using Alipay.

    // 使用微信支付
    context.setStrategy(std::make_shared<WeChatPayment>());
    context.executePayment(300); // 输出:Paid 300 using WeChat.

    return 0;
}

代码解析

  1. 策略接口(PaymentStrategy 定义了支付行为的接口,具体支付方式将实现该接口。
  2. 具体策略类
    • CreditCardPayment:实现了信用卡支付逻辑。
    • AlipayPayment:实现了支付宝支付逻辑。
    • WeChatPayment:实现了微信支付逻辑。
  3. 上下文类(PaymentContext
    • 持有策略对象,并通过 setStrategy 方法动态切换支付方式。
    • executePayment 调用策略对象的 pay 方法执行支付。
  4. 动态切换策略
    • 上下文类的策略对象可以在运行时切换,使得客户端代码无需修改即可支持新策略。

优点

  1. 符合开闭原则:添加新策略时,无需修改现有代码。
  2. 行为独立:每种策略封装独立的算法,便于扩展和维护。
  3. 灵活性高:上下文可以在运行时选择或替换策略。

缺点

  1. 增加对象数量:每种策略都需要实现一个类,可能会增加代码复杂度。
  2. 上下文依赖策略接口:如果策略接口发生变化,需要同时修改上下文和具体策略类。

适用场景

  1. 算法需要动态切换
    • 支付系统支持多种支付方式。
    • 不同的排序算法(如快速排序、归并排序、冒泡排序)。
  2. 避免冗长的条件语句
    • 如果代码中存在大量 if-elseswitch-case,可以考虑使用策略模式。
  3. 行为需要独立封装
    • 将算法封装为独立的策略类,便于复用和扩展。

总结

策略模式通过将算法封装为独立的类,使得算法可以互相替换,同时保持上下文代码的稳定性。在需要动态选择算法或避免冗长条件语句的场景中,策略模式是一个非常优秀的设计选择。

本文由作者按照 CC BY 4.0 进行授权