设计模式 Posted on 2023-01-09 22:53:17 2023-04-13 23:29:45 by Author 摘要 本文简单介绍了7种设计模式的相关知识,用于个人理解,之后慢慢的对这其中的每一种设计模式单独详细介绍自己的理解 ### 设计模式 #### 面向对象三大特征 ##### 封装 - 隐藏内部实现 ##### 继承 - 复用现有代码 ##### 多态 - 改写对象行为 ##### 面向对象的再认识 - 从宏观层面来看,面向对象构建方式能更加适应软件的变化,能够将变化带来的影响减为最小。 - 从微观方面来看,面向对象的构建方式更加强调各个类的责任,各负其责。 - 语言层面看,对象封装 代码和数据 - 从规格层面看,对象是一系列可被使用的公共接口。 - 从概念层面讲,对象是某种拥有责任的抽象。 #### 面向对象设计原则 ##### 依赖倒置原则 - 高层模块(稳定)不应该依赖底层模块(变化),二者都应该依赖于抽象(稳定) - 抽象(稳定)不应该依赖于实现细节(变化),实现细节(变化)不应该依赖于抽象(稳定) ##### 开发封闭 原则(OCP) - 对扩展开放,对更改封闭。 - 类模块应该是可扩展的,但是不可修改。 ##### 单一职责原则(SRP) - 一个类应该仅有一个引起它变化的原则 - 变化的方向隐含着类的原则 ##### 替换原则 - 子类必须能够替换他们的基类(IS-A) - 继承表达类型抽象 ##### 接口隔离原则(ISP) - 不应该强迫客户依赖它们不用的方法 - 接口应该小而完备 ##### 优先使用对象组合,而不是类继承 - 类继承通常为白箱复用,对象组合通常为黑箱复用。 - 继承在某种程度上破坏了封装性,子类父类耦合度较高 - 对象组合只要求被组合的对象具有良好的接口,耦合度较低 ##### 封装变化点 - 使用封装来创建对象之间的分界层,一个稳定,一个变化 ##### 针对接口编程,而不是针对实现编程原则 - 不将变量类型声明为某个特定的具体类,而是声明为某个接口。 - 客户程序无需获取对象的具体类型,只需要知道对象具有的接口。 - 减少系统各部分依赖关系,从而实现高内聚,低耦合类型设计方案。 #### 模板模式 ##### 模式分类 - 组件协作 - 单一职责 - 对象创建 - 对象性能 - 接口隔离 - 状态变化 - 数据结构 - 行为变化 - 领域问题 ##### 重构关键方法 - 静态----动态 - 早绑定---晚绑定 - 继承---组合 - 编译时依赖---运行时依赖 - 紧耦合---松耦合 #### 策略模式 ##### 动机 - 软件构建过程中,某些对象使用的算法可能多种多样,经常变化,如果将这些算法都编码到对象中,将会使得对象变得复杂,而且有时候支持不使用算法也是一个性能负担。 - 根据需要透明的更改对象的算法,将算法和对象本身解耦,从而避免上述问题。 ```c++ class TaxStrategy{ public: virtual double Caculate(const Context& context)=0; virtual ~TaxStrategy(){}; }; }; class SaleOrder{ private: TaxStrategy* strategy; public: SaleOrder(StrateFactory* strateFactory){ this->strategy = strateFactory; } ~SaleOrder(){ delete this->strategy; } public double CalculateTax(){ //.... Context context(); double val = strategy->Calculate(contex); //.... } }; class CNTax:public TaxStrategy{ public: virtual double Caculate(const Context& context){ //*********; } }; ``` #### 观察者模式 ##### 动机 - 在软件构建过程中,我们需要为某些对象建立一种 通知依赖关系-----一个对象的状态发送改变,所有的依赖对象(观察者对象)都能够将得到通知,如果这样的依赖关系过于紧密,将软件不能很好的抵御变化。 - 使用面向对象技术,可以将这种依赖关系弱化,形成一种稳定的依赖关系,实现软件体系松耦合。 ```c++ class MainForm :public Form,public IProgress { TextBox* txtFilePath;//Form,TextBox是一个类,还未定义 TextBox* txtFileNumber; ProgressBar* progressBar; public: void Button_Click() { string filePath = txtFilePath->getText(); int number = atoi(txtFileNumber->getTEXT().c_str); FileSplitter splitter(filePath, number); splitter.add_IProgress(this); ConsoleNotifier cn; splitter.add_IProgress(&cn); splitter.split(); splitter.remove_IProgress(&cn); } virtual void DoProgress(float value) { progressBar->setValue(value); } }; class IProgress { public: virtual void DoProgress(float value) = 0; virtual ~IProgress() {} }; class ConsoleNotifier :public IProgress { public: virtual void DoProgress(float value) { cout << "."; } }; class FileSplitter { string m_filePath; int m_fileNumber; list<IProgress*> m_progressBar;//抽象的通知机制 public: FileSplitter(const string& filePath, int fileNumber) : m_filePath(filePath), m_fileNumber(fileNumber) {} void split() { for (int i = 0; i < m_fileNumber; ++i) { //... onProgress(0.0); } } void add_IProgress(IProgress * ipgrogress) { m_progressBar.push_back(ipgrogress); } void remove_IProgress(IProgress* iprogress) { m_progressBar.remove(iprogress); } private: void onProgress(float value) { auto start = m_progressBar.begin(); auto end = m_progressBar.end(); while (start != end) { (* start)->DoProgress(value); start++; } } }; ``` #### 装饰器模式 ##### 动机 - 在某些情况下,我们可能会过度地使用继承来扩展对象的功能,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类增大(扩展功能的增多),各种子类的组合会导致更多子类的膨胀。 - 如何使"对象功能的扩展"能够根据需求来动态实现,同时避免扩展功能的增多带来的子类膨胀问题,使得任何”功能扩展变化“所导致的影响降为最低  改变后:  ~~~c++ class Stream { public: virtual char Read(int number) = 0; virtual void Seek(int position) = 0; virtual void Write(char data) = 0; virtual~Stream() {} }; class FileStream :public Stream { public: virtual char Read(int number){ // } virtual void Seek(int position) { // } virtual void Write(char data) { // } }; class MemoryStream :public Stream { public: virtual char Read(int number) { // } virtual void Seek(int position) { // } virtual void Write(char data) { // } }; class DecoratorStream :public Stream { protected: Stream* stream; DecoratorStream(Stream* stm) :stream(stm) {} }; class CryptoStream :public DecoratorStream { public: CryptoStream(Stream* stm) :DecoratorStream(stm) {} virtual char Read(int number) { // stream->Read(number); } virtual void Seek(int position) { // stream->Seek(position); } virtual void Write(char data) { // stream->Write(data); } }; class BufferedStream :public DecoratorStream { public: BufferedStream(Stream* stm) :DecoratorStream(stm) { } virtual char Read(int number) { // stream->Read(number); } virtual void Seek(int position) { // stream->Seek(position); } virtual void Write(char data) { // stream->Write(data); } }; void Process() { FileStream* s1 = new FileStream(); CryptoStream* s2 = new CryptoStream(s1); BufferedStream* s3 = new BufferedStream(s1); BufferedStream* s4 = new BufferedStream(s2); MemoryStream* s1 = new MemoryStream(); BufferedStream* ss2 = new BufferedStream(s1); BufferedStream* ss3 = new BufferedStream(ss2); CryptoStream* ss4 = new CryptoStream(ss3); } ~~~ - 要点 - 通过采用组合而非继承的手法,Decorator模式实现在运行时动态扩展对象功能的能力,而且可以根据需求扩展多个功能,避免了使用继承带来的灵活性和多子类衍生问题。 - Decorator类在接口上表现为is-a Component的继承关系,即Decoarator类继承了Component类所具有的接口,但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。 - Decorator模式的目的并非解决多个子类衍生的多继承问题,Decorator模式应用的要点在于解决”主体在多个方向上的扩展功能"----是为“修饰"的含义 #### 桥模式 ###### 动机 - 由于某些类型的固有实现逻辑,使得它们具有两个变化的维度,乃至多个维度的变化 - 如何应对这种多维度的变化,如何使用面向对象技术使得类型可以轻松的沿着两个以及多个方向变化,从而不引入额外的复杂度。  改变前代码: ```c++ class Messager { public: virtual void Login(string username, string password) = 0; virtual void SendMessage(string message) = 0; virtual void SendPicture(Image image) = 0; virtual void PlaySound() = 0; virtual void DrawShape() = 0; virtual void Connect() = 0; virtual~Messager() {} }; class PCMessagerBase :public Messager { public: virtual void PlaySound(){} virtual void DrawShape() {} virtual void WriteText() = 0; virtual void Connect() {} }; class MobileMessageBase :public Messager { public: virtual void PlaySound() {} virtual void DrawShape() {} virtual void WriteText() = 0; virtual void Connect() {} }; //业务抽象 class PCMessagerLite : public PCMessagerBase { public: virtual void Login(string username, string password) { PCMessagerBase::Connect(); } virtual void SendMessage(string message) { PCMessagerBase::WriteText(); } virtual void SendPicture(Image image) { PCMessagerBase::DrawShape(); } }; class PCMessagerPerfect : public PCMessagerBase { public: virtual void Login(string username, string password) { PCMessagerBase::PlaySound(); PCMessagerBase::Connect(); } virtual void SendMessage(string message) { PCMessagerBase::PlaySound(); //***** PCMessagerBase::WriteText(); } virtual void SendPicture(Image image) { PCMessagerBase::DrawShape(); } }; class MobileMessagerLite :public MobileMessageBase { public: virtual void Login(string username, string password) { MobileMessageBase::Connect(); //**** } virtual void SendMessage(string message) { MobileMessageBase::PlaySound(); //***** MobileMessageBase::WriteText(); } virtual void SendPicture(Image image) { MobileMessageBase::DrawShape(); } }; class MobileMessagerPerfect :public MobileMessageBase { public: virtual void Login(string username, string password) { MobileMessageBase::Connect(); //**** } virtual void SendMessage(string message) { MobileMessageBase::PlaySound(); //***** MobileMessageBase::WriteText(); } virtual void SendPicture(Image image) { MobileMessageBase::DrawShape(); } }; void Process() { } ``` 改变后的代码 ```c++ class Messager { protected: MessagerImp* messagerImp; public: Messager(MessagerImp* imp) :messagerImp(imp) {} virtual void Login(string username, string password) = 0; virtual void SendMessage(string message) = 0; virtual void SendPicture(Image image) = 0; virtual~Messager() {} }; class MessagerImp { public: virtual void PlaySound() = 0; virtual void DrawShape() = 0; virtual void Connect() = 0; virtual void WriteText()=0; virtual~MessagerImp() {} }; class MobileMessageImp :public MessagerImp { virtual void PlaySound(){} virtual void DrawShape(){} virtual void Connect() {} virtual void WriteText() {} }; class MessagerLite :public Messager { public: MessagerLite(MessagerImp* imp) :Messager(imp) {} virtual void Login(string username, string password) { messagerImp->Connect(); //**** } virtual void SendMessage(string message) { messagerImp->PlaySound(); //***** messagerImp->WriteText(); } virtual void SendPicture(Image image) { messagerImp->DrawShape(); } }; class MessagerPerfect :public Messager { public: MessagerPerfect(MessagerImp* imp) :Messager(imp) {} virtual void Login(string username, string password) { messagerImp->Connect(); //**** } virtual void SendMessage(string message) { messagerImp->PlaySound(); //***** messagerImp->WriteText(); } virtual void SendPicture(Image image) { messagerImp->DrawShape(); } }; void Process() { MessagerImp* imp = new MobileMessageImp(); Messager* message = new MessagerPerfect(imp); message->Login("wew","sdfsf"); } ``` ##### 总结 - Bridge模式使用对象间的组合关系,解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各种维度的变化,即子类化它们。 - Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。 - Bridge模式应用一般在”两个非常强的变化维度",有时候一个类也有多于两个变化的维度,这时可以使用Bridge扩展模式。 #### 工厂模式 ##### 动机 - 在软件系统中,经常面临着创建对象的工作,由于需求的变化,需要创建的对象的具体类型经常变化。 - 如何应对这种变化,如何绕过常规的对象创建方法,提供一种封装机制,来避免程序和这种具体对象创建的工作的紧耦合?  ```c++ class ISplitter { public: virtual void split() = 0; virtual~ISplitter() {}; }; class BinarySplitter :public ISplitter { virtual void split() {}; }; class TxtSplitter :public ISplitter { virtual void split() {}; }; class PictureSplitter :public ISplitter { virtual void split() {}; }; class ViodeoSplitter :public ISplitter { virtual void split() {}; }; class MainForm :public Form { public: void Button_Click() { ISplitter* isp = new BinarySplitter();//依赖具体类,不好 isp->split(); } }; ``` 更改后 ```c++ class SplitterFactory { public: virtual ISplitter* CreateSplitter() = 0; virtual ~SplitterFactory() {} }; class MainForm2 :public Form { private: SplitterFactory* factory;//工厂 public: MainForm2(SplitterFactory* sp) { this->factory = sp; } void Button_Click() { ISplitter* sp = factory->CreateSplitter(); sp->split(); } }; class BinarySplitterFactory :public SplitterFactory { public: virtual ISplitter* CreateSplitter() { return new BinarySplitter(); } }; class ViodeoSplitterFactory :public SplitterFactory { public: virtual ISplitter* CreateSplitter() { return new ViodeoSplitter(); } }; class PictureSplitterFactory :public SplitterFactory { public: virtual ISplitter* CreateSplitter() { return new PictureSplitter(); } }; class TxtSplitterFactory :public SplitterFactory { public: virtual ISplitter* CreateSplitter() { return new TxtSplitter(); } }; ``` ##### 总结 - Factory 方法模式用于隔离类对象的使用者和具体类之间的耦合关系,面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱 - Factotry方法模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展的策略,较好的解决这种紧耦合关系。 - Factory方法模式解决单个对象需求的变化,缺点在于要求创建方法/参数相同。 #### 抽象工厂 ##### 动机 - 在软件系统中,经常面临”一系列相互依赖的对象“的创建工作,同时,由于需求的变化,往往存在更多系统对象的创建工作。 - 如何应对这种变化?如何绕过常规的对象创建方法(new), 提供一种”封装机制"来避免客户程序和这种"多系列具体对象创建工作"的紧耦合?  更改前: ```c++ class EmployeeDAO { public: vector<EmployeeDAO>GetEmployees() { SqlConnection* connection = new SqlConnection(); connection->ConnectionString = "..."; SqlCommand* command = new SqlCommand(); command->CommandText = "..."; SqlDataReader* reader = command->ExecuteReader(); while (reader->Read()) { } } }; ``` 更改中: ```c++ class IDBConnection { }; class IDBConnectionFactory { public : virtual IDBConnection* CreateDBConnection() = 0; }; class IDBCommand { }; class IDBCommandFactory { public: virtual IDBCommand* CreateIDBCommand() = 0; }; class IDataReader { }; class IDataReaderFactory { public: virtual IDataReader* CreateIDataReader() = 0; }; //支持Sql class SqlConnection : public IDBConnection { }; class SqlConnectionFactory :public IDBConnectionFactory { public: virtual SqlConnection* CreateSqlConnection() { return new SqlConnection(); } }; class SqlCommand : public IDBCommand { }; class SqlCommandFactory :public IDBCommandFactory { public: virtual SqlCommand* CreateSqlCommand() { return new SqlCommand(); } }; class SqlReader :public IDataReader { }; class SqlReaderFactory :public SqlReaderFactory { public: virtual SqlReader* CreateSqlReader() { return new SqlReader(); } }; //支持Orcal class OracleConnection : public IDBConnection { }; class OracleCommand : public IDBCommand { }; class OracleReader :public IDataReader { }; class EmployeeDAO { IDBCommandFactory* dbCommmandFactory; IDBConnectionFactory* dbConnectionFactory; IDataReaderFactory* dataReaderFactory; public: vector<EmployeeDAO>GetEmployees() { IDBConnection* connection = dbConnectionFactory->CreateDBConnection(); connection->ConnectionString ("..."); IDBCommand* command = dbCommmandFactory->CreateIDBCommand(); command->CommandText = ("..."); IDataReaderFactory* dataReaderFactory; IDataReader* reader = dataReaderFactory->CreateIDataReader(); while (reader->Read()) { } } }; ``` 更改后: ```c++ class IDBConnection { }; class IDBCommand { }; class IDataReader { }; class IDBFactory { public: virtual IDBConnection* CreateDBConnection() = 0; virtual IDBCommand* CreateIDBCommand()=0; virtual IDataReader* CreateIDataReader()=0; }; class SqlConnection : public IDBConnection { }; class SqlCommand : public IDBCommand { }; class SqlReader :public IDataReader { }; class SqlDBFactory :public IDBFactory { public: virtual IDBConnection* CreateDBConnection() { return new IDBConnection(); }; virtual IDBCommand* CreateIDBCommand() { return new IDBCommand(); }; virtual IDataReader* CreateIDataReader() { return new IDataReader(); }; }; class EmployeeDAO { IDBFactory* dbFactory; public: vector<EmployeeDAO>GetEmployees() { IDBConnection* connection = dbFactory->CreateDBConnection(); connection->ConnectionString("..."); IDBCommand* command = dbFactory->CreateIDBCommand(); command->CommandText = ("..."); IDataReader* reader = dbFactory->CreateIDataReader(); while (reader->Read()) { } } }; ``` ##### 要点总结 - 如果没有应对“多系列对象构建"的需求变化,则没有必要使用Abstract Factory模式,这时候简单的工厂完全可以使用。 - 系列对象指的是在某一特定系列下的对象之间相互依赖,或作用的关系,不同系列的对象之间不能相互依赖。 - Abstract Factory模式主要在于应对"新系列"的需求变动,其缺点在于难以应对"新对象"的需求变动。 #### 代理模式 ###### 动机 - 在面向对象系统中,有些对象由于某种原因(比如对象的创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者,或者系统结构带来很多麻烦。 - 如何在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性,增加一层中间层是软件开发常见的解决方式。 - 为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。  处理前: ```c++ class ISubject { public: virtual void process(); }; class RealSubject :public ISubject { public: virtual void process() { } }; class ClientApp { ISubject* subject; public: ClientApp() { subject = new RealSubject(); } void DoTask() { subject->proces(); } }; ``` 处理后: ```c++ class ISubject { public: virtual void proces(); }; class SubjectProxy :public ISubject { public: virtual void process() { //... } }; class ClientApp { ISubject* subject; public: ClientApp() { subject = new SubjectProxy(); } void DoTask() { subject->proces(); } }; ``` ##### 总结要点 - 增加一层间接层是软件系统中对许多复杂问题的一种常见解决方法,在面向对象系统中,直接使用某些对象会带来许多问题,作为间接层的proxy对象便是解决这一问题的常用手段。 - 具体proxy设计模式的实现方法,实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如copy-on-write技术,有些可能对组件模块提供抽象代理层,在架构层次对对象做proxy - Proxy并不一定要求保持接口的完整的一致性,只要能够实现间接控制,有时候损失一些透明性是可以接受的。 #### 适配器模式 ##### 动机 - 在软件系统中,由于应用环境的变化,常常需要将一些现存的对象放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。 - 如何应对这种”迁移的变化“,如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口? ##### 模式定义 - 将一个类的接口转化成客户希望的另一个接口。Adapter模式使得原本接口不兼容而不能一起工作的那些类可以一起工作。  ```c++ //目标接口 class ITarget { public: virtual void process() = 0; }; //遗留接口 class IAdaptee { public: virtual void foo(int data) = 0; virtual int bar() = 0; }; //遗留类型 class OldClass : public IAdaptee { //.... virtual void foo(int data) {} virtual int bar() {} }; class Adapter :public ITarget { protected: IAdaptee* pAdaptee; public: Adapter(IAdaptee* daptee) { this->pAdaptee = daptee; } virtual void process() { int data = pAdaptee->bar(); pAdaptee->foo(data); } }; int main() { IAdaptee* pAdaptee = new OldClass(); ITarget* pTarget = new Adapter(pAdaptee); pTarget->process(); } ``` ##### 总结要点 - Adapter模式主要应用于"希望复现一些现有的类",但是接口又与复用环境要求不一致的情况,在遗留代码复用,类库迁移等方面非常有用。 - GoF23定义了两种Adapter模式的实现结构:对象适配器和类适配器。但是类适配器采用多继承的实现方式,一般不推荐使用。对象适配器采用”对象组合“的方式,更符合松藕和。 - Adapter模式实现的非常灵活,不必拘于Gof23中定义的两种结构。列如,完全可以将Adapter模式中的现存对象作为新的接口参数,来达到适配的目的。 #### 迭代器模式 ##### 动机 - 在软件构建过程中,集合对象内部结构常常变化各异,但是对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种透明遍历也为同一种算法在多种集合对象进行操作提供了可能。 - 使用面向对象技术将这种遍历机制抽象为迭代器对象为应对变化中的集合对象提供一种优雅的方式。 - 提供一种方法顺序访问一个聚合对象对象的各个元素,而又不暴露(稳定)该对象的内部表示。  c++ stl中实现多态的机制是使用模板多态的。
{{ item.content }}
{{ child.content }}