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

C++的7种设计模式原则

一、设计模式前言

设计模式(Design Patterns)的“模式”指的是一种在软件设计中经过验证的、解决特定问题的方案。它们不是具体的代码,而是解决常见设计问题的抽象方案或模板。设计模式提供了一种标准的方式来组织代码,以提高代码的重用性、可维护性和可扩展性。

设计模式通常包括以下几个方面:

  1. 问题(Problem):描述在特定上下文中需要解决的设计问题。
  2. 解决方案(Solution):提供一种或多种解决该问题的方法。
  3. 效果(Consequences):说明采用该模式后可能会带来的优点和缺点。

《Design Patterns: Elements of Reusable Object-Oriented Software》,通常被称为《Gang of Four》(GoF)一书,是设计模式领域的经典之作。这本书将设计模式分为三大类:

  • 创建型模式:如单例模式、工厂模式、建造者模式等,主要解决对象创建问题。
  • 结构型模式:如适配器模式、装饰器模式、桥接模式等,主要解决类或对象的组合问题。
  • 行为型模式:如观察者模式、策略模式、状态模式等,主要解决对象间的通信和职责分配问题。

每种设计模式都有其特定的应用场景,通过使用这些模式,开发者能够更有效地管理复杂的系统设计

 二、设计模式基本原则

设计模式的最高目标应当是:高内聚、低耦合

高内聚(High Cohesion)

定义:高内聚指的是一个模块或类的职责应该高度集中,即模块或类应当完成单一且明确的功能。

特点

  • 职责单一:一个模块或类应该只关注于完成某一个具体的功能或职责,避免过多的职责分散在一个模块中。
  • 功能相关:模块内部的功能和数据是紧密相关的,这样可以使得模块更加专注和稳定。

好处

  • 易于理解和维护:一个模块应当只负责自己的职责

低耦合(Low Coupling)

定义:低耦合指的是一个模块或类与其他模块或类之间的依赖关系应该尽可能少,互相之间的影响最小化。

特点

  • 独立性强:一个模块的实现和变化不应该影响到其他模块,模块之间的交互通过清晰的接口进行。
  • 接口明确:模块之间的通信应该通过接口或抽象类来实现,具体实现细节对外部隐藏。

好处

  • 灵活性高:低耦合使得各个模块可以独立开发、测试和维护,增强了系统的灵活性。
  • 易于扩展:当需要增加新的功能或替换某个模块时,可以不影响其他模块,降低了改动的风险。
  • 增强可复用性:低耦合的模块更容易在不同的上下文中复用,因为它们不依赖于特定的实现或其他模块的内部结构

        在软件设计中,实现“高内聚、低耦合”的目标,可以使系统更加健壮、易于维护和扩展。高内聚确保了每个模块专注于自己的职责,而低耦合则减少了模块之间的依赖,增强了系统的灵活性和可维护性。这两个原则(目标)通常被视为良好软件设计的基石。

三、设计模式的7种基本原则

1)开放封闭原则OCP(Open for Extension,Closed for Modification Principle)

模块/类的变化是通过增加功能代码实现的,非修改源代码。即“软件实体(如类、模块、函数等)应该对扩展开放,对修改封闭。”

实现OCP常用方法:

  1. 使用抽象和接口:通过抽象类或接口定义模块的核心功能,然后使用具体的实现类来扩展功能。这样,你可以在不修改原有代码的情况下,添加新的实现类来扩展功能。

  2. 使用设计模式:

    • 策略模式(Strategy Pattern):可以通过定义策略接口和不同的策略实现来实现功能的扩展,而不修改现有的代码。
    • 装饰器模式(Decorator Pattern):允许你通过包装现有的对象来添加新的功能,而不改变原有对象的代码。
    • 模板方法模式(Template Method Pattern):通过定义一个算法的骨架,并允许子类重写某些步骤,从而实现功能的扩展。

设计模式后面会详细讲述,这里使用代码简单演示下OCP的方法。

// 模拟银行业务代码功能实现的迭代,符合开放封闭原则

  • 对扩展开放:代码允许通过增加新的派生类(如autoTransferAccountsWorker 、autoPayWorker和autoDepositWorker )来扩展业务功能,而不需要修改现有的BankWorker类及其子类。这符合开放封闭原则中“对扩展开放”的要求。

  • 对修改封闭:代码的设计避免了对现有类(如BankWorker和已有的业务类)进行修改。只需创建新的业务类并实现 doWorking() 方法即可扩展功能。这也符合开放封闭原则中“对修改封闭”的要求。

ocp.cpp

#include <iostream>
using namespace std;class BankWork
{
public:void deposit(){std::cout << "存款: " << std::endl;}void transferAccounts(){std::cout << "转账: " << std::endl;}void pay(){std::cout << "缴费: " << std::endl;}
protected:
private:
};class BankWorker : public BankWork
{
public:virtual void doWorking() = 0;
};//已存在的业务
class  DepositWorker : public BankWorker
{
public:virtual void doWorking(){std::cout << "存款: " << std::endl;}
};class  transferAccountsWorker : public BankWorker
{
public:virtual void doWorking(){std::cout << "转账: " << std::endl;}
};class  PayWorker : public BankWorker
{
public:virtual void doWorking(){std::cout << "缴费: " << std::endl;}
};//扩展业务
class  autoTransferAccountsWorker : public BankWorker
{
public:virtual void doWorking(){std::cout << "批量转账: " << std::endl;}
};class  autoPayWorker : public BankWorker
{
public:virtual void doWorking(){std::cout << "批量缴费: " << std::endl;}
};//又一个新扩展的业务
class  autoDepositWorker : public BankWorker
{
public:virtual void doWorking(){std::cout << "批量存款: " << std::endl;}
};//传统的业务调用
void main001()
{BankWork *ptr = new BankWork ;ptr->deposit();ptr->transferAccounts();ptr->pay();delete ptr;std::cout << "传统的业务调用完成! " << std::endl;return ;
}//新的业务调用
void main002()
{BankWorker *new_ptr  = NULL;new_ptr = new DepositWorker;new_ptr->doWorking(); //有多态发生delete new_ptr;new_ptr = new transferAccountsWorker;new_ptr->doWorking(); //有多态发生delete new_ptr;new_ptr = new PayWorker;new_ptr->doWorking(); //有多态发生delete new_ptr;std::cout << "新的业务调用完成! " << std::endl;return ;
}//框架函数
void howDo(BankWorker *ptr)
{ptr->doWorking(); //有多态发生
}//扩展业务调用
void main003()
{BankWorker *new_ptr  = NULL;new_ptr = new autoTransferAccountsWorker;howDo(new_ptr);delete new_ptr;new_ptr = new autoPayWorker;howDo(new_ptr);delete new_ptr;new_ptr = new autoDepositWorker;howDo(new_ptr);delete new_ptr;std::cout << "扩展业务调用完成! " << std::endl;return ;
}
int main()
{
//调用业务main001();main002();main003();
}

