【设计模式】8大设计原则和23种设计模式


李建忠老师《C++设计模式入门》笔记,加入了一些自己的理解。

设计原则

依赖倒置原则(DIP)

高层模块不应该依赖低层模块,二者都应该依赖抽象。
抽象不应该依赖细节,细节应该依赖抽象。

开放-封闭原则(OCP)

软件实体(类、模块、函数等)应该可以扩展,但是不可修改。
对扩展开放,对更改封闭。

也就是说当变化来临时,尽量增加模块,而不要对现有模块进行修改。

单一职责原则(SRP)

一个类应该仅有一个引起它变化的原因。
变化的方向隐含着类的责任。

里氏代换原则(LSP)

子类必须能够替换掉它们的父类(IS-A)。
继承表达类型抽象。

子类和父类是继承关系,而不是组合。

接口隔离原则(ISP)

不应该强迫客户程序依赖它们不用的方法。
接口应该小而完备。

利用封装对用户屏蔽非必要的接口,将它们设置为private或protect,而不是public。

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

类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。
继承在某种程度上破坏了封装性,子类父类耦合度高。
而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。

对象组合表现为嵌套类(比如class A中包含class B)

封装变化点

使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。

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

不将变量类型声明为某个特定的具体类,而是声明为某个接口。
客户程序无需获知对象的具体类型,只需要知道所具有的接口。
减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。

对第一条,主要强调的是工程设计中,而不是涉及到诸如vector、list等库的底层实现实现。

设计模式

设计模式分类

创建型模式(Creational Pattern):工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式,共5种;
结构型模式(Structural Pattern):适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式,共7种;
行为型模式(Behavioral Pattern):策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式,共11种。

模板方法(Template Method)

在这里插入图片描述

(在父类中)定义一个操作中的算法的骨架(稳定),而将一些步骤延迟(变化)到子类中。模板方法使得子类可以不改变(复用)一个算法的结构即可重定义(override)该方法的某些特定步骤。
也就是可以在父类(程序库)中定义好整个程序的运行流程(稳定),但将某些方法(变化)设为虚函数,让子类在继承时实现这些虚函数。这样,编写子类的应用程序员就只需关心对应的方法实现,而不用考虑程序的整体流程。
在这里插入图片描述

反而言之,当整体流程/骨架不稳定时,不应该选择使用模板方法模式。但在绝大多数场景下,我们倾向于认定整个流程是稳定的。(下图红框表示稳定,蓝框表示变化,下同)
在这里插入图片描述
在这里插入图片描述

PS:在C++语言中,对于稳定的方法,应写为非虚函数;对于变化的代码,应写为虚函数。对于父类或可能被继承并实现多态的类,应当将它们的析构函数声明为虚函数,即使这个方法是空的(防止父类指针提升为指向子类对象的指针后,在析构时出现问题)。

策略模式(Strategy)

在这里插入图片描述

定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展、子类化)。
也就是说,通过面向对象的封装与多态机制,将不同的算法(策略)子类化,继承父类相同的抽象接口,在运行时再根据实际情景(context)选择合适的算法(策略)执行,避免在客户类中出现大量的else if语句。
在这里插入图片描述
PS:避免过多else if语句带来的另一个不太明显的优点是,可以一定程度上缩减连续代码段的长度,可以占用更少的运行时存储空间,将节省出来的空间分配给其他重要的程序,提高总体的运行效率。极端情况下,当cpu缓存、内存存储空间较小时,甚至可以保证一部分代码不会因空间问题频繁的进行cache和内存间,以及内存和swap间的搬移。
在这里插入图片描述

观察者模式(Observer)

在这里插入图片描述

定义对象间的一种一对多(变化)的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
在这里插入图片描述
在这里插入图片描述

PS:C++支持多继承机制,但实际开发中不推荐使用,因为可能会带来复杂的耦合性问题。只有一类多继承是接受实际使用的,就是子类在只继承一个父类的基础上,再继承1到多个接口。

装饰模式(Decorator)

在这里插入图片描述

动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更加灵活,即可以显著消除重复代码并减少子类个数。
具体做法首先是在不同的具体类中提取出重复的、抽象的共同父类,通过指针实现多态性,进而将不同的具体子类合并为一个统一的子类,将编译时装配转化为运行时装配,通过具体运行时向类传递的指针类型对应到具体的子类。更进一步,额外定义一个中间类Decorator,包含抽象父类指针类型的多态指针(该指针在所有子类中共有且相同),它继承原先的抽象父类,并被不同的子类所继承。
在这里插入图片描述
在这里插入图片描述

桥模式(Bridge)

在这里插入图片描述
将抽象部分(业务功能)与实现部分(平台部署实现)分离,使它们都可以独立的变化。
在这里插入图片描述
在这里插入图片描述

工厂方法(Factory Method)

在这里插入图片描述
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到子类,目的是为了解耦,方法是通过虚函数抽象。
工厂方法,以及其他所有创建型方法的最本质的目的,都是为了避免某个实体类对另一个实体类的依赖,也就是不违背依赖倒置原则。
在这里插入图片描述
在这里插入图片描述

抽象工厂(Abstract Factory)

在这里插入图片描述
重点:相互依赖的对象的创建。
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。
在这里插入图片描述

抽象工厂的核心是在工厂方法的基础上,当不同的业务类之间具有相关性(比如SQL Server、MySQL和Oracle数据库各自都有自己的数据库创建、查询、修改工厂类)时,可以将有相关性的工厂类合并成一个工厂(如SQL Server、MySQL和Oracle数据库各自整合自己的工厂类而形成一个统一的工厂),减少代码复杂度的同时,避免不相关的类之间实例的误用。
在这里插入图片描述

原型模式(Prototype)

