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》。这个名字传达了他们在设计模式领域的权威地位,并且也带有一种略显俏皮的意味。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/396887.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为JetBrains IDE设置自定义TODO筛选器(筛选指定的关键字)和Live Templates

为JetBrains IDE设置自定义TODO筛选器&#xff08;筛选指定的关键字&#xff09;和Live Templates 以下内容以搜索关键字 // TODO Zzz 为例&#xff0c;不区分大小写&#xff0c;可以将模板中的 Zzz 换成其他内容。 设置自定义TODO筛选器 在IDE设置中找到TODO选项&#xff0…

AWS注册是否必须使用美元银行卡

亚马逊网络服务(AWS)作为全球领先的云计算平台,吸引了众多企业和个人用户。然而,不少人在注册AWS账户时会产生疑问:是否必须使用美元银行卡?实际上,这种说法并不准确。虽然AWS的主要结算货币是美元,但用户在注册和使用过程中有多种支付方式可供选择。我们结合九河云的分析来告…

程序员前端开发者的AI绘画副业之路:在裁员危机中寻找新机遇

正文&#xff1a; 在这个充满变数的时代&#xff0c;作为一名前端开发者&#xff0c;我经历了行业的起伏&#xff0c;见证了裁员危机和中年失业危机的残酷。在这样的背景下&#xff0c;我开始了利用AI绘画作为副业的探索&#xff0c;不仅为了寻求经济上的稳定&#xff0c;更是为…

DLMS/COSEM中的信息安全:安全密钥(下)

2.5组件B终端实体证书类型要由DLMS/COSEM服务器支持 每个DLMS/COSEM服务器应使用X.509 v3格式&#xff0c;并包含以下任一项&#xff1a; ——具有P-256或P-384 ECDSA功能的签名密钥&#xff1b;或 ——具有P-256或P-384 ECDSA功能的密钥协商密钥。 每张证书均应使用ECDSA进行签…

lvs(linux virtual server)实例

一.lvs概述 1.1什么是lvs LVS&#xff08;Linux Virtual Server&#xff09;是一个基于Linux操作系统的虚拟服务器技术&#xff0c;用于实现负载均衡和高可用性。LVS通过将客户端的请求分发到多台后端服务器上&#xff0c;从而提高整体服务的处理能力和可靠性。LVS主要有两个组…

蓝桥杯 双周赛 第16场 小白赛 题目复盘 (2024年8月10日)

3. 织女的考验 下面的代码看似很正确&#xff0c;但忽略了一个细节&#xff0c;下面判断是否有字母数量不同时&#xff0c;用abs(cnt[i]) 1判断&#xff0c;这里忽略了abs(cnt[i]) 等于其他值的情况(YES和NO都存在) #include <iostream> #include <cstring> usi…

java: 程序包org.springframework.boot.autoconfigure不存在

通过 mvn -U idea:idea 命令重新加载maven包&#xff0c;具体操作是这样的&#xff1a; 打开cmd窗口cd 到 工程根目录&#xff0c;比如我的工程是&#xff1a;D:\IdeaProjects\demo&#xff0c; 执行 mvn -U idea:idea 命令&#xff0c;完了以后重新运行项目就正常了&#xff…

《网络编程实战系列》(17)网络桥接模式

文章目录 **桥接模式的基本原理****桥接模式的应用场景****桥接模式的优缺点****桥接模式的实现****总结**桥接模式(Bridge Mode)是一种网络配置模式,用于将多个网络接口或网络段连接在一起,使其在逻辑上形成一个单一的网络。这种模式常用于在不同网络之间传递数据包,并使…

树与二叉树、图的基本概念

一、树与二叉树的基本概念和性质 1. 树的的性质 1&#xff09;树中的结点数 n 等于所有结点的度数之和加 1 【说明】结点的度是指该结点的孩子数量&#xff0c;每个结点与其每个孩子都由唯一的边相连&#xff0c;因此树中所有结点的度数之和等于树中的边数之和。树中的结点&…

pr涂鸦转场(喷漆涂鸦效果视频转场过渡效果)mogrt模板

https://prmuban.com/40353.html 主要特点&#xff1a; 与Adobe Premiere Pro 2024兼容 4K分辨率&#xff08;38402160&#xff09; 自动调整大小功能 快速渲染时间 无需额外插件 包括视频教程