运行效果

 

2)依赖倒置原则DIP(Dependence Inversion Principle)

依赖于抽象的接口,不依赖具体的实现(类/模块),即依赖于接口编程,其核心思想是:高层模块不应该依赖于低层模块。两者都应该依赖于抽象。抽象不应该依赖于细节。细节应该依赖于抽象。

实现DIP常用方法:

  1. 使用接口或抽象类:将高层模块和低层模块的依赖关系抽象化。例如,定义一个接口或抽象类,低层模块实现这个接口,而高层模块依赖于这个接口。

  2. 依赖注入(Dependency Injection):通过构造函数注入、方法注入或属性注入等方式,将依赖项传递给需要它们的类,从而使高层模块和低层模块之间的依赖关系变得松散。

DIP的示例:

  1. Bankworker类依赖于 DepositInterfaceTransferAccountsInterface 和 PayInterface 接口,而不是具体的实现类。这样,BankWorker可以使用任何实现了这些接口的类。

  2. 具体实现类仍然是 DepositWorkerTransferAccountsWorker 和 PayWorker。这些实现类可以独立开发和测试,不需要修改BankWorker

  3. 依赖注入则通过构造函数将具体的实现类传递给 BankWorker。这样,BankWorker 不再直接创建具体的实现对象,而是通过接口来使用它们。

  4. howDo 函数用于处理扩展业务,它仍然依赖于接口,这样可以处理不同的具体实现而不需要修改其代码。

dip.cpp

#include <iostream>
#include <memory>// 业务接口
class DepositInterface
{
public:virtual ~DepositInterface() = default;virtual void deposit() const = 0;
};class TransferAccountsInterface
{
public:virtual ~TransferAccountsInterface() = default;virtual void transferAccounts() const = 0;
};class PayInterface
{
public:virtual ~PayInterface() = default;virtual void pay() const = 0;
};// 高层模块:业务处理器
class BankWorker
{
public:BankWorker(std::unique_ptr<DepositInterface> depositImpl,std::unique_ptr<TransferAccountsInterface> transferImpl,std::unique_ptr<PayInterface> payImpl): depositImpl(std::move(depositImpl)),transferImpl(std::move(transferImpl)),payImpl(std::move(payImpl)) {}void deposit() const{depositImpl->deposit();}void transferAccounts() const{transferImpl->transferAccounts();}void pay() const{payImpl->pay();}private:std::unique_ptr<DepositInterface> depositImpl;std::unique_ptr<TransferAccountsInterface> transferImpl;std::unique_ptr<PayInterface> payImpl;
};// 具体实现类
class DepositWorker : public DepositInterface
{
public:void deposit() const override{std::cout << "存款: " << std::endl;}
};class TransferAccountsWorker : public TransferAccountsInterface
{
public:void transferAccounts() const override{std::cout << "转账: " << std::endl;}
};class PayWorker : public PayInterface
{
public:void pay() const override{std::cout << "缴费: " << std::endl;}
};// 扩展业务处理器
class AutoTransferAccountsWorker : public TransferAccountsInterface
{
public:void transferAccounts() const override{std::cout << "批量转账: " << std::endl;}
};class AutoPayWorker : public PayInterface
{
public:void pay() const override{std::cout << "批量缴费: " << std::endl;}
};class AutoDepositWorker : public DepositInterface
{
public:void deposit() const override{std::cout << "批量存款: " << std::endl;}
};// 框架函数
template<typename T>
void howDo(const std::unique_ptr<T>& ptr)
{if constexpr (std::is_base_of<DepositInterface, T>::value) {ptr->deposit();}if constexpr (std::is_base_of<TransferAccountsInterface, T>::value) {ptr->transferAccounts();}if constexpr (std::is_base_of<PayInterface, T>::value) {ptr->pay();}
}// 传统的业务调用
void main001()
{BankWorker worker(std::make_unique<DepositWorker>(),std::make_unique<TransferAccountsWorker>(),std::make_unique<PayWorker>());worker.deposit();worker.transferAccounts();worker.pay();std::cout << "传统的业务调用完成! " << std::endl;
}// 扩展业务调用
void main002()
{std::unique_ptr<AutoTransferAccountsWorker> autoTransferWorker = std::make_unique<AutoTransferAccountsWorker>();howDo(autoTransferWorker);std::unique_ptr<AutoPayWorker> autoPayWorker = std::make_unique<AutoPayWorker>();howDo(autoPayWorker);std::unique_ptr<AutoDepositWorker> autoDepositWorker = std::make_unique<AutoDepositWorker>();howDo(autoDepositWorker);std::cout << "扩展业务调用完成! " << std::endl;
}int main()
{main001();main002();return 0;
}

