当前位置: 首页 > news >正文

C++开发之设计模式

设计模式存在的意义

  1. 设计模式提供了经过验证的解决方案,帮助开发者在不同的项目中重用这些解决方法,减少重复劳动,提高代码的复用性。
  2. 设计模式通常遵循面向对象的设计原则,如单一职责原则、开放封闭原则等,能够帮助开发者编写更清晰、可维护的代码,可以使代码更具有可读性和结构性,便于理解和维护。
  3. 设计模式帮助降低系统各部分之间的耦合度,提高系统的灵活性和可扩展性。
  4. 设计模式提供了通用的设计方案,促进团队协作

设计模式中面向对象的设计原则

以下八原则有助于构建设计合理、可维护且易于扩展的系统

1.依赖倒置原则(DIP)

高层模式(稳定)不应该依赖于低层模块(变换),二者都应该依赖于抽象(稳定)。

抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。

2.开放封闭原则

软件实体(如类、模板、函数)应该对扩展开放,对修改封闭。也就是说,软件设计应该允许功能扩展,但不需要修改现有的代码。这样可以在不破坏现有功能的情况下进行系统扩展。

3.单一职责原则

每个类应该只有一个单一的职责(或者说一个改变的原因)。换句话说,一个类应该确保它的职责或功能集中在一个特定方面上。如果一个类负责的功能过多,他可能会因为多个不同的原因而发生变化,这样会使得类的维护变得复杂且容易出错。

4.里氏替换原则

对象的子类型必须能够替换其基类型而不改变程序的正确性。换句话说,子类应当能够替代其基类对象而不会影响程序的正确性和行为。

5.接口隔离原则

一个类不应该被迫依赖于它不需要的接口,换句话说接口应当尽可能小而专注,避免包含不必要的方法。建议将大的、复杂的接口拆分成多个更小、更具体的接口,这样,依赖这些接口的类只需要关注他们实际使用的接口,而不必实现他们不需要的方法。

接口:是一种抽象的结构,定义了一组方法和属性,但不提供具体的实现

6.优先使用对象组合,而不是类继承

对象组合:一个类中包含其他类的实例,并使用这些实例的方法和属性。

因为继承在某种程度上破坏了封装性,子类父类耦合度高,子类依赖于父类的实现,如果父类的实现发生变化,所有子类可能都需要修改。对象组合只要求被组合的对象具有良好的定义和接口,这种设计原则有利于减少耦合。

7.封装变化点

使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生影响。即将可能发生变化的部分封装到独立的模块或类中,以减少对系统的替他部分的影响。

8.针对接口编程,而不是针对实现编程

在编程中尽量依赖于接口(抽象类或接口:这两种定义了类该实现的功能规范,但不关心具体的实现细节),而不是具体的实现类。从而实现高内聚和松耦合

面向对象设计中的重构原则

静态->动态

        在软件设计中,从“静态”转向“动态”意味着尽可能延迟决策的时机,把原本在编译时就确定的结构和行为,推迟到运行时再决定。这种做法能够提升系统的灵活性和扩展性。例如,通过插件机制动态加载模块,或者通过配置文件和反射机制决定某个类的具体实现,从而减少重新编译和部署的成本,更适合应对需求频繁变化的场景。

早绑定->晚绑定

        早绑定是指在编译阶段就确定调用哪个函数或使用哪个类;而晚绑定则是在运行时动态决定具体调用的函数或使用的实例。晚绑定常常依赖于接口或虚函数,典型如多态机制,使得系统在保持接口稳定的前提下,可以通过不同的实现来动态响应不同的运行时需求。这种方式有助于降低模块间的耦合度,提高系统的灵活性和可扩展性。

继承->组合

        “继承”是一种强耦合的结构层级关系,一旦子类继承了父类,就会受到其实现细节的强烈约束;而“组合”则是一种更为灵活的方式,通过将一个类作为成员嵌入另一个类中来复用功能。组合遵循“有一个”而不是“是一个”的原则,更符合面向对象中“优先使用对象组合而非类继承”的设计建议,可以避免继承带来的层级僵化和代码复用不灵活的问题。

编译时依赖->运行时依赖

        编译时依赖意味着一个模块在编译时就需要另一个模块的具体实现,这会导致系统耦合度高、灵活性差。而运行时依赖则是通过依赖注入、服务定位器等机制,在程序运行时再指定依赖关系,使得模块之间只依赖抽象接口,而非具体实现。这样可以实现解耦,提高代码的可替换性和可测试性,也有利于实现模块热插拔和灵活配置。