在这里插入图片描述
原型模式使用原型(Prototype)实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。
在这里插入图片描述

原型模式通过对象对自身的复制实现新对象的创建,与工厂模式相比不需要再为每一个具体类类单独定义一个对应的工厂类,但需要每个类实现一个clone方法。当类的结构较为复杂,或者同一个类不同对象间有较多相同参数设置时,优先考虑使用原型模式的“复制”,反之考虑工厂模式的“创建”。
在这里插入图片描述

构建器模式 / 建造者模式(Builder)

在这里插入图片描述
创建者模式将一个复杂对象的构建与其表示相分离,使得同样的构造过程(稳定)可以创建不同的表示(变化)。

创建者模式和模板方法的区别在于,模板方法是父类对子类实现进行操作,而创建者模式则是父类和子类都不关系如何创建,而是通过创建第三个类,也常被称为监工类(Director),来定义具体的创建方法。简单地说也就是将描述如何创建的方法进一步从父类中抽取出来,放在监工类中。
在这里插入图片描述
如图,一个监工类也可以监督多个构建过程(可以有多个不同的ConcreteBuilder实现Builder接口,Director通过for循环启动每个“工程”的构建)。
在这里插入图片描述

单例模式(Singleton)

在这里插入图片描述
单例模式保证一个类仅有一个实例,并提供一个该实例的全局访问点(指针)。
在这里插入图片描述
扩展:饿汉模式和懒汉模式的区别和实现。
在这里插入图片描述

门面模式 / 外观模式(Facade)

门面模式是一种接口隔离模式。在组件构建的过程中,某些接口之间直接的依赖常常会带来很多问题,甚至根本无法实现。采用添加一层间接(稳定)的接口,来隔离本来互相紧密关联的接口的解决方案,就是接口隔离模式的核心思想。
在这里插入图片描述
在这里插入图片描述
即主要是统一了内外连接的接口,保证在内部出现变化时,不会影响外部调用者的稳定。
门面模式为子系统中的一组接口提供了一个一致(稳定)的界面,该模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。
在这里插入图片描述
在这里插入图片描述

代理模式(Proxy)

在这里插入图片描述
代理模式为其他对象提供一种代理以控制对这个对象的访问,使用隔离和接口等方法。
在这里插入图片描述
在这里插入图片描述

适配器模式(Adapter)

在这里插入图片描述
适配器模式将一个类的接口转换成客户希望的另一个接口,它使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
在这里插入图片描述
扩展:对象适配器和类适配器(不常用且不推荐)。
在这里插入图片描述

中介者模式(Mediator)

在这里插入图片描述
用一个中介对象来封装 一系列的对象交互(封装变化)。中介者使各对象不需要显式的相互引用(编译时依赖->运行时依赖),从而使其耦合松散 (管理变化),而且可以独立地改变它们之间的交互。

在这里插入图片描述
其中ConcreteColleague1和ConcreteColleague2之间本身是直接相关,但现在增加了ConcreteMediator之后,两者之间的直接依赖关系被取消,转而分别和ConcreteMediator形成了双向的依赖。
在这里插入图片描述
在这里插入图片描述

状态模式(State)

在这里插入图片描述
状态模式允许一个对象在其内部状态改变时改变它的行为,从而使对象看起来似乎修改了其行为。
在这里插入图片描述

将不同的状态分别定义为对应的状态类,将可能发生的操作抽象出去作为接口,每个状态类都继承这个接口,并在实现的每个操作函数中定义对象处于当前状态时执行该操作产生的行为,并更新状态(转移到下一个状态,以指针形式维护)。如上图中ConcreteStateA和ConcreteStateB在自己的Handle函数内部都可以定义向其他状态转移的代码。
在这里插入图片描述

备忘录模式(Memento)

在这里插入图片描述
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
在这里插入图片描述
在这里插入图片描述

组合模式(Composite)

在这里插入图片描述
组合模式将对象组合成树形结构以表示“部分-整体”的层次结构,它使得用户对单个对象和组合对象的使用具有一致性(稳定)。
在这里插入图片描述
也就是通过定义树形结构,划分根节点类和树节点类的层次关系后,可以在根节点中定义递归调用(多态指针),而使得对根节点的操作可以递归的使所有该节点的叶节点也进行相应的操作。
在这里插入图片描述
重点:一对多关系到一对一关系,一致地处理。

迭代器模式(Iterator)

在这里插入图片描述
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示(稳定)。
在这里插入图片描述
当前在C++中,该基于面向对象的迭代器模式已基本被基于泛型编程的迭代器取代。
在这里插入图片描述

职责链模式(Chain of Responsibility)

在这里插入图片描述
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
在这里插入图片描述
在这里插入图片描述

命令模式(Command)

在这里插入图片描述
命令模式将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
在这里插入图片描述
命令模式最核心的作用,就是通过面向对象的方式实现了命令执行者和响应者之间的松耦合。
在这里插入图片描述

访问器模式 / 访问者模式(Visitor)

在这里插入图片描述
表示一个作用于某对象结构中的各元素的操作,使得可以在不改变(稳定) 各元素的类的前提下定义 (扩展) 作用于这些元素的新操作(变化)。
在这里插入图片描述

如图,访问者模式要求图中的Visitor类是稳定的,这就反过来要求ConcreteElementA和ConcreteElementB也是稳定的,而这一点在实际使用中非常难保证,因为这意味着开发者要提前明确Element的各个子类,保证不会更改。因此访问者模式只能在特定的应用场景下使用。反之,当Visitor不稳定,即无法确定Element子类个数及层次结构时,使用访问者模式没有意义。
在这里插入图片描述

解释器模式(Interpreter)

在这里插入图片描述
给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。
在这里插入图片描述
通过使用面向对象特性,增强了原文法解析的扩展性,方便后续可能的变化。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值