运行效果

 

3)迪米特法则LoD(Law of Demeter)

迪米特法则又被称为"最少知识原则","不要和陌生人说话原则",一个对象/模块应当对其他对象/模块尽可能的不了解,以降低各个对象/模块之间的耦合关系,整体提高系统的可维护性、健壮性。

LoD的核心要点:

  1. 每个对象应当只与其直接的朋友对象通信。换句话说,方法只能调用:

    • 对象的自身的方法。
    • 作为参数传递进来的对象的方法。
    • 对象创建的(例如,通过构造函数或工厂方法)对象的方法。
    • 对象的成员变量(成员变量应当是该类的内部实现的一部分)。
  2. 避免链式调用。即对象 A 不应直接调用对象 B 的方法,然后再通过对象 B 调用对象 C 的方法。这样会使得对象 A 对对象 C 的实现细节产生依赖,从而增加耦合度。

实现LoD常用方法:

  1. 封装数据:将数据和操作数据的方法封装在一个类中,尽量减少外部对这些数据的直接访问。

  2. 使用接口:通过接口定义模块之间的交互,而不是直接操作对象的内部状态。

  3. 控制方法访问范围:尽量将方法和数据的访问范围控制在最小的范围内。例如,使用私有(private)或保护(protected)访问修饰符来限制访问

LoD的示例:

1、设置函数howDo(const T& obj)为操作对象的引用(或指针),避免直接对具体实现类的操作,

2、设置BankWorker 设计为只与接口 DepositInterfaceTransferAccountsInterface 和 PayInterface 交互,而不是具体的实现类。

lod.cpp

#include <iostream>
#include <memory>// 业务接口
class DepositInterface
{
public:virtual ~DepositInterface() = default;virtual void deposit() const = 0;
};class TransferAccountsInterface
{
public:virtual ~TransferAccountsInterface() = default;virtual void transferAccounts() const = 0;
};class PayInterface
{
public:virtual ~PayInterface() = default;virtual void pay() const = 0;
};// 高层模块:业务处理器
class BankWorker
{
public:BankWorker(std::unique_ptr<DepositInterface> depositImpl,std::unique_ptr<TransferAccountsInterface> transferImpl,std::unique_ptr<PayInterface> payImpl): depositImpl(std::move(depositImpl)),transferImpl(std::move(transferImpl)),payImpl(std::move(payImpl)) {}void deposit() const{depositImpl->deposit();}void transferAccounts() const{transferImpl->transferAccounts();}void pay() const{payImpl->pay();}private:std::unique_ptr<DepositInterface> depositImpl;std::unique_ptr<TransferAccountsInterface> transferImpl;std::unique_ptr<PayInterface> payImpl;
};// 具体实现类
class DepositWorker : public DepositInterface
{
public:void deposit() const override{std::cout << "存款: " << std::endl;}
};class TransferAccountsWorker : public TransferAccountsInterface
{
public:void transferAccounts() const override{std::cout << "转账: " << std::endl;}
};class PayWorker : public PayInterface
{
public:void pay() const override{std::cout << "缴费: " << std::endl;}
};// 扩展业务处理器
class AutoTransferAccountsWorker : public TransferAccountsInterface
{
public:void transferAccounts() const override{std::cout << "批量转账: " << std::endl;}
};class AutoPayWorker : public PayInterface
{
public:void pay() const override{std::cout << "批量缴费: " << std::endl;}
};class AutoDepositWorker : public DepositInterface
{
public:void deposit() const override{std::cout << "批量存款: " << std::endl;}
};// 框架函数
template<typename T>
void howDo(const T& obj)
{if constexpr (std::is_base_of<DepositInterface, T>::value) {obj.deposit();}if constexpr (std::is_base_of<TransferAccountsInterface, T>::value) {obj.transferAccounts();}if constexpr (std::is_base_of<PayInterface, T>::value) {obj.pay();}
}// 传统的业务调用
void main001()
{BankWorker worker(std::make_unique<DepositWorker>(),std::make_unique<TransferAccountsWorker>(),std::make_unique<PayWorker>());worker.deposit();worker.transferAccounts();worker.pay();std::cout << "传统的业务调用完成! " << std::endl;
}// 扩展业务调用
void main002()
{std::unique_ptr<AutoTransferAccountsWorker> autoTransferWorker = std::make_unique<AutoTransferAccountsWorker>();howDo(*autoTransferWorker);std::unique_ptr<AutoPayWorker> autoPayWorker = std::make_unique<AutoPayWorker>();howDo(*autoPayWorker);std::unique_ptr<AutoDepositWorker> autoDepositWorker = std::make_unique<AutoDepositWorker>();howDo(*autoDepositWorker);std::cout << "扩展业务调用完成! " << std::endl;
}int main()
{main001();main002();return 0;
}

 运行效果

4)单一职责原则SRP(Single Responsibility Principle)

类/模块职责(功能)要单一,即对外尽可能的只提供一种功能,也希望引起功能变化的原因只有一个。SRP的核心思想是:一个类应该只有一个原因引起它的变化。

SRP的好处:

  1. 提高代码的可维护性:如果一个类只承担一个职责,当这个职责发生变化时,只需要修改这个类,减少了对其他部分的影响。
  2. 增强代码的可读性:单一职责的类通常更小、更专注,易于理解和阅读。
  3. 提高代码的可重用性:职责单一的类可以在其他项目中更容易地复用,因为它的功能和行为更明确。
  4. 简化测试:职责明确的类更容易进行单元测试,因为它们的行为更集中和专一

SRP的示例:

  1. 分离处理类:BankWorker 被拆分为 DepositProcessorTransferProcessor 和 PayProcessor。每个处理器类负责一种业务操作的处理。

  2. 具体业务类:具体的实现类(如 DepositWorker 和 AutoDepositWorker)直接传递给处理器类。