紧耦合->松耦合

        紧耦合指的是系统中各模块之间依赖性强,变更一个模块往往会牵连多个其他模块;而松耦合则强调模块之间通过接口、抽象和依赖注入等方式减少直接依赖,保持相对独立。松耦合使得系统更容易维护、扩展和测试,也能更好地支持模块化开发和团队协作,是高内聚低耦合这一软件设计基本原则的重要体现。

一、创建型设计模式

1.单例模式singleton

        在软件系统中,通常有这样一些特殊的类,必须保证他们在系统中只存在一个实例(对象),才能保证他们的逻辑正确性、以及良好的效率

如何实现?

  1. 将构造函数设置为私有private
  2. 禁用拷贝构造函数和赋值操作符
  3. 实例化对象,和获取实例化对象写在类内,并用static进行修饰

示例:

class Singleton {
private:// 私有构造函数,防止外部创建实例Singleton();public:// 获取唯一实例的方法static Singleton* getInstance();// 禁用拷贝构造和赋值操作符Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;private:// 唯一实例的指针static Singleton* m_single_instance;
};

(1)单线程中实现单例模式(在多线程中不安全)

#include <iostream>class Singleton {
private:// 私有构造函数,防止外部创建实例Singleton() {}public:// 获取唯一实例的方法static Singleton* getInstance();// 禁用拷贝构造和赋值操作符Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;private:// 静态成员变量,用于保存唯一实例static Singleton* m_single_instance;
};// 静态成员初始化为 nullptr
Singleton* Singleton::m_single_instance = nullptr;// 获取实例的方法实现
Singleton* Singleton::getInstance() {if (m_single_instance == nullptr) {m_single_instance = new Singleton();}return m_single_instance;
}

(2)多线程中实现,使用锁和atomic操作(防止编译器对指令进行乱序执行)

#include <mutex>
#include <atomic>class Singleton {
private:Singleton() {}  // 私有构造函数,防止外部实例化public:static Singleton* getInstance();Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;private:static std::atomic<Singleton*> m_single_instance;  // 原子指针,支持多线程安全访问
};// 原子指针初始化为 nullptr
std::atomic<Singleton*> Singleton::m_single_instance{nullptr};// 获取唯一实例的方法(线程安全 + 双重检查锁)
Singleton* Singleton::getInstance() {// 尝试先读取原子指针,避免每次都加锁Singleton* instance_ptr = m_single_instance.load(std::memory_order_acquire);if (instance_ptr == nullptr) {static std::mutex mutex;  // 局部静态互斥锁,线程安全初始化std::lock_guard<std::mutex> lock(mutex);  // 加锁确保只有一个线程能进入// 再次检查实例是否已经被创建(防止多个线程创建多个实例)instance_ptr = m_single_instance.load(std::memory_order_acquire);if (instance_ptr == nullptr) {instance_ptr = new Singleton();m_single_instance.store(instance_ptr,     std::memory_order_release);}}return instance_ptr;
}

2.工厂方法模式(factory method)

        通过对象创建模式绕开new,来避免对象创建(new 具体的类)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。他是接口抽象之后的第一步工作。该模式通过面向对象(多态)的手法,将要创建的具体对象延迟到子类,从而实现一种扩展而非修改的策略,较好的解决了这种紧耦合关系。

目的:定义一个创建对象的接口,但让子类决定实例化哪一个类。

应用场景:用于创建对象时,需要灵活的选择具体实现的情况,例如创建不同类型的对象

示例:

#include <iostream>// 抽象基类(接口)
class ISplitter {
public:virtual void split() = 0;             // 纯虚函数,表示接口virtual ~ISplitter() {}               // 虚析构函数,确保多态删除安全
};// 文本分割器:继承接口并实现 split 方法
class TextSplitter : public ISplitter {
public:void split() override {std::cout << "切割文本" << std::endl;}
};// 视频分割器:继承接口并实现 split 方法
class VideoSplitter : public ISplitter {
public:void split() override {std::cout << "切割视频" << std::endl;}
};

class 某某{

*  ISplitter * splitter=new textSplitter(); 此时就出现了问题,ISplitter是抽象依赖,textSplitter是细节依赖,使得某某类依赖了具体的类(textsplitter类),违背了依赖倒置原则,工厂方法模式就是要解决这个问题,绕开这个new

};

解决方法示例

