设计模式专栏


本文章主要是记录个人学习设计原则和设计模式的一些笔记,希望对其他朋友也有参考价值。

一、七大设计原则

目的:稳定的软件架构所需要遵循的原则。
学习方法:从设计模式来理解设计原则在实际上的应用。
SOLID原则:
S:单一职责原则
O:开闭原则
L:里氏替换原则+迪米特法则
I:接口隔离原则
D:依赖倒置原则

其他:复合聚合原则

1、单一职责

  1. 单一职责:一个类只负责一个功能。如果有2个功能,一个功能的改变会影响其他的。
    主要是为了解耦。如qt 界面绘制类,只控制界面的逻辑,获取/保存数据,由另一个类实现。
    1)优点:a.降低类的复杂度,一个类只负责一项职责
    b.提高类的可读性,可维护性
    c.降低变更引起的风险

按照功能划分颗粒度,划分太细会导致接口/类变多,可读性下降。

2、开闭原则

  1. 开闭原则:对扩展开放,对修改封闭(意味着类一旦设计完成,就可以独立完成其工作,而不要对已有代码进行任何修改)。
    1)实现方法:核心思想就是面对抽象编程,而不是面对具体编程,因为抽象相对稳定。
    让类依赖于固定的抽象,所以对修改是封闭的;而通过面向对象的继承和多态机制,可以实现新的扩展方法,所以对于扩展就是开放的。

3、 里氏代换

  1. 里氏代换:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
    1)作用:a.克服了继承中重写父类造成的可复用性变差的缺点。
    b.确保类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
    2)其他解释

在一个软件系统中,子类应该能够完全替换任何父类能够出现的地方,并且经过替换后,不会让调用父类的客户程序从行为上有任何改变。

3、迪米特法则:

一个软件实体应当尽可能少的与其他实体发生相互作用。这样,当一个模块修改时,就会尽量少的影响其他的模块,扩展会相对容易。迪米特法则是对软件实体之间通信的限制,它对软件实体之间通信的宽度和深度做出了要求。

4、接口隔离原则

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

在这里插入图片描述

5、依赖倒置:

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

依赖倒转的中心思想是面向接口编程。
依赖倒转的设计理念为:相对于细节的多变性,抽象的东西要稳定的多。以抽象的基础搭建的架构比以细节为基础的架构要稳定的多。

抽象和细节的理解:
接口是对实现的抽象,类是对接口的抽象,对接口进行设计。----高内聚(重复逻辑代码少),低耦合(进行模块设计、分层)。
把不同的细节组合成元件,把元件组合成更大的元件,一层层封装,思路会变得更加清晰,因为细节被屏蔽了。

6、复合/聚合原则

尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的;

1)继承的缺点:限制了系统的灵活性,使类与类之间的耦合度增加,父类的变化可能会影响到所有的子类。
2)复合/聚合的优点:可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。

二、UML类图

UML是统一建模语言,是一种辅助团队成员交流的工具。

常见类关系:

  1. 依赖关系:某个类以局部变量出现另一个类中
    1)虚线,箭头,箭头指向被依赖者,use a
  2. 关联关系:某个类以成员变量的形式出现另一个类中 has a
    1)实线,箭头
    2)分类
    a. 聚合关系,空心菱形,菱形表示整体。是一种弱相互关系,整体和部分可以独立存在,不同生命周期 has a
    b. 组合关系,实心菱形,菱形表示整体。强相互关系,整体和部分不可以分割,相同生命周期 contains a

聚合和组合的区别在于:聚合关系是“has-a”关系,组合关系是“contains-a”关系;聚合关系表示整体与部分的关系比较弱,而组合比较强;聚合关系中代表部分事物的对象与代表聚合事物的对象的生存期无关,一旦删除了聚合对象不一定就删除了代表部分事物的对象。组合中一旦删除了组合对象,同时也就 删除了代表部分事物的对象。

  1. 泛化关系is a
    1)类继承关系,实线,三角形表示父类
    2)接口继承关系,虚线,三角形表示接口

强弱:泛化=实现>组合>聚合>关联>依赖

  1. 使用技巧
    1.有关系,先用依赖,后续修改
    2、关联:只使用关联,分不清组合和聚合时。 聚合:添加管理职责, 上下级,从属关系。

