23种设计模式之基本介绍
参考资料
- Java设计模式:23种设计模式全面解析(超级详细)
- 韩顺平老师的Java设计模式(图解+框架源码剖析)
- 秦小波老师的《设计模式之禅》
下文如有错漏之处,敬请指正
一、简介
设计模式(Design Pattern)是一套理论,是软件界前辈们代码设计经验的总结,其目的是为了提高代码的可扩展性、可靠性、可重用性和可读性。
使用设计模式,可以使软件具有:
- 可扩展性(可以非常方便的增加新的功能)
- 可靠性(当增加新功能后,对原有的功能不产生影响)
- 可重用性(相同功能的代码,不用多次编写)
- 可读性(编程规范,便于其他程序员的阅读和理解)
设计模式常用的7大原则:
- 单一职责原则 (Single Responsibility Principle)
- 接口隔离原则 (Interface Segregation Principle)
- 依赖倒转原则 (Dependence Inversion Principle)
- 里氏替换原则 (Liskov Substitution Principle)
- 开闭原则 (Open/Closed Principle)
- 迪米特原则 (Least Knowledge Principe)
- 合成复用原则 (Composite Reuse Principle)
二、7大原则
单一职责原则(SRP)
基本介绍
- 一个类、接口、或方法只负责一项职责,即只做一件事
- 目的是降低类的复杂度,使每一个类、接口或方法都有一个清晰的定义
场景模拟
如类A负责两个不同的职责:职责1,职责2。当职责1的需求变更而要修改类A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 类A1(包含职责1)和类 A2(包含职责2)。
接口隔离原则(ISP)
基本介绍
一个类对另一个类的依赖应该建立在最小的接口上,即一个类不应该依赖它不需要的接口。
场景模拟
一个接口I有5个功能,类A实现了该接口I,但实际只用到接口I的2个功能,这时类中的另外三个方法就会被浪费,所以需要将接口I的粒度进行分解,将类A需要的两个接口分离出来变为接口X,让类A实现接口X,使类A中的功能全部被使用。
依赖倒转原则(DIP)
基本介绍
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转的中心思想是面向接口编程
- 目的是减少类间的耦合性,提高系统的稳定性
场景模拟
每个APP都有页面显示的需求,且每个APP页面显示的内容均不同,手机系统不可能为每个APP页面显示单独开发。因此手机系统自定义一个页面显示功能接口,对显示功能进行规范,让每个APP实现该接口进行页面显示的定制开发。
里氏替换原则(LSP)
基本介绍
- 父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常
- 里氏替换原则是对继承(OO)的规范
- 如果父类的某些方法在子类中发生了”畸变“(改变了方法原有的意义),建议断开继承关系,采用依赖、聚合或组合的方式代替继承
- 子类可以扩展父类的功能,但不能改变父类原有的功能,即子类中尽量不要重写父类的方法
- 目的是代码共享,减少创建类的工作量和提高代码的可扩展性
场景模式
如一个动物类,其中有一个方法是奔跑。老虎类继承了动物类,老鹰也继承了动物类,但是它不会奔跑,于是它重写了父类的奔跑方法,此时便违反了里氏替换原则。因此要对动物类进行粒度分解,分为飞禽类和走兽类,让老鹰继承飞禽类。
开闭原则(OCP)
基本介绍
- 一个软件的实体应该对扩展开放,对修改关闭。即一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
- 用抽象构建框架,用实现扩展细节
场景模拟
如有一个主题类,当用户想更换主题时,需要对原有主题类的代码进行更改,这就违反了开闭原则。所以应将主题类设为抽象类,由其子类来定义主题,当需要更换主题时,只要增加一个子类,不改变原有代码结构。
迪米特原则(LKP)
基本介绍
- 迪米特原则又称为最少知识原则
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
场景模拟
如电影制作公司,当电影制作完成之后交由院线上线,由院线排片出售票务,由院线和用户产生关系,电影制作公司不与用户产生关系。
合成复用原则(CRP)
基本介绍
尽量使用组合或聚合的方式替代继承。
场景模拟
如咖啡店出售的咖啡可以搭配调料,调料有(牛奶,芝士,方糖),如果使用继承的话,就会有一个咖啡父类,咖啡+牛奶类、咖啡+芝士类、咖啡+方糖,每当增加一个调料时,就要增加一个咖啡类+调料类,如果调料非常多,那么将会产生非常多的类。此时可以采用组合或聚合的方法,有一个咖啡父类,在咖啡父类中聚合调料对象,在使用时只需要将调料传入,每当增加一个调料,只需要将调解对象传入,不需要增加新的咖啡类。
7种原则总结
- 每种原则要求的侧重点不同。
- 开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭。
- 里氏替换原则告诉我们不要破坏继承体系。
- 依赖倒置原则告诉我们要面向接口编程。
- 单一职责原则告诉我们实现类要职责单一。
- 接口隔离原则告诉我们在设计接口的时候要精简单一。
- 迪米特法则告诉我们要降低耦合度。
- 合成复用原则告诉我们要优先使用组合或者聚合关系,少用继承关系。
三、类与类之间的关系
-
依赖(虚线箭头)
-
一个类在类中使用到另一个类,则两个类之间存在依赖关系。
-
在UML类图中,依赖关系用带箭头的虚线表示,由依赖的一方指向被依赖的一方
-
如下图所示,Person类依赖Address类
-
-
泛化 (实线空心)
-
泛化实际上是继承关系
-
在UML中,泛化关系用带空心三角形的直线来表示,由子类指向父类
-
如下图所示,Student与Professor是子类,Person是父类
-
-
实现(虚线空心)
-
实现实际上是面向接口编程
-
在UML中,实现关系用带空心三角形的虚线来表示,由接口实现类指向接口类
-
如下图所示,Diesel与NEV是接口实现类,Vehicle是接口类
-
-
关联(实线)
- 描述类之类间的关系。
- 单向
- 1:1
- 1:n
- n:1
- 双向
- 1:1
- 1:n
- n:m
- 单向
- 在UML中,关联关系用实线来表示
- 如下图所示,Person类与Address的关联关系是(1:n)
- 描述类之类间的关系。
-
聚合(空心菱形)
- 整体和部分的关系且整体与部分可以分离,即has-a的关系。
public class Car{ private MusicSystem musicSystem; //聚合关系 }
- 在UML中,聚合关系使用空心菱形表示(菱形指向的是整体),由部分指向整体
- 如下图所示,Car类聚合MusicSystem类
-
组合(实心菱形)
- 整体和部分的关系且整体与部分不可以分离,即contains-a的关系。
public class Car{ private Engine=new Engine(); //组合关系 }
- 在UML中,组合关系使用实心菱形表示(菱形指向的是整体),由部分指向整体
- 如下图所示,Car类组合Engine类
四、 设计模式分类
设计模式分为三种类型(创建型、结构型、行为型),共23种。
创建型模式
用于描述“如何创建对象”,它的主要特点是“将对象的创建与使用分离”。
1、 单例模式(Singleton)
一个类只存在一个实例,并且自行实例化并向全局提供一个获取该实例的方法。
2、工厂模式(Factory)
提供一种方法可以通过类或方法产生对象。
3、原型模式(Prototype)
通过克隆原型的实例对象,创建新的对象。
4、建造者模式(Builder)
将一个复杂对象分解成多个相对简单的部分,然后根据不同需求分别创建它们,最后构建成该复杂对象。
5、抽象工厂模式(AbstractFactory)
用来生产固定产品线组成的不同产品族,抽象工厂规定生产的产品集合(即固定产品线),产品集合中的每个产品由抽象工厂的子类工厂(产品族工厂)负责生产。
结构型模式
用于描述如何将类或对象按某种布局组成更大的结构。
1、适配器模式(Adapter)
将一个类的接口转换成客户端期望的另外一个接口,使得原本因接口不兼容的类能协同工作。
2、桥接模式(Bridge)
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
3、装饰者模式(Decorator)
动态的给一个对象增加额外的功能。
4、组合模式(Composite)
将对象组合成树形结构以表示“部分与整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。
5、外观模式(Facade)
为多个复杂的子系统提供一个一致的接口,使调用端可以更加容易的访问这些子系统。
6、享元模式(Flyweight)
运用共享技术来有效地支持大量细粒度对象的复用。
7、代理模式(Proxy)
通过代理类控制目标类的访问。即客户端可以通过代理对象间接地访问目标对象,从而限制、增强或修改目标对象的。
行为型模式
用于描述类或对象之间如何相互协作,共同完成单个对象无法完成的任务,以及如何分配职责。
1、模板方法模式(TemplateMethod)
定义一个算法的框架,封装步骤相同的部分,将特定(不同)步骤的部分交由子类完成,以此达到代码复用。
2、命令模式(Command)
将某个产品的相关操作抽象成命令,由命令连接调用者和实现者,使命令的调用者与命令的实现者分离。
3、访问者模式(Visitor)
在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
4、迭代器模式(Iterator)
提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部结构。
5、观察者模式(Observer)
多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
6、中介者模式(Mediator)
定义一个中介对象来封装原有对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。即将多对多依赖关系转为一对多,其中一就是中介者。
7、备忘录模式(Memento)
在不破坏封装性的前提下,记录一个对象的内部状态,并在该对象之外保存这个状态,当需要时能将该对象恢复到原先保存的状态。
8、解释器模式(Interpreter)
给定一门语言,定义它的文法表示,再设计一个解析器来解释语言中的句子。
9、状态模式(State)
允许一个对象在其内部状态发生改变时改变其行为。
10、策略模式(Strategy)
定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
11、责任链模式(Chain of Responsibility)
把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
总结
23 种设计模式不是孤立存在的,很多模式之间存在一定的关联关系,在大型系统开发中常常需要同时使用多种设计模式。了解每一种设计模式的特点,做到手中无剑胜有剑。