240806-在Linux/RHEL开机中自动启动bash脚本

A. 常规方法 要在Red Hat Enterprise Linux (RHEL) 中设置开机启动的bash脚本&#xff0c;可以使用以下方法之一&#xff1a; 方法1&#xff1a;使用/etc/rc.d/rc.local 打开/etc/rc.d/rc.local文件&#xff1a; sudo vi /etc/rc.d/rc.local在文件末尾添加你想要执行的bash脚…

<Qt> 窗口

目录 一、Qt窗口 二、菜单栏 &#xff08;一&#xff09;创建菜单栏 &#xff08;二&#xff09;在菜单栏中添加菜单 &#xff08;三&#xff09;创建菜单项 &#xff08;四&#xff09;在菜单项之间添加分割线 &#xff08;五&#xff09;给菜单项添加槽函数 &#xf…

虚拟机和docker容器的区别

背景 我们知道虚拟机和docker容器中的进程都可以在独立的空间中运行&#xff0c;互不干扰&#xff0c;那么两者主要区别是什么呢 虚拟机和docker容器的区别 虚拟机主要是通过硬件模拟出来一个个操作系统&#xff0c;每个操作系统有自己的的文件和目录&#xff0c;网络设备等…

最新HTML设计搜索表单

设计搜索表单 页眉中包含表单&#xff0c;表单中只需包含label和Input. 实现如下效果&#xff1a;文本框动态变宽效果 代码&#xff1a;6.2.4.设计搜索表单.html <!DOCTYPE html> <html><head><meta charset"utf-8"><title></t…

第R2周:Tensorflow2实现:LSTM-火灾温度预测

本文为365天深度学习训练营 中的学习记录博客原作者&#xff1a;K同学啊 任务说明&#xff1a;数据集中提供了火灾温度&#xff08;Tem1&#xff09;、一氧化碳浓度&#xff08;CO 1&#xff09;、烟雾浓度&#xff08;Soot 1&#xff09;随着时间变化数据&#xff0c;我们需要…

Flip PDF Plus Corporate v7.1.25 激活版下载及安装步骤 (PDF电子书翻页)

前言 Flip PDF Plus Corporate 是一款功能强大的PDF翻页工具&#xff0c;也被称为名编辑电子杂志大师。它能够快速将PDF文件转换成具有翻页动画效果的电子书&#xff0c;同时保留原始的超链接和书签。该工具支持将相册、视频、音频、Flash、YouTube视频和链接等内容完全嵌入到…

OpenGL入门一:基础知识及概念

1、什么是OpenGL OpenGL&#xff1a;open graphic library&#xff0c;即开发图形库。它被定义为“图形硬件的一种软件接口”。实质上是3D图形和模型库&#xff0c;它具有高度可移植性&#xff0c;并且具有非常快的速度。可以创建优雅漂亮的3D图形&#xff0c;具有出色的视觉质…

vue3实现video视频+弹幕评论

vue3实现视频加评论 之前写了一篇博客使用了弹幕插件http://t.csdnimg.cn/616mlvue3 使用弹幕插件&#xff0c;今天对这个页面进行了升级 变成了 vue3使用video 这个没有使用插件&#xff0c;昨天看了好多&#xff0c;没发现有用的插件&#xff0c;下载了几个都没办法使用就用…

Linux C++ 多线程编程

Linux C 多线程编程 参考教程&#xff1a; c&#xff1a;互斥锁/多线程的创建和unique_lock&#xff1c;mutex&#xff1e;的使用_mutex 头文件 vcCSDN博客 1. 编写unique_mutex 1.1 创建文件夹 通过终端创建一个名为unique_mutex的文件夹以保存我们的VSCode项目&#xff…

基于HTML弹性布局做的支付宝界面

里面有一些语言图标&#xff0c;想用的可以去iconfont-阿里巴巴矢量图标库里面寻找&#xff0c;这类图标跟文字一样可以设置大小、文本居中之类的&#xff0c;并不算严格意义上的图片&#xff0c;废话不多说&#xff0c;直接上成果和代码 <!DOCTYPE html> <html lang&…