三、时序图

看图方式:柱子表示激活状态,完成某项目任务
实线箭头表示调用,虚线箭头表示调用后返回

画图软件:visio

四、设计模式前言

设计模式优点:

  1. 易拓展,开闭原则
  2. 易维护,封装函数、类调用
  3. 易复用,!=复制,使用封装,可以直接去调用。

总共23种,三大类型。
—创建型模式 与对象创建有关,涉及到对象实例化方式,共5种。
—结构型模式 关注类和对象的组合,共7种
—行为型模式 类之间互相交互和分配职责 共11种

一、创建型模式

有单例模式,原型模式,建造者模式,工厂模式,抽象工厂模式,共5种。
与对象创建有关,涉及到对象实例化方式。

1、单例模式

确保一个类只有一个实例被建立。

要求:

  1. 构造函数应该声明为非公有,从而禁止外界创建实例。静态成员初始化可以调用new,不影响,其他地方不可调用。
  2. 拷贝操作和移动操作也应该禁止。
  3. 通过getInstance()获取。static *p; static *p getInstance(); 静态函数可以访问私有的构造函数。

懒汉模式:用的时候初始化. 双重判断+ 锁,线程安全
饿汉模式:一开始就创建,静态变量初始化的时候赋值。 线程安全

2、原型模式

从A的实例得到一份与A内容相同。本质是拷贝构造函数,实际上是实现了一个clone接口。

  1. 用new新建对象不能获取当前对象运行时的状态。
  2. 需要用基类指针获取到派生类对象。

类图:
在这里插入图片描述
作用:

  1. 简化创建过程。当new一个对象,构造参数比较多时或者类初始化需要消化非常多的资源,这个资源包括数据、硬件资源。就可以使用原型模式来创建一个新的对象,不必去理会创建的过程。

  2. 当需要一个对象副本时,同时又需要避免外部对数据对象进行修改,那就拷贝一个对象副本供外部使用。

3、建造者模式

建造者模式,是将一个复杂的对象的构建(build)与它的表示(product)分离,使得同样的构建过程(director)可以创建不同的表示(产品)。
一个复杂对象的创建,由固定的步骤组成。

build负责创建各个组件,director负责把各个组件的组装步骤。Man是生成出来的人,具有各个属性。

类图:
在这里插入图片描述

4、工厂模式

1、定义:在创建对象时提供了一种封装机制,将实际创建对象的代码与使用代码分离。

简单工厂模式:一个工厂,生产多个产品。
创建一个类,实现getInstance(int i),通过传入参数确定生成的产品。
不足:新增子类时,需要修改源代码。

工厂方法模式: 一个工厂生产一个产品
抽象工厂(Abstract Factory):定义了创建产品对象的接口。
具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体的产品对象。
抽象产品(Abstract Product):定义了产品的基本属性和行为。
具体产品(Concrete Product):实现了抽象产品接口,是具体的产品对象。

5、抽象工厂模式

1、使用场景,新增产品类型(不继承product)时,使用该模式可以减少工厂数量。

一个工厂,生产一系列产品。

二、结构型模式

共7种,关注类和对象的组合。

对对象添加职责:适配器、代理、桥接模式
操作的抽象:组合、外观
加减:装饰、享元

1、适配器模式

转换器:将一个类的接口转换成客户希望的另外一个接口这样使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

比如你在网上买了一个手机,但是买家给你发回来了一个3接头的充电器,但是恰好你又没有3接头的插槽,只有2个接口的插槽,于是你很直然地便会想到去找你个3接口转两接口的转换器。简单的分析下这个转换器便是我们这里的适配器Adapter。三相插头便是我们要适配的Adaptee。

分为类适配器和对象适配器。
类适配器:使用继承
在这里插入图片描述

对象适配器:使用组合
在这里插入图片描述
对象适配器相比类适配器来说更加灵活,他可以选择性适配自己想适配的对象。(复合/聚合原则)。

体现的设计原则:依赖倒置原则:Adapter需要继承Target,而不是直接使用Adapter。面向抽象编程。

2、代理模式

给对象增加额外功能。在调用这个方法之前做前置处理,调用这个方法之后做后置处理。
体现的设计原则:依赖倒置,面对抽象编程。继承于subject,而不是直接使用代理类。
类图
在这里插入图片描述