srp.cpp

#include <iostream>
#include <memory>// 业务接口
class DepositInterface
{
public:virtual ~DepositInterface() = default;virtual void deposit() const = 0;
};class TransferAccountsInterface
{
public:virtual ~TransferAccountsInterface() = default;virtual void transferAccounts() const = 0;
};class PayInterface
{
public:virtual ~PayInterface() = default;virtual void pay() const = 0;
};// 具体实现类
class DepositWorker : public DepositInterface
{
public:void deposit() const override{std::cout << "存款: " << std::endl;}
};class TransferAccountsWorker : public TransferAccountsInterface
{
public:void transferAccounts() const override{std::cout << "转账: " << std::endl;}
};class PayWorker : public PayInterface
{
public:void pay() const override{std::cout << "缴费: " << std::endl;}
};// 扩展业务实现类
class AutoDepositWorker : public DepositInterface
{
public:void deposit() const override{std::cout << "批量存款: " << std::endl;}
};class AutoTransferAccountsWorker : public TransferAccountsInterface
{
public:void transferAccounts() const override{std::cout << "批量转账: " << std::endl;}
};class AutoPayWorker : public PayInterface
{
public:void pay() const override{std::cout << "批量缴费: " << std::endl;}
};// 业务处理器
class DepositProcessor
{
public:DepositProcessor(std::unique_ptr<DepositInterface> depositImpl): depositImpl(std::move(depositImpl)) {}void process() const{depositImpl->deposit();}private:std::unique_ptr<DepositInterface> depositImpl;
};class TransferProcessor
{
public:TransferProcessor(std::unique_ptr<TransferAccountsInterface> transferImpl): transferImpl(std::move(transferImpl)) {}void process() const{transferImpl->transferAccounts();}private:std::unique_ptr<TransferAccountsInterface> transferImpl;
};class PayProcessor
{
public:PayProcessor(std::unique_ptr<PayInterface> payImpl): payImpl(std::move(payImpl)) {}void process() const{payImpl->pay();}private:std::unique_ptr<PayInterface> payImpl;
};// 传统的业务调用
void main001()
{DepositProcessor depositProcessor(std::make_unique<DepositWorker>());TransferProcessor transferProcessor(std::make_unique<TransferAccountsWorker>());PayProcessor payProcessor(std::make_unique<PayWorker>());depositProcessor.process();transferProcessor.process();payProcessor.process();std::cout << "传统的业务调用完成! " << std::endl;
}// 扩展业务调用
void main002()
{DepositProcessor depositProcessor(std::make_unique<AutoDepositWorker>());TransferProcessor transferProcessor(std::make_unique<AutoTransferAccountsWorker>());PayProcessor payProcessor(std::make_unique<AutoPayWorker>());depositProcessor.process();transferProcessor.process();payProcessor.process();std::cout << "扩展业务调用完成! " << std::endl;
}int main()
{main001();main002();return 0;
}

运行效果

 

5)接口隔离原则ISP(Interface Segregation Principle)

一个类不应被迫依赖它不使用的方法。换句话说,一个接口应当尽可能地保持简单和专注,避免将过多的职责集中在一个接口上。

ISP的核心思想是:客户端不应该依赖于它不需要的接口。

ISP的好处:

  1. 减少耦合:当接口被拆分成多个小接口时,每个客户端只依赖于它所需要的部分,减少了不必要的依赖。
  2. 提高灵活性:系统中的类只实现它们需要的接口,易于进行修改和扩展。
  3. 增强可维护性:小的接口通常更简单,易于理解和维护。
  4. 提升可重用性:小接口具有明确的责任,可以在不同的上下文中重用。

接口隔离原则的主要目标是避免“胖接口”(fat interface),即一个接口包含过多的方法。

实现ISP的常用方法:

  1. 分析接口的职责:识别接口中的不同职责,将它们拆分成多个小的接口。
  2. 定义专注的接口:创建专注于单一功能的小接口,以减少不必要的依赖。
  3. 采用组合:通过接口的组合来实现复杂的功能,而不是将所有功能放在一个接口中。
  4. 避免接口膨胀:避免将过多的方法放在一个接口中,保持接口的简单性和专注性。

ISP的示例:

  1. 接口设计:将BankWorker类拆分为多个接口(DepositInterface、TransferAccountsInterface、PayInterface),这样每个接口只包含一个特定的业务功能。符合接口隔离原则,提高了代码的灵活性和可维护性。

isp.cpp

