一、设计模式前言
设计模式(Design Patterns)的“模式”指的是一种在软件设计中经过验证的、解决特定问题的方案。它们不是具体的代码,而是解决常见设计问题的抽象方案或模板。设计模式提供了一种标准的方式来组织代码,以提高代码的重用性、可维护性和可扩展性。
设计模式通常包括以下几个方面:
- 问题(Problem):描述在特定上下文中需要解决的设计问题。
- 解决方案(Solution):提供一种或多种解决该问题的方法。
- 效果(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常用方法:
-
使用抽象和接口:通过抽象类或接口定义模块的核心功能,然后使用具体的实现类来扩展功能。这样,你可以在不修改原有代码的情况下,添加新的实现类来扩展功能。
-
使用设计模式:
- 策略模式(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常用方法:
-
使用接口或抽象类:将高层模块和低层模块的依赖关系抽象化。例如,定义一个接口或抽象类,低层模块实现这个接口,而高层模块依赖于这个接口。
-
依赖注入(Dependency Injection):通过构造函数注入、方法注入或属性注入等方式,将依赖项传递给需要它们的类,从而使高层模块和低层模块之间的依赖关系变得松散。
DIP的示例:
-
Bankworker类依赖于
DepositInterface、
TransferAccountsInterface
和PayInterface
接口,而不是具体的实现类。这样,BankWorker
可以使用任何实现了这些接口的类。 -
具体实现类仍然是
DepositWorker
、TransferAccountsWorker
和PayWorker
。这些实现类可以独立开发和测试,不需要修改BankWorker
。 -
依赖注入则通过构造函数将具体的实现类传递给
BankWorker
。这样,BankWorker
不再直接创建具体的实现对象,而是通过接口来使用它们。 -
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的核心要点:
-
每个对象应当只与其直接的朋友对象通信。换句话说,方法只能调用:
- 对象的自身的方法。
- 作为参数传递进来的对象的方法。
- 对象创建的(例如,通过构造函数或工厂方法)对象的方法。
- 对象的成员变量(成员变量应当是该类的内部实现的一部分)。
-
避免链式调用。即对象 A 不应直接调用对象 B 的方法,然后再通过对象 B 调用对象 C 的方法。这样会使得对象 A 对对象 C 的实现细节产生依赖,从而增加耦合度。
实现LoD常用方法:
-
封装数据:将数据和操作数据的方法封装在一个类中,尽量减少外部对这些数据的直接访问。
-
使用接口:通过接口定义模块之间的交互,而不是直接操作对象的内部状态。
-
控制方法访问范围:尽量将方法和数据的访问范围控制在最小的范围内。例如,使用私有(private)或保护(protected)访问修饰符来限制访问
LoD的示例:
1、设置函数howDo(const T& obj)为操作对象的引用(或指针),避免直接对具体实现类的操作,
2、设置BankWorker
设计为只与接口 DepositInterface
、TransferAccountsInterface
和 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的好处:
- 提高代码的可维护性:如果一个类只承担一个职责,当这个职责发生变化时,只需要修改这个类,减少了对其他部分的影响。
- 增强代码的可读性:单一职责的类通常更小、更专注,易于理解和阅读。
- 提高代码的可重用性:职责单一的类可以在其他项目中更容易地复用,因为它的功能和行为更明确。
- 简化测试:职责明确的类更容易进行单元测试,因为它们的行为更集中和专一
SRP的示例:
-
分离处理类:
BankWorker
被拆分为DepositProcessor
、TransferProcessor
和PayProcessor
。每个处理器类负责一种业务操作的处理。 -
具体业务类:具体的实现类(如
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的好处:
- 减少耦合:当接口被拆分成多个小接口时,每个客户端只依赖于它所需要的部分,减少了不必要的依赖。
- 提高灵活性:系统中的类只实现它们需要的接口,易于进行修改和扩展。
- 增强可维护性:小的接口通常更简单,易于理解和维护。
- 提升可重用性:小接口具有明确的责任,可以在不同的上下文中重用。
接口隔离原则的主要目标是避免“胖接口”(fat interface),即一个接口包含过多的方法。
实现ISP的常用方法:
- 分析接口的职责:识别接口中的不同职责,将它们拆分成多个小的接口。
- 定义专注的接口:创建专注于单一功能的小接口,以减少不必要的依赖。
- 采用组合:通过接口的组合来实现复杂的功能,而不是将所有功能放在一个接口中。
- 避免接口膨胀:避免将过多的方法放在一个接口中,保持接口的简单性和专注性。
ISP的示例:
-
接口设计:将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的好处:
- 增强可替换性:子类可以替代父类,增加了系统的灵活性和可扩展性。
- 提高可维护性:符合 LSP 的系统设计更容易进行维护和扩展,因为子类与基类之间的关系更明确。
- 减少错误:遵循 LSP 可以避免在使用子类时出现意外的错误或不一致的行为。
- 增强可靠性:系统的行为在替换子类时保持一致,提高了系统的可靠性。
实现LSP的常用方法:
- 确保子类的行为符合父类的契约:子类应当遵循父类的接口,不改变父类方法的预期行为。
- 避免在子类中抛出异常:子类的方法不应抛出父类方法未预料的异常。
- 维护一致性:子类应当在行为和状态上与父类一致,不应引入新的约束或改变已有的约束。
- 使用抽象类或接口:当有多个子类有不同的行为时,可以使用抽象类或接口来定义通用的功能,避免将所有功能放在一个类中。
LSP的示例:
创建了一个模板类 Processor
,它可以用于处理任何实现了 DepositInterface
、TransferAccountsInterface
或 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) 是通过创建一个新类(子类)来扩展或修改已有类(父类)的功能。子类继承父类的所有公共属性和方法,并可以覆盖或扩展它们。
使用组合的好处:
- 减少耦合:组合使得类之间的关系更加松散,减少了对父类实现细节的依赖,从而降低了类之间的耦合度。
- 增强灵活性:通过组合可以灵活地改变对象的行为或功能,而不需要修改现有的类。它使得系统可以在运行时动态地改变对象的行为。
- 提高可维护性:组合比继承更容易进行修改和维护,因为更改一个组件的实现通常不会影响到其他组件。
- 避免“继承深度”问题:继承可能导致深层次的继承链,使得系统复杂度增加,而组合可以避免这种情况。
判断是否适合组合的常用方法:
- 识别可复用的功能:分析系统中需要复用的功能,确定是否可以通过组合来实现这些功能,而不是通过继承。
- 使用接口和抽象类:通过接口或抽象类定义功能的契约,然后通过组合将具体的实现注入到类中。
- 设计模块化的组件:将功能划分为独立的组件,这些组件可以被组合在一起以实现复杂的功能。
- 避免过度使用继承:继承应该仅用于表示“是一个”(is-a)关系,而不是“有一个”(has-a)关系。对于“有一个”关系,组合通常是更合适的选择。
组合的示例:
- 函数对象:在
Processor
中使用std::function
来传递具体的业务逻辑。这使得Processor
不再依赖具体的实现类。 - 业务逻辑:在
main001
和main002
中,直接定义了业务逻辑的函数对象,这样可以根据需要在代码中灵活地调整行为,而不需要创建新的类。
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"书名来源于书籍的四位作者,他们分别是:
- Erich Gamma(艾里希·伽玛)
- Richard Helm(理查德·赫尔姆)
- Ralph Johnson(拉尔夫·约翰逊)
- John Vlissides(约翰·弗利西德斯)
这四位作者在书中总结并分类了设计模式,这本书在软件工程领域中具有重要的影响力,因此他们被戏称为《Gang of Four》。这个名字传达了他们在设计模式领域的权威地位,并且也带有一种略显俏皮的意味。