分类

  1. 远程代理:为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。
  2. 虚代理:延迟加载,先加载轻量级的代理对象,真正需要再加载真实对象。在需要的时候才初始化主题对象。
  3. 保护代理,用来控制真实对象的访问权限,一般用于对象有不同的访问权限的时候。也被称为动态代理。
  4. 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。如智能指针shared_ptr和unique_ptr。

3、桥接模式

1、定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。如下面的例子是shape是抽象,颜色是实现。

2、体现的设计原则:复合聚合原则。

3、适用场景:当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
在这里插入图片描述

4、组合模式

对操作的抽象。组合模式使得用户对单个对象和组合对象的使用具有一致性,可统一调用接口。

使用场景:需求中体现部分于整体层次的结构时,用户可以忽略组合对象与单个对象的不同,统一使用相同接口访问。

使用举例:
目录和文件,目录和文件都可以删除或者打开。打开目录其实就是打开目录的全部子目录和该目录下的文件。删除文件就是删除一个文件,删除目录是删除全部子目录和全部文件。

在这里在树节点插入图片描述

5、外观模式

1、定义:外观模式又称为门面模式(Facade Pattern),提供一个统一接口 , 用于访问子系统中的一群接口。

2、体现的设计原则:迪米特法则。降低子系统与调用者的耦合度,调用方便。

3、类图
在这里插入图片描述

6、装饰模式

1、定义:动态地给一个对象添加一些额外的职责。

2、体现的设计原则:开闭原则和依赖倒置原则。

原因:
有时我们希望给某个对象而不是整个类添加一些功能。比如有一个手机,为手机添加特性,比如增加挂件、屏幕贴膜等。
使用继承机制不够灵活,用户不能控制对组件添加功能的方式和时机。
一种较为灵活的方式是将组件嵌入另一个对象(装饰类)中,由这个对象添加功能,我们称这个嵌入的对象为装饰。

类图:
在这里插入图片描述

7、享元模式

1、定义:运用共享技术有效地支持大量细粒度对象的复用。
2、原因:当对象数量太多时,将导致运行代价过高,带来性能下降等问题。享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。

在享元模式中可以共享的相同内容称为内部状态,而那些需要外部环境来设置的不能共享的内容称为外部状态。
通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的(对象不再重复创建)。
也就是说,享元模式的本质是分离与共享 : 分离变与不变,并且共享不变。

案例:通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。
在这里插入图片描述
享元工厂,管理创建的对象,如map或vector容器。由整个对象变成存储可变状态,内存减少。

案例可以见:
https://blog.csdn.net/weixin_43905387/article/details/117135556

三、行为型模式

类之间互相交互和分配职责。

1、封装变化:职责链、状态、策略、模板方法、
2、管理:命令、备忘录、观察者、中介者
3、访问:访问者、迭代器
4、其他:解释器。

1、职责链模式

1、定义:把请求发送者和接收者解耦,请求按照链子处理。
2、体现的设计原则:开闭原则。

事例:考虑员工要求加薪。公司的管理者一共有三级,总经理、总监、经理,如果一个员工要求加薪,应该向主管的经理申请,如果加薪的数量在经理的职权内,那么经理可以直接批准,否则将申请上交给总监。总监的处理方式也一样,总经理可以处理所有请求。

类图
在这里插入图片描述

2、状态模式

1、定义:一个对象有多种状态,在每一个状态下,都会有不同的行为。
2、体现的设计原则:开闭原则。分离出容易变化的。

将对象的状态封装成一个独立的类,并将动作行为委托到代表当前状态的对象,行为会随着内部的状态而改变。

优点:消除了分支语句,就像工厂模式消除了简单工厂模式的分支语句一样,将状态处理分散到各个状态子类中去,每个子类集中处理一种状态,这样就使得状态的处理和转换清晰明确。
类图
在这里插入图片描述

3、策略模式

1、定义:实现和状态模式类似,区别在于是对算法的封装。

类图
在这里插入图片描述

传递方法1:指针

//Cache需要用到替换算法
class Cache
{
private:
	ReplaceAlgorithm *m_ra;
public:
	Cache(ReplaceAlgorithm *ra) { m_ra = ra; }
	~Cache() { delete m_ra; }
	void Replace() { m_ra->Replace(); }
};