#include <iostream>
#include <memory>// 基类
class BankWork
{
public:virtual ~BankWork() = default;virtual void deposit() const{std::cout << "存款: " << std::endl;}virtual void transferAccounts() const{std::cout << "转账: " << std::endl;}virtual void pay() const{std::cout << "缴费: " << std::endl;}
};// 业务接口
class DepositInterface
{
public:virtual ~DepositInterface() = default;virtual void deposit() const = 0;
};class TransferAccountsInterface
{
public:virtual ~TransferAccountsInterface() = default;virtual void transferAccounts() const = 0;
};class PayInterface
{
public:virtual ~PayInterface() = default;virtual void pay() const = 0;
};// 业务处理类
class BankWorker : public DepositInterface, public TransferAccountsInterface, public PayInterface
{
public:virtual ~BankWorker() = default;void deposit() const override{std::cout << "存款: " << std::endl;}void transferAccounts() const override{std::cout << "转账: " << std::endl;}void pay() const override{std::cout << "缴费: " << std::endl;}
};// 业务处理器
class DepositWorker : public DepositInterface
{
public:void deposit() const override{std::cout << "存款: " << std::endl;}
};class TransferAccountsWorker : public TransferAccountsInterface
{
public:void transferAccounts() const override{std::cout << "转账: " << std::endl;}
};class PayWorker : public PayInterface
{
public:void pay() const override{std::cout << "缴费: " << std::endl;}
};// 扩展业务
class AutoTransferAccountsWorker : public TransferAccountsInterface
{
public:void transferAccounts() const override{std::cout << "批量转账: " << std::endl;}
};class AutoPayWorker : public PayInterface
{
public:void pay() const override{std::cout << "批量缴费: " << std::endl;}
};class AutoDepositWorker : public DepositInterface
{
public:void deposit() const override{std::cout << "批量存款: " << std::endl;}
};// 框架函数
template<typename T>
void howDo(const std::unique_ptr<T>& ptr)
{// 先检查 T 是否实现了相关接口if constexpr (std::is_base_of<DepositInterface, T>::value) {ptr->deposit();}if constexpr (std::is_base_of<TransferAccountsInterface, T>::value) {ptr->transferAccounts();}if constexpr (std::is_base_of<PayInterface, T>::value) {ptr->pay();}
}// 传统的业务调用
void main001()
{std::unique_ptr<BankWorker> ptr = std::make_unique<BankWorker>();ptr->deposit();ptr->transferAccounts();ptr->pay();std::cout << "传统的业务调用完成! " << std::endl;
}// 新的业务调用
void main002()
{std::unique_ptr<DepositWorker> depositWorker = std::make_unique<DepositWorker>();depositWorker->deposit();std::unique_ptr<TransferAccountsWorker> transferWorker = std::make_unique<TransferAccountsWorker>();transferWorker->transferAccounts();std::unique_ptr<PayWorker> payWorker = std::make_unique<PayWorker>();payWorker->pay();std::cout << "新的业务调用完成! " << std::endl;
}// 扩展业务调用
void main003()
{std::unique_ptr<AutoTransferAccountsWorker> autoTransferWorker = std::make_unique<AutoTransferAccountsWorker>();howDo(autoTransferWorker);std::unique_ptr<AutoPayWorker> autoPayWorker = std::make_unique<AutoPayWorker>();howDo(autoPayWorker);std::unique_ptr<AutoDepositWorker> autoDepositWorker = std::make_unique<AutoDepositWorker>();howDo(autoDepositWorker);std::cout << "扩展业务调用完成! " << std::endl;
}int main()
{main001();main002();main003();return 0;
}

运行效果

 

6)里氏替换原则LSP(Liskov Substitution Principle)

如果 S 是 T 的子类型,那么在程序中的对象类型 T 替换为 S 时,程序的行为不应发生变化。换句话说,子类对象应当能够替换掉父类对象,而不改变程序的正确性。其核心思想是:子类对象必须能够替换父类对象而不影响程序的正确性。

LSP的好处:

  1. 增强可替换性:子类可以替代父类,增加了系统的灵活性和可扩展性。
  2. 提高可维护性:符合 LSP 的系统设计更容易进行维护和扩展,因为子类与基类之间的关系更明确。
  3. 减少错误:遵循 LSP 可以避免在使用子类时出现意外的错误或不一致的行为。
  4. 增强可靠性:系统的行为在替换子类时保持一致,提高了系统的可靠性。

实现LSP的常用方法:

  1. 确保子类的行为符合父类的契约:子类应当遵循父类的接口,不改变父类方法的预期行为。
  2. 避免在子类中抛出异常:子类的方法不应抛出父类方法未预料的异常。
  3. 维护一致性:子类应当在行为和状态上与父类一致,不应引入新的约束或改变已有的约束。
  4. 使用抽象类或接口:当有多个子类有不同的行为时,可以使用抽象类或接口来定义通用的功能,避免将所有功能放在一个类中。

LSP的示例:

创建了一个模板类 Processor,它可以用于处理任何实现了 DepositInterfaceTransferAccountsInterface 或 PayInterface 的对象。为了使代码保持一致性,为每个接口类型专门化了 Processor 类。这样可以确保 Processor 只依赖于基接口,并且所有实现类都可以按需替换而不会改变程序的行为。

lsp.cpp

#include <iostream>
#include <memory>// 业务接口
class DepositInterface
{
public:virtual ~DepositInterface() = default;virtual void deposit() const = 0;
};class TransferAccountsInterface
{
public:virtual ~TransferAccountsInterface() = default;virtual void transferAccounts() const = 0;
};class PayInterface
{
public:virtual ~PayInterface() = default;virtual void pay() const = 0;
};// 具体实现类
class DepositWorker : public DepositInterface
{
public:void deposit() const override{std::cout << "存款: " << std::endl;}
};class TransferAccountsWorker : public TransferAccountsInterface
{
public:void transferAccounts() const override{std::cout << "转账: " << std::endl;}
};class PayWorker : public PayInterface
{
public:void pay() const override{std::cout << "缴费: " << std::endl;}
};// 扩展业务实现类
class AutoDepositWorker : public DepositInterface
{
public:void deposit() const override{std::cout << "批量存款: " << std::endl;}
};class AutoTransferAccountsWorker : public TransferAccountsInterface
{
public:void transferAccounts() const override{std::cout << "批量转账: " << std::endl;}
};class AutoPayWorker : public PayInterface
{
public:void pay() const override{std::cout << "批量缴费: " << std::endl;}
};// 业务处理器
template <typename T>
class Processor
{
public:Processor(std::unique_ptr<T> impl) : impl(std::move(impl)) {}void process() const{impl->execute();}private:std::unique_ptr<T> impl;
};// 处理器特化
template <>
class Processor<DepositInterface>
{
public:Processor(std::unique_ptr<DepositInterface> depositImpl): depositImpl(std::move(depositImpl)) {}void process() const{depositImpl->deposit();}private:std::unique_ptr<DepositInterface> depositImpl;
};template <>
class Processor<TransferAccountsInterface>
{
public:Processor(std::unique_ptr<TransferAccountsInterface> transferImpl): transferImpl(std::move(transferImpl)) {}void process() const{transferImpl->transferAccounts();}private:std::unique_ptr<TransferAccountsInterface> transferImpl;
};template <>
class Processor<PayInterface>
{
public:Processor(std::unique_ptr<PayInterface> payImpl): payImpl(std::move(payImpl)) {}void process() const{payImpl->pay();}private:std::unique_ptr<PayInterface> payImpl;
};// 传统的业务调用
void main001()
{Processor<DepositInterface> depositProcessor(std::make_unique<DepositWorker>());Processor<TransferAccountsInterface> transferProcessor(std::make_unique<TransferAccountsWorker>());Processor<PayInterface> payProcessor(std::make_unique<PayWorker>());depositProcessor.process();transferProcessor.process();payProcessor.process();std::cout << "传统的业务调用完成! " << std::endl;
}// 扩展业务调用
void main002()
{Processor<DepositInterface> depositProcessor(std::make_unique<AutoDepositWorker>());Processor<TransferAccountsInterface> transferProcessor(std::make_unique<AutoTransferAccountsWorker>());Processor<PayInterface> payProcessor(std::make_unique<AutoPayWorker>());depositProcessor.process();transferProcessor.process();payProcessor.process();std::cout << "扩展业务调用完成! " << std::endl;
}int main()
{main001();main002();return 0;
}