#include <iostream>//新建一个工厂方法基类
class SplitterFactory {
public://工厂方法,创建基类virtual ISplitter* createSplitter() = 0;virtual ~SplitterFactory() {}
};//具体类的工厂方法
class TextSplitterFactory : public SplitterFactory {
public://让子类工厂方法决定具体实现基类的哪个具体子类virtual ISplitter* createSplitter() {return new TextSplitter();}
};class VideoSplitterFactory : public SplitterFactory {
public:virtual ISplitter* createSplitter() {return new VideoSplitter();}
};//抽象基类(接口)
class ISplitter {
public:virtual void split() = 0;virtual ~ISplitter() {}
};//具体类
class TextSplitter : public ISplitter {
public:virtual void split() {std::cout << "切割文本" << std::endl;}
};class VideoSplitter : public ISplitter {
public:virtual void split() {std::cout << "切割视频" << std::endl;}
};class 某某 {//只需要一个工厂SplitterFactory* factory;public:某某(SplitterFactory* factory) {   //传入哪种splitter的factory,通过factory->createSplitter()最后得到的就是哪种splitterthis->factory = factory;}void function() {ISplitter* splitter = factory->createSplitter();  //多态new//注意:如果此处用new创建,那当需求变化时,就要改new后面的具体类,//工厂方法的出现让我们不需要去修改,而是去添加,//当有业务变化时,我们只需要再添加一个子类工厂方法即可splitter->split();delete splitter;}
};

        此时某某类依赖于splitterFactory和ISplitter两个抽象类,不依赖具体哪个类,实现解耦,这样就解决了依赖倒置问题

3.抽象工厂模式(abstract factory)

        与方法工厂类似,方法工厂是抽象工厂的特例。不同之处在于在软件系统中,经常面临着“一系列的相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定他们具体的类。

示例:

//数据库访问有关的基类
class IDBConnection {};class IDBCommand {};class IDataReader {};//抽象工厂
class IDBFactory {
public:virtual IDBConnection* CreateDBConnection() = 0;virtual IDBCommand* CreateDBCommand() = 0;virtual IDataReader* CreateDataReader() = 0;
};//支持sql server
class sqlConnection : public IDBConnection {};class sqlCommand : public IDBCommand {};class sqlDataReader : public IDataReader {};//sql工厂
class sqlFactory : public IDBFactory {
public:virtual IDBConnection* CreateDBConnection() {return new sqlConnection();}virtual IDBCommand* CreateDBCommand() {return new sqlCommand();}virtual IDataReader* CreateDataReader() {return new sqlDataReader();}
};//支持oracle server
class oracleConnection : public IDBConnection {};class oracleCommand : public IDBCommand {};class oracleDataReader : public IDataReader {};//oracle工厂
class oracleFactory : public IDBFactory {
public:virtual IDBConnection* CreateDBConnection() {return new oracleConnection();}virtual IDBCommand* CreateDBCommand() {return new oracleCommand();}virtual IDataReader* CreateDataReader() {return new oracleDataReader();}
};class 某某 {//只需要一个工厂IDBFactory* dbFactory;IDBConnection* connection;IDBCommand* command;IDataReader* reader;public:某某(IDBFactory* dbFactory) {   //传入哪种splitter的factory,通过factory->createSplitter()最后得到的就是哪种splitterthis->dbFactory = dbFactory;connection = dbFactory->CreateDBConnection();Connection->connectionString("mmmm");command = dbFactory->CreateDBCommand();Command->setconnection(connection);redaer = dbFactory->CreateDataReader();Reader->readstring(connection);}void function() {//注意:如果此处用new创建,那当需求变化时,就要改new后面的具体类,工厂方法的出现让我们不需要去修改,而是去添加,当有业务变化时,我们只需要再添加一个子类工厂方法即可}
};

二、行为设计模式

1.策略模式(strategy)

        在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。

        策略模式的作用是在运行时根据需要透明的更改对象的算法,将算法与对象本身解耦,从而避免上述问题。

模式定义:定义一系列算法,把他们一个一个封装起来,并且使他们可以相互替换(变换)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。

注意:一般出现if-else/switch可以考虑策略模式

示例:计算不同国家的税的算法

一般写法