缺点:暴露了太多的细节。

传递方法2:结合简单工厂

//Cache需要用到替换算法
enum RA {LRU, FIFO, RANDOM}; //标签
class Cache
{
private:
	ReplaceAlgorithm *m_ra;
public:
	Cache(enum RA ra) 
	{ 
		if(ra == LRU)
			m_ra = new LRU_ReplaceAlgorithm();
		else if(ra == FIFO)
			m_ra = new FIFO_ReplaceAlgorithm();
		else if(ra == RANDOM)
			m_ra = new Random_ReplaceAlgorithm();
		else 
			m_ra = NULL;
	}
	~Cache() { delete m_ra; }
	void Replace() { m_ra->Replace(); }
};

只要知道算法的相应标签即可,而不需要知道算法的具体定义。

传递方法3:利用模板实现

//Cache需要用到替换算法
template <class RA>
class Cache
{
private:
	RA m_ra;
public:
	Cache() { }
	~Cache() { }
	void Replace() { m_ra.Replace(); }
};

原文链接:https://blog.csdn.net/wuzhekai1985/article/details/6665197

4、模板方法

1、定义:定义操作中的算法骨架,将一些步骤延迟到子类中去实现。

2、体现的设计原则:开闭原则。把变化的部分抽象出来。

3、实现:由基类来实现基本固定的逻辑,而把不同的部分封装在子类里,实现代码的复用。

类图
在这里插入图片描述
模板模式和策略模式区别
策略模式:定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。关键点在于每个算法都是过程完整且独立的。
模板方法模式:模板则是将骨架定义好,例如执行的步骤或先后顺序。骨架中的部分在父类中进行实现,而子类的个性化行为则由子类继承再加以实现
区别的本质就是策略模式是替换了整个流程。而模板模式替换的是固定流程中的一些特定的内容。

5、命令模式

1、定义:请求和执行者解耦,将一个请求封装为一个对象,便于对命令进行管理。
2、实现:命令中有基类执行者指针,并不需要知道实际执行者是谁。

场景:
客户端有多个请求协议的情况下,可以将协议当做命令。对请求排队或者记录请求日志,以及支持可撤销的操作。

类图
在这里插入图片描述图中Invoker负责接受和管理各种命令。然后由Invoker统一向Receiver发起请求。所有类型的请求都封装在Command中了。

命令模式优点:
1.设计命令队列
2.设置命令日志
3.接受者可以根据请求判断是否执行
4.可以实现撤销和重做

细节需要看下面链接:
原文链接:https://blog.csdn.net/konglongdanfo1/article/details/83381519

6、备忘录模式

1、定义:快照模式。在不破坏封装的情况下,捕获一个对象的内部状态,并在该对象之外保存这个状态。便于回退。

类图
在这里插入图片描述

参考链接:https://www.cnblogs.com/jing99/p/12617294.html

7、观察者模式

1、定义:多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式。

类图:
在这里插入图片描述

8、中介者模式

1、定义:关系简化,将多对多的交互简化为一对多的交互。

类图
在这里插入图片描述

参考链接:
https://zhuanlan.zhihu.com/p/447208807

9、访问者模式

1、定义:不改变各元素的类的前提下定义作用于这些元素的新操作。

2、体现的设计原则:开闭原则。分离出容易变化的。

3、使用场景:把处理从数据结构中分离出来,适用于数据结构相对稳定的情况。

代码实现的核心思想是在访问者类Visitor通过接口函数Visitor::visitorElement(ElementA*)访问目标对象成员函数,目标类Element通过接口函数Element::accept(Visitor* visitor){visitor->visitorElementA(this);}反射自动化调用访问自身,将自身指针this传递给访问者访问接口Visitor::visitorElement(this)。

反射:直接调用元素本身,通过传入的访问者,可以有不同的操作。访问者可以获取元素进行操作

类图
在这里插入图片描述

10、迭代器模式

1、定义:提供一种方法顺序访问一个聚合对象中各个元素,而又无需暴露该对象的内部表示。

类图
在这里插入图片描述

11、解释器模式 用到较少

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

使用场景:
通常当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。
在这里插入图片描述
原文链接:https://blog.csdn.net/xiqingnian/article/details/42222369

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值