运行效果

 

7)优先使用组合,而非继承原则(Favor Composition over Inheritance)

在设计类的结构时,应该优先考虑使用对象组合的方式来实现功能,而不是通过继承来扩展类的功能。

组合(Composition) 指的是通过将一个类的实例作为另一个类的成员变量来实现功能的复用。例如,通过组合将一个对象的功能嵌入到另一个对象中,从而实现扩展和复用。

继承(Inheritance) 是通过创建一个新类(子类)来扩展或修改已有类(父类)的功能。子类继承父类的所有公共属性和方法,并可以覆盖或扩展它们。

使用组合的好处: 

  1. 减少耦合:组合使得类之间的关系更加松散,减少了对父类实现细节的依赖,从而降低了类之间的耦合度。
  2. 增强灵活性:通过组合可以灵活地改变对象的行为或功能,而不需要修改现有的类。它使得系统可以在运行时动态地改变对象的行为。
  3. 提高可维护性:组合比继承更容易进行修改和维护,因为更改一个组件的实现通常不会影响到其他组件。
  4. 避免“继承深度”问题:继承可能导致深层次的继承链,使得系统复杂度增加,而组合可以避免这种情况。

 判断是否适合组合的常用方法:

  1. 识别可复用的功能:分析系统中需要复用的功能,确定是否可以通过组合来实现这些功能,而不是通过继承。
  2. 使用接口和抽象类:通过接口或抽象类定义功能的契约,然后通过组合将具体的实现注入到类中。
  3. 设计模块化的组件:将功能划分为独立的组件,这些组件可以被组合在一起以实现复杂的功能。
  4. 避免过度使用继承:继承应该仅用于表示“是一个”(is-a)关系,而不是“有一个”(has-a)关系。对于“有一个”关系,组合通常是更合适的选择。

组合的示例:

  • 函数对象:在Processor中使用std::function来传递具体的业务逻辑。这使得Processor不再依赖具体的实现类。
  • 业务逻辑:在main001main002中,直接定义了业务逻辑的函数对象,这样可以根据需要在代码中灵活地调整行为,而不需要创建新的类。

composition.cpp

#include <iostream>
#include <memory>
#include <functional>// 业务接口
class DepositInterface
{
public:virtual ~DepositInterface() = default;virtual void deposit() const = 0;
};class TransferAccountsInterface
{
public:virtual ~TransferAccountsInterface() = default;virtual void transferAccounts() const = 0;
};class PayInterface
{
public:virtual ~PayInterface() = default;virtual void pay() const = 0;
};// 业务处理器
template <typename T>
class Processor
{
public:Processor(std::function<void()> func) : func(func) {}void process() const{func();}private:std::function<void()> func;
};// 传统的业务调用
void main001()
{Processor<DepositInterface> depositProcessor([]() {std::cout << "存款: " << std::endl;});Processor<TransferAccountsInterface> transferProcessor([]() {std::cout << "转账: " << std::endl;});Processor<PayInterface> payProcessor([]() {std::cout << "缴费: " << std::endl;});depositProcessor.process();transferProcessor.process();payProcessor.process();std::cout << "传统的业务调用完成! " << std::endl;
}// 扩展业务调用
void main002()
{Processor<DepositInterface> depositProcessor([]() {std::cout << "批量存款: " << std::endl;});Processor<TransferAccountsInterface> transferProcessor([]() {std::cout << "批量转账: " << std::endl;});Processor<PayInterface> payProcessor([]() {std::cout << "批量缴费: " << std::endl;});depositProcessor.process();transferProcessor.process();payProcessor.process();std::cout << "扩展业务调用完成! " << std::endl;
}int main()
{main001();main002();return 0;
}

运行效果

 

四、设计模式经典之作

ps:《Design Patterns: Elements of Reusable Object-Oriented Software》一书是一本经典设计模式的作品,推荐去看看。"Gang of Four"书名来源于书籍的四位作者,他们分别是:

  1. Erich Gamma(艾里希·伽玛)
  2. Richard Helm(理查德·赫尔姆)
  3. Ralph Johnson(拉尔夫·约翰逊)
  4. John Vlissides(约翰·弗利西德斯)

这四位作者在书中总结并分类了设计模式,这本书在软件工程领域中具有重要的影响力,因此他们被戏称为《Gang of Four》。这个名字传达了他们在设计模式领域的权威地位,并且也带有一种略显俏皮的意味。

相关文章:

C++的7种设计模式原则