// 一般写法:如果再需要计算法国的税,那么需要修改枚举,以及计算实现,这就违反了开放封闭原则
enum TaxBase {CN_Tax,US_Tax,DE_Tax
};class salesOrder {TaxBase tax;public:double Calculate() {if (tax == CN_Tax) {// 处理中国税收计算return CN_Tax_Result;}else if (tax == US_Tax) {// 处理美国税收计算return US_Tax_Result;}else if (tax == DE_Tax) {// 处理德国税收计算return DE_Tax_Result;}// 其他计算return 0.0;  // 如果没有匹配的税种,返回默认值}
};

策略模式写法

// 基类
class TaxStrategy {
public:// 一般实现一个方法virtual double Calculate(const Context& context) = 0;virtual ~TaxStrategy() {}
};// 子类
class CNTax : public TaxStrategy {
public:virtual double Calculate(const Context& context) {// 中国税收计算实现..........return 0.0;  // 示例返回值}
};class USTax : public TaxStrategy {
public:virtual double Calculate(const Context& context) {// 美国税收计算实现..........return 0.0;  // 示例返回值}
};class DETax : public TaxStrategy {
public:virtual double Calculate(const Context& context) {// 德国税收计算实现..........return 0.0;  // 示例返回值}
};// 工厂
class strategyFactory {
public:virtual TaxStrategy* newStrategy() = 0;virtual ~strategyFactory() {}
};// 子类工厂
class CNTaxFactory : public strategyFactory {
public:virtual TaxStrategy* newStrategy() {return new CNTax;}
};class USTaxFactory : public strategyFactory {
public:virtual TaxStrategy* newStrategy() {return new USTax;}
};class DETaxFactory : public strategyFactory {
public:virtual TaxStrategy* newStrategy() {return new DETax; }
};// 实现
class salesOrder {
private:TaxStrategy* strategy;public:salesOrder(strategyFactory* strategyFactory) { // strategyFactory对象来指定支持哪种算法this->strategy = strategyFactory->newStrategy();}~salesOrder() {delete this->strategy;}public:double CalculateTax(const Context& context) {// 税收计算(多态调用)double val = strategy->Calculate(context);return val;}
};

2.观察者模式(Observer/Event)

        用于建立一种一对多的依赖关系,以便当一个对象的的状态发生改变时,所有依赖于它的对象都会自动收到通知并更新

优点:松耦合、动态添加/移除观察者

缺点:通知开销、可能造成依赖问题

关键角色:

主题

主题对象是被观察的对象。他通常有一个列表来存储观察者(observer)对象,并提供注册、注销观察者的方法。

观察者

观察者对象是依赖于主题对象的对象。当主题状态改变时,观察者会被通知并进行更新。观察者通常会实现一个统一的接口,该接口用于接受主题的通知。

主要组成:

subject(主题)接口:

attach(observer):用于添加观察者

Detach(observer):用于移除观察者

Notify():用于通知所有注册的观察者

concreteSubject(具体主题):

实现了subject接口,维护具体的状态和观察者列表

当状态变化时调用notify()方法通知所有观察者

observer(观察者)接口:

Update():用于接收主题的通知并进行更新

concreteObserver(具体观察者)

实现了observer接口,定义了在收到通知时执行的具体操作

可以获取到主题的状态,并进行相应的处理

示例:

#include <iostream>
#include <vector>
#include <algorithm>  // for std::remove// 观察者接口
class Observer {
public:virtual void update(int state) = 0;virtual ~Observer() {}
};// 具体观察者
class ConcreteObserver : public Observer {
private:int observeState;
public:virtual void update(int state) override {observeState = state;std::cout << "observer state updated to " << observeState << std::endl;}
};// 主题接口
class Subject {
public:virtual void attach(Observer* observer) = 0;virtual void detach(Observer* observer) = 0;virtual void notify() = 0;virtual ~Subject() {}
};// 具体主题
class ConcreteSubject : public Subject {
private:int subjectState;std::vector<Observer*> observers;
public:void attach(Observer* observer) override {observers.push_back(observer);}void detach(Observer* observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}void notify() override {// 通知所有观察者for (auto* observer : observers) {observer->update(subjectState);}}void setState(int state) {subjectState = state;notify();  // 设置状态后通知观察者}int getState() const {return subjectState;}
};int main() {ConcreteSubject subject;ConcreteObserver observer1;ConcreteObserver observer2;subject.attach(&observer1);subject.attach(&observer2);subject.setState(5);  // 这将更新所有观察者的状态return 0;
}

3.模板方法模式(template method)

        是一种行为设计模式,用于定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法允许子类在不改变算法结构的情况下重新定义算法中的某些特定步骤。这种模式是为了减少代码重复和实现步骤的复用。

主要概念:

  1. 模板方法:定义算法的骨架。模板方法通常是一个固定的步骤序列,其中某些步骤由子类实现。模板方法在基类中定义,并有基类中定义的方法调用子类的方法来完成具体的目标。
  2. 抽象类Abstract class:定义了模板方法,并包含一个或多个抽象方法。这些抽象方法由子类实现
  3. 具体类Concrete class:实现了抽象类中定义的抽象方法,并提供了具体的实现细节

示例:

制作饮料的抽象类中,有制作饮料的抽象方法,再写出泡茶和制作咖啡的具体类

#include <iostream>// 抽象类
class 制作饮料 {
public:// 模板方法: 允许将公共的代码放在抽象类的模板方法中,子类只需要实现特定的步骤,减少代码重复。// 在模板方法中,流程已经固定,子类只需要关注特定的步骤,按需进行重写。// 缺点:不够灵活,如果需要改变算法中的流程,可能需要修改抽象类中的模板方法void 模板方法() {boilWater();brew();pourInCup();addCondiments();}virtual ~制作饮料() = default;protected:// 抽象方法:子类实现的具体细节,在子类中要重写virtual void brew() = 0;virtual void addCondiments() = 0;// 具体方法,模板方法的一部分,所有子类都共享void boilWater() {std::cout << "Boiling water" << std::endl;}void pourInCup() {std::cout << "Pouring into cup" << std::endl;}
};// 具体类
class 制作茶 : public 制作饮料 {
protected:void brew() override {std::cout << "Steeping the tea" << std::endl;}void addCondiments() override {std::cout << "Adding lemon" << std::endl;}
};class 制作咖啡 : public 制作饮料 {
protected:void brew() override {std::cout << "Dripping coffee through filter" << std::endl;}void addCondiments() override {std::cout << "Adding sugar and milk" << std::endl;}
};int main() {制作茶 tea;制作咖啡 coffee;tea.模板方法();coffee.模板方法();return 0;
}

4.状态模式(state)

        允许一个对象在其内部状态改变时改变其行为。换句话说,状态模式使得对象在其状态改变时能够表现出不同的行为,而不需要修改其类的代码。

实现:状态模式通过将状态的行为封装到状态对象中来实现这一点,从而使得状态和行为能够相互独立变化

主要组成部分:

1.上下文(context):维护对当前状态的引用,并允许状态对象处理请求。上下文可能包含一些操作,当内部状态发生变化时,它会转移到其他状态。

2.状态(state):定义一个接口,表示所有具体类的共同行为。状态类通常包含处理请求的方法,并在需要时转移到其他状态

3.具体状态(concreteState):实现state接口,定义状态特定的行为。每个具体状态类代表一个具体的状态,并实现状态接口的方法。

状态模式的工作流程:

1.定义状态接口:创建一个抽象的状态接口,定义所有具体状态需要实现的方法。这些方法通常用于处理上下文的请求,并可能会导致状态的变化。

2.实现具体状态类:实现多个状态类,每个状态类实现状态接口,并提供与特定状态相关的行为。状态类可以在处理请求时更改上下文的状态。

3.上下文类:持有一个 State 对象的引用,并提供方法来设置和获取当前状态。上下文类通常包含处理请求的方法,它将请求委托给当前状态对象。

示例:

演示一个简单的上下文状态管理系统。假设我们有一个简单的任务处理系统,他有两个状态Idle和processing

#include <iostream>using namespace std;// 状态接口
class State {
public:virtual void handleRequest() = 0;virtual ~State() {}
};// 具体状态类A
class ConcreteStateA : public State {
public:void handleRequest() override {// 当前是状态A,接收到请求后,打印并模拟切换到状态Bcout << "in State A. Transitioning to State B" << endl;}
};// 具体状态类B
class ConcreteStateB : public State {
public:void handleRequest() override {// 当前是状态B,接收到请求后,打印并模拟切换到状态Acout << "in State B. Transitioning to State A" << endl;}
};// 上下文类
class Context {
public:Context(State* state) : state(state) {}// 切换当前状态void setState(State* newState) {state = newState;}// 接收请求,调用当前状态的行为void request() {state->handleRequest();  // 将 handleRequest() 委托给当前对象}private:State* state;  // 当前状态
};// 客户端代码
int main() {// 创建两个状态实例State* stateA = new ConcreteStateA();State* stateB = new ConcreteStateB();// 创建上下文,并设置初始状态为 AContext* context = new Context(stateA);// 执行第一次请求,当前是 A 状态context->request();// 切换为 B 状态,再次请求context->setState(stateB);context->request();// 清理内存delete context;delete stateB;delete stateA;return 0;
}

5.命令模式

        用于将请求封装成一个对象,从而允许对请求进行参数化、队列化、撤销和重做操作。命令模式通过将请求的发起者与请求的处理者解耦,使得可以更灵活的管理请求及其执行过程。

主要组成部分:

  1. 命令接口(command):定义一个接口,声明一个执行请求的方法。所有具体命令都实现这个接口,以执行具体的操作
  2. 具体命令(concreteCommand):实现命令接口,定义与请求相关的操作,并将请求的接收者与请求的操作者进行绑定
  3. 接收者(receiver):执行具体的操作或业务逻辑,它响应具体命令的请求。
  4. 调用者(invoker):负责调用命令对象的execute方法。调用者持有一个命令对象,并在需要时调用它。
  5. 客户端(client):创建具体命令对象和接收者,并设置命令的接收者。客户端将具体命令对象传递给调用者。

示例:

如何使用命令模式来实现一个遥控器系统,用于打开和关闭灯

#include <iostream>using namespace std;// 接收者类
class Light {
public:void on() {cout << "the light is on" << endl;}void off() {cout << "the light is off" << endl;}
};// 命令接口
class Command {
public:virtual void execute() = 0; // 请求执行的方法virtual ~Command() {}
};// 具体命令类:开灯指令
class LightOnCommand : public Command {
public:LightOnCommand(Light* light) : light(light) {}void execute() override {light->on();  // 调用接收者的具体方法}private:Light* light;  // 命令的接收者
};// 具体命令类:关灯指令
class LightOffCommand : public Command {
public:LightOffCommand(Light* light) : light(light) {}void execute() override {light->off();  // 调用接收者的具体方法}private:Light* light;  // 命令的接收者
};// 调用者类
class RemoteControl {
public:void setCommand(Command* command) {this->command = command;  // 设置命令对象}void pressButton() {command->execute();  // 执行命令}private:Command* command;  // 当前命令对象
};// 客户端
int main() {RemoteControl* remote = new RemoteControl();  // 创建调用者Light* light = new Light();  // 创建接收者Command* lightOn = new LightOnCommand(light);  // 创建开灯命令Command* lightOff = new LightOffCommand(light);  // 创建关灯命令remote->setCommand(lightOn);  // 设置命令remote->pressButton();        // 执行当前命令(开灯)remote->setCommand(lightOff); // 更换命令remote->pressButton();        // 执行当前命令(关灯)// 清理资源delete light;delete lightOn;delete lightOff;delete remote;return 0;
}

三、结构型设计模式

1.适配器模式(Adapter)

        用于将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而无法一起工作的那些类可以一起工作。这个模式通过创建一个适配器的类,将适配者类的接口转换为目标接口,使得客户端可以使用适配者类的功能而不需要了解其底层实现细节。

主要组成部分:

  1. 目标接口:客户端期望的接口,它定义了适配器应该遵循的接口规范。目标接口是客户端与适配器之间沟通的桥梁
  2. 适配者类:需要被适配的类,它具有一个不同于目标接口的接口。适配者类的接口不符合目标接口规范,需要通过适配器类进行转换
  3. 适配器类:将适配者类的接口转换为目标接口。适配器类实现了目标接口,并在内部调用适配者类的方法,从而将适配者类的功能暴露给客户端。
  4. 客户端:使用目标接口与适配器类交互。客户端通过目标接口与适配器进行交互,而不需要了解适配者的细节。

示例:

假设我们有一个现有的 Adaptee 类,它有一个 specificRequest 方法,而客户端需要一个 Target 接口,它有一个 request 方法。我们可以使用适配器模式将 Adaptee 类适配到 Target 接口。

#include <iostream>using namespace std;// 目标接口:这是客户端期望的接口,定义了一个request方法
class Target {
public:virtual void request() = 0;virtual ~Target() {}
};// 适配者类:他里面有一个不符合目标接口的方法specificRequest
class Adaptee {
public:virtual void specificRequest() {std::cout << "specific request" << std::endl;}
};// 适配器类:负责将target接口的调用(request)转换为adaptee类的实际操作(specificRequest)
class Adapter : public Target {
public:Adapter(Adaptee* adaptee) : adaptee(adaptee) {}void request() override {adaptee->specificRequest(); // 将调用转发给适配者}private:Adaptee* adaptee;
};// 客户端
int main() {Adaptee* adaptee = new Adaptee(); // 创建不兼容接口的类Target* target = new Adapter(adaptee); // 用适配器包装适配者target->request(); // 通过目标接口调用适配者的功能delete target;delete adaptee;return 0;
}

2.外观模式(Facade)

为一组接口提供一个统一的接口,使得子系统更容易使用。

主要目的:简化复杂系统的操作,使得客户端与复杂子系统之间的交互变得更加简单和清晰。通过提供一个统一的外观接口,外观模式隐藏了系统的复杂性,减少了客户端与子系统之间的耦合。

主要组成:

  1. 1.外观类(facade):提供一个简化的接口,封装了对复杂子系统的调用。外观类将客户端的请求委托给适当的子系统,并将子系统的复杂性隐藏起开。
  2. 2.子系统类(subsystem):实现系统的核心功能,通常是一些复杂且功能丰富的类。子系统类的接口不直接暴露给客户端。
  3. 3.客户端(client):通过外观类与子系统交互,客户端只需要了解外观类提供的简化接口,为不需要直接操作子系统。
  4. 示例:

  5. 假设我们有一个复杂的计算机系统,包括多个子系统:CPU、Memory 和 Disk。这些子系统提供了不同的功能,但客户端使用这些功能时需要处理复杂的操作。我们可以通过外观模式为这些子系统提供一个统一的接口,使得客户端能够简单地启动计算机,而无需了解每个子系统的内部操作。

#include <iostream>
#include <string>// 多个子系统类
class CPU {
public:void freeze() {std::cout << "CPU freezing" << std::endl;}void jump(int position) {std::cout << "CPU jumping to position " << position << std::endl;}void execute() {std::cout << "CPU executing" << std::endl;}
};class Memory {
public:void load(int position, const std::string& data) {std::cout << "Memory loading data at position " << position << ": " << data << std::endl;}
};class Disk {
public:std::string read(int sector) {return "Data from disk sector " + std::to_string(sector);}
};// 外观类:提供一个简化的接口startComputer,它将启动计算机所需的多个子系统操作操作封装在一起。
// 客户端只需要通过外观类的方法来完成复杂的操作,而不需要了解每个子系统的具体操作。
class ComputerFacade {
public:ComputerFacade() : cpu(), memory(), disk() {}void startComputer() {cpu.freeze();memory.load(0, disk.read(0));cpu.jump(0);cpu.execute();}private:CPU cpu;Memory memory;Disk disk;
};// 客户端:客户端只需与ComputerFacade进行交互,通过调用startComuter()方法来完成计算机的启动操作,
// 而不需要直接操作cpu、memory、disk等子系统
int main() {ComputerFacade computer;computer.startComputer(); // 客户端通过外观类启动计算机return 0;
}

3.装饰者模式(Decorator)

        它允许在不修改对象结构的情况下,动态的为对象添加新功能。通过将功能封装在装饰者类中,使得功能的扩展变得灵活且可以组合,从而实现对对象的增强

主要组成部分:

  1. 1.抽象组件(component):定义一个接口或抽象类,描述具体的组件和装饰者需要实现的方法。它通常包含一个操作方法,用于执行某种功能。
  2. 2.具体组件(concreteComponent):实现component接口,定义了具体的功能。他是被装饰的原始对象。
  3. 3.装饰者(decorator):继承自component,持有一个component对象的引用,并通过该引用委托操作的调用给实际的组件。装饰者可以在调用实际组件的方法之前或之后添加新的行为
  4. 4.具体装饰者(concreteDecorator):继承自decorator,在decorator的基础上,添加具体的功能增强或修改
  5. 示例:

  6. 考虑一个基本的文本处理系统,我们有一个基本的 Text 类和几个装饰者类,分别用于添加不同的功能(如加粗、斜体)。

  7. #include <iostream>
    #include <string>// 抽象组件
    class Text {
    public:virtual void render() = 0;  // 需要增强的功能virtual ~Text() {}
    };// 具体组件:实现了Text接口并提供了基本的render方法
    class PlainText : public Text {
    public:PlainText(const std::string& content) : content(content) {}void render() override {std::cout << content << std::endl;}private:std::string content;  // 文字内容
    };// 装饰者基类:持有一个Text对象的指针,并将操作委托给它。它可以添加新功能。
    class TextDecorator : public Text {
    public:TextDecorator(Text* text) : text(text) {}void render() override {text->render();  // 委托给实际组件}protected:Text* text;  // 被装饰的组件
    };// 具体装饰者类:加粗
    class BoldDecorator : public TextDecorator {
    public:BoldDecorator(Text* text) : TextDecorator(text) {}void render() override {std::cout << "<b>";TextDecorator::render();std::cout << "</b>" << std::endl;}
    };// 具体装饰者类:倾斜
    class ItalicDecorator : public TextDecorator {
    public:ItalicDecorator(Text* text) : TextDecorator(text) {}void render() override {std::cout << "<i>";TextDecorator::render();std::cout << "</i>" << std::endl;}
    };// 客户端:创建一个PlainText对象,然后逐层装饰,最终使用装饰后的对象调用render方法。
    int main() {Text* plainText = new PlainText("hello");Text* boldText = new BoldDecorator(plainText);  // 加粗装饰Text* italicBoldText = new ItalicDecorator(boldText);  // 斜体和加粗装饰italicBoldText->render();  // 渲染加粗且斜体的文字// 清理内存delete italicBoldText;delete boldText;delete plainText;return 0;
    }
    

4.代理模式

        用于为其他对象提供一种代理以控制对该对象的访问。代理模式通过引入一个代理对象,来控制对真实对象的访问。代理模式常用于控制对真实对象的访问,提供额外的功能或保护

主要组成部分:

  1. 1.主题接口(subject)
  2. 定义了真实对象和代理对象都需要实现的接口。这个接口声明了真实对象和代理对象所共享的方法。
  3. 2.真实主题(realSubject)
  4. 实现主题接口,定义了实际的业务逻辑。真实主题是代理模式中需要被代理的实际对象。
  5. 3.代理(proxy)
  6. 实现了主题接口,持有对真实主题对象的引用。代理对象通过调用真实主题对象的方法来实现功能,并可以在调用之前或之后执行其他操作(如权限检查、日志记录等)。

代理模式的种类:

  1. 1.静态代理:代理对象在编译时就已经确定了,代理类是手动创建的,通常 用在需要为目标对象提供额外功能时。
  2.    2.动态代理:代理对象在运行时生成,通常使用反射机制创建。可以在运行时创建代理对象,并动态地处理方法调用。
  3. 示例:

  4. 有一个虚拟的文件打印系统,其中代理对象控制对实际打印操作的访问

  5. #include <iostream>// 主题接口
    class Document {
    public:virtual void print() = 0;  // 真实对象和代理对象所共享的方法virtual ~Document() {}
    };// 真实主题
    class RealDocument : public Document {
    public:void print() override {std::cout << "Printing real document" << std::endl;}
    };// 代理类
    class ProxyDocument : public Document {
    public:// 代理类持有对真实主题对象的引用ProxyDocument(RealDocument* realDoc) : realDocument(realDoc) {}void print() override {if (checkAccess()) {realDocument->print();logAccess();}}private:bool checkAccess() {std::cout << "Checking access permission" << std::endl;return true;  // 假设访问权限通过}void logAccess() {std::cout << "Logging access permission" << std::endl;}RealDocument* realDocument;
    };// 客户端
    int main() {RealDocument* realDoc = new RealDocument();  // 创建真实主题对象ProxyDocument* proxy = new ProxyDocument(realDoc);  // 创建代理对象,传入被代理的对象proxy->print();  // 通过代理对象访问真实主题// 清理内存delete realDoc;delete proxy;return 0;
    }
    

相关文章:

  • 中小企业技术跃迁:云原生后端如何实现高效低成本系统建设
  • Java:XML被自动转义
  • 【软件设计师】模拟题一
  • 面试题:Redis 一次性获取大量Key的风险及优化方案
  • R 语言科研绘图第 41 期 --- 桑基图-基础
  • Redis 及其在系统设计中的作用
  • 【docker】 pull FROM build
  • Dash框架深度解析:数据驱动型Web应用的Python化革命
  • 前端基础之《Vue(9)—混入》
  • Linux 命令行与 vi/vim 编辑器完全指南
  • JetBrains GoLang IDE无限重置试用期,适用最新2025版
  • std::deque的简化源码详解
  • 架构-数据库系统
  • Java基础集合 面试经典八股总结 [连载ing]
  • Java开发工具IntelliJ IDEA v2025.1——全面支持Java 24、整合AI
  • C++内存管理那些事
  • 树型结构(知识点梳理及例题精讲)
  • 一键多环境构建——用 Hvigor 玩转 HarmonyOS Next
  • Docker 部署 Redis:快速搭建高效缓存服务
  • 解决yarn install 报错 error \node_modules\electron: Command failed.
  • 经济日报金观平:统筹国内经济工作和国际经贸斗争
  • 靳燕出任中央戏剧学院党委副书记,原任中戏院长助理
  • 印度媒体称印巴在克什米尔再次交火
  • 三大交易所修订股票上市规则:明确关键少数责任,强化中小股东保障
  • 保时捷中国研发中心落户上海虹桥商务区,计划下半年投入运营
  • 体坛联播|皇马上演罢赛闹剧,杨瀚森宣布参加NBA选秀