一、设计模式前言 设计模式&#xff08;Design Patterns&#xff09;的“模式”指的是一种在软件设计中经过验证的、解决特定问题的方案。它们不是具体的代码&#xff0c;而是解决常见设计问题的抽象方案或模板。设计模式提供了一种标准的方式来组织代码&#xff0c;以提高代码…...

24.8.5数据结构|栈

栈-弹夹 1、定义&#xff1a; 栈就是特殊的线性表&#xff0c;与之前的线性表的区别就是增加了约束&#xff0c;只允许在一端插入和删除&#xff0c;就这麽简单。 2、基本操作 栈的插入操作叫&#xff1a;入栈{进栈、压栈}&#xff1b;栈的删除&#xff1a;出栈{退栈&#x…...

LeetCode算法题训练

力扣刷题训练 开始记录力扣的刷题之路 刷题思路来自灵茶山艾府 入门题单&#xff1a; 「新」动计划 编程入门编程基础 0 到 1 训练方法 A 滑动窗口&#xff08;定长/不定长/多指针&#xff09;二分算法&#xff08;二分答案/最小化最大值/最大化最小值/第K小&#xff09…...

Python | Leetcode Python题解之第326题3的幂

题目&#xff1a; 题解&#xff1a; class Solution:def isPowerOfThree(self, n: int) -> bool:return n > 0 and 1162261467 % n 0...

手机CPU性能天梯图(2024年8月),含安兔兔/GB6/3DMark跑分

原文地址&#xff08;高清无水印原图/持续更新/含榜单出处链接&#xff09;&#xff1a; 2024年8月手机处理器天梯图 2024年8月1日更新日志&#xff1a;由于近期并未有新处理器发布&#xff0c;故只做常规更新&#xff1b;移除鲁大师天梯图&#xff1b;补充其它天梯图数量。 -…...

通过实际的例子和代码演示,可以更好地理解 `optional` 的使用方式和应用场景

当然&#xff0c;让我们通过一些实际的例子来演示 std::optional 的使用方式和应用场景。 场景 1&#xff1a;函数返回值 假设我们有一个函数&#xff0c;它尝试从字符串中解析一个整数&#xff0c;但如果字符串不是一个有效的整数&#xff0c;我们希望返回一个错误状态。 #…...

Java 电商秒杀系统优化实战:实现进阶示例详解与 RabbitMQ 配置

上一篇博客介绍了使用消息队列、异步处理等技术构建 Java 电商秒杀系统的基本思路&#xff0c;本文将进一步优化代码实现&#xff0c;并提供更详细的代码示例和 RabbitMQ 配置&#xff0c;助您构建更健壮、高效的秒杀系统。 一、 代码优化 1. 接口限流 在 SeckillController…...

路径规划 | 基于狼群算法的无人机路径规划(Matlab)

目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 路径规划 | 基于狼群算法的无人机路径规划&#xff08;Matlab&#xff09; 狼是一种群居性动物&#xff0c;社会分工明确&#xff0c;通过承担各自的责任与团结协作&#xff0c;共同促进整个狼群的生存与发展。狼群算…...

13-python函数返回值和装包的后续提取数据方法——解包

1.1 参数解包 不定长参数简单来讲就是装包&#xff0c;把多个参数装到一个元组或者装到字典中&#xff0c;就叫做装包 Ctrld可以快速向下复制 传递实参时&#xff0c;也可以在序列类型的参数前添加星号&#xff0c;这样他会自动将序列中的元素依次作为参数传递 注意&#x…...

I. 对线

https://codeforces.com/gym/103186/problem/I 一开始感觉操作挺复杂的 但是写过Chino的数列 - 洛谷 发现可以通过矩阵来实现swap操作&#xff0c;就想能不能用线段树维护矩阵来写 有三排兵线&#xff0c;我们维护区间和&#xff0c;因此初始矩阵就有了 接下来分析每个操作的…...

Topsis法模型(评价类问题)

目录 本文章内容参考&#xff1a; 一. 概念 二. 特点和适用范围 三. 实现步骤 四. 代码实现 本文章内容参考&#xff1a; TOPSIS法模型讲解(附matlab和python代码) 【数学建模快速入门】数模加油站 江北_哔哩哔哩_bilibili 一. 概念 TOPSIS&#xff08;Technique for O…...

HPA 与pod调度

HPA 自动更新工作负载资源&#xff08;例如 Deployment 或者 StatefulSet&#xff09;&#xff0c; 目的是自动扩缩工作负载以满足需求。 绑定到deploy上&#xff0c;控制pod 依托于metrics-server HorizontalPodAutoscaler 水平pod自动扩缩&#xff1a;意味着对增加的负…...

jupyter下载

https://blog.csdn.net/qq_48372575/article/details/125630622 我下面是CPU运行的&#xff0c;GPU链接在上面 Anaconda下载 https://docs.anaconda.com/miniconda/miniconda-other-installer-links/ 参考链接&#xff1a; https://blog.csdn.net/qq_48372575/article/detai…...

蓝桥杯双周赛 第 16 场 小白入门赛 解题报告 | 珂学家 | 七夕娱乐场

前言 题解 因为这场七夕节&#xff0c;所以出的特别友好。 整体还是偏思维。 T6 额外提供组合数学解&#xff0c;还是蛮有趣的。 A. 喜鹊罢工 题型: 签到 365 可以有多少个 7 组成 365可以有多少个7组成 365可以有多少个7组成 向上取整即可 #include <iostream>usi…...

[C++] 深入理解面向对象编程特性 : 继承

文章目录 继承的概念与定义继承的定义定义格式不同继承方式与继承的基类中访问限定符间的影响C中的继承和访问控制总结父类的private成员在子类中的访问限制protected成员的使用场景成员访问方式总结继承方式的默认值实际应用中的继承方式 示例代码 OOP中类之间的关系“is a” …...

汇昌联信科技做拼多多电商怎么引流?

在互联网经济高速发展的今天&#xff0c;电商平台如雨后春笋般涌现&#xff0c;其中拼多多以其独特的社交电商模式迅速崛起。对于汇昌联信科技而言&#xff0c;如何在拼多多平台上有效引流&#xff0c;成为提升销量和品牌知名度的关键。本文将深入探讨汇昌联信科技在拼多多电商…...

公网ip和私网ip的区别

1.接入方式不同\n公网IP以公网连接Internet上的非保留地址&#xff0c;私网IP则是局域网上的IP&#xff0c;通过NAT才能够与公网进行通信。 2.特点不同\n公网IP由国际互联网络信息中心InterNIC负责,将IP地址分配给注册并向InterNIC提出申请的机构或组织。私网IP则是为节省可分…...

【开发踩坑】windows查看jvm gc信息

windows查看jvm gc信息 EZ 找出java进程PID 控制面板----搜索任务管理器---- 任务管理器----搜索 java----详细信息 这里PID是4856 cmd jstat gc面板 reference&#xff1a; jstat命令...

时间序列预测 | CEEMDAN+CNN+Transformer多变量时间序列预测(Python)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 时间序列预测 | CEEMDANCNNTransformer多变量时间序列预测&#xff08;Python&#xff09; 时间序列预测 创新点 多尺度特征提取&#xff1a;CEEMDAN将复杂的时间序列分解成多个IMFs&#xff0c;使得CNN和Transforme…...

vue3--实现vue2插件JSONPathPicker的路径获取功能

背景 最近在进行vue2项目升级为vue3。 在项目中需要获取json某些字段的路径&#xff0c;vue2中使用JSONPathPicker &#xff0c;但是该插件不支持vue3&#xff0c;vue3中也没有相应的模块有该功能。 实现目标&#xff1a; 原vue2中JSONPathPicker实现的功能&#xff1a; 查…...

SuccBI+低代码文档中心 — 可视化分析(仪表板)(上)

有关仪表板的设计器&#xff1a; 查询设置 由于仪表板的设计器是所见即所得的&#xff0c;可以将当前制作的内容和数据的查询结果实时展示在界面中&#xff0c;当引入到仪表板的模型数据量较大时&#xff0c;为了提高设计器界面的查询性能&#xff0c;提供了以下两种方法&…...

P3156 【深基15.例1】询问学号

昨天我发布了关于数据结构线性表的学习知识&#xff08;【数据结构】顺序表-CSDN博客&#xff09;。所谓“纸上得来终觉浅”&#xff0c;光看不练可不行&#xff0c;下面我们来看一下顺序表的习题。 题目链接 【深基15.例1】询问学号 - 洛谷 题目解读 题目描述了一个场景&…...

详解Xilinx FPGA高速串行收发器GTX/GTP(5)--详解8B10B编解码

目录 1、8B/10B编码是什么? 2、8B/10B编码的规则 3、两个例子 4、GTX的8B/10B编码 5、Verilog实现 文章总目录点这里:《FPGA接口与协议》专栏的说明与导航 1、8B/10B编码是什么? 简单来说,8B/10B编码就是将原本是8bits的数据,按照一定的规则扩展编码到10b…...

python 画多盘的写放大曲线方法

在服务器测试中我们经常会遇见客户要求画出每个SSD的WAF曲线&#xff0c;也就是写放大&#xff0c;通常的做法就是我们每隔10分钟记录一下每个SSD的host写入量和nand写入量&#xff0c;下面我们介绍一下python处理多盘的WAF的做法 如图所示 假设这是一个记录多盘的写入量信息的…...

计算机网络TCP/UDP知识点

这是一些在学习过程中关于计算机网络八股文的一些知识点记录&#xff1a; TCP/UDP TCP怎么保证可靠性 1.序列号&#xff0c;确认应答&#xff0c;超时重传 数据到达接收方&#xff0c;接收方需要发出一个确认应答&#xff0c;表示已经收到该数据段&#xff0c;并且确认序号…...

JavaScript 文档元素获取

目录 通过id获取文档元素 任务描述 相关知识 什么是DOM 文档元素 节点树 通过id获取文档元素 编程要求 通过类名获取文档元素 任务描述 相关知识 通过类名获取文档元素 编程要求 通过标签名获取文档元素 任务描述 相关知识 通过标签的名字获取文档元素 获取标…...

docker pull实现断点续传

问题背景 在使用Docker拉取DockerHub的镜像时&#xff0c;经常会出现网络不稳定的问题&#xff0c;这就导致拉取到一半的镜像会重新拉取&#xff0c;浪费时间。例如下面这种情况&#xff1a; 第二次拉取 这是一个网络中断的场景&#xff0c;第二次重新拉取的时候&#xff0c;同…...

无字母数字webshell之命令执行

源码 题目限制&#xff1a; webshell长度不超过35位除了不包含字母数字&#xff0c;还不能包含$和_ 这里使用php5来解决 可以围绕以下两点展开&#xff1a; shell下可以利用.来执行任意脚本Linux文件名支持用glob通配符代替 .或者叫period&#xff0c;它的作用和source一样…...

华为OD笔试

机试总分400。三道题目。100&#xff0b;100&#xff0b;200 华为od考试时间为150分钟&#xff0c;共有三道编程题&#xff0c;分数分别为100、100和200。如果你是目标院校&#xff08;查看目标院校请戳&#xff09;的话&#xff0c;及格线是160分&#xff0c;非目标院校则不确…...

HAProxy理论+实验

目录 一、基于cookie的会话保持 1、配置选项 2、配置示例 3、验证cookie信息 二、IP透传 1、layer4 与 layer7 &#xff08;1&#xff09;四层:IPPORT转发 &#xff08;2&#xff09;七层:协议内容交换 三、haproxy的ACL应用 1、ACL配置选项 &#xff08;1&#xf…...