设计模式是人们经过长期编程经验总结出来的一种编程思想。随着软件工程的不断演进,针对
不同的需求,新的设计模式不断被提出(比如大数据领域中这些年不断被大家认可的数据分片思
想),但设计模式的原则不会变。基于设计模式的原则,我们可以使用已有的设计模式,也可以根
据产品或项目的开发需求在现有的设计模式基础上组合、改造或重新设计自身的设计模式。
设计模式有
7
个原则:单一职责原则、开闭原则、里氏代换原则、依赖倒转原则、接口隔离原
则、合成
/
聚合复用原则、迪米特法则,接下来对这些原则一一进行讲解。
1.
单一职责原则
单一职责原则又称单一功能原则,它规定一个类只有一个职责。如果有多个职责(功能)被设
计在一个类中,这个类就违反了单一职责原则。
2.
开闭原则
开闭原则规定软件中的对象(类、模块、函数等)对扩展开放,对修改封闭,这意味着一个实
体允许在不改变其源代码的前提下改变其行为,该特性在产品化的环境下是特别有价值的,在这种
环境下,改变源代码需要经过代码审查、单元测试等过程,以确保产品的使用质量。遵循这个原则
的代码在扩展时并不发生改变,因此不需要经历上述过程。
3.
里氏代换原则
里氏代换原则是对开闭原则的补充,规定了在任意父类可以出现的地方,子类都一定可以出
现。实现开闭原则的关键就是抽象化,父类与子类的继承关系就是抽象化的具体表现,所以里氏代
换原则是对实现抽象化的具体步骤的规范。
4.
依赖倒转原则
依赖倒转原则指程序要依赖于抽象(
Java
中的抽象类和接口),而不依赖于具体的实现(
Java
中的实现类)。简单地说,就是要求对抽象进行编程,不要求对实现进行编程,这就降低了用户与
实现模块之间的耦合度。
5.
接口隔离原则
接口隔离原则指通过将不同的功能定义在不同的接口中来实现接口的隔离,这样就避免了其他
类在依赖该接口(接口上定义的功能)时依赖其不需要的接口,可减少接口之间依赖的冗余性和复
杂性。
6.
合成
/
聚合复用原则
合成
/
聚合复用原则指通过在一个新的对象中引入(注入)已有的对象以达到类的功能复用和
扩展的目的。它的设计原则是要尽量使用合成或聚合而不要使用继承来扩展类的功能。
7.
迪米特法则
迪米特法则指一个对象尽可能少地与其他对象发生相互作用,即一个对象对其他对象应该有尽
可能少的了解或依赖。其核心思想在于降低模块之间的耦合度,提高模块的内聚性。迪米特法则规
定每个模块对其他模块都要有尽可能少的了解和依赖,因此很容易使系统模块之间功能独立,这使
得各个模块的独立运行变得更简单,同时使得各个模块之间的组合变得更容易。
设计模式按照其功能和使用场景可以分为三大类:创建型模式(
Creational Pattern
)、结构型
模式(
Structural Pattern
)和行为型模式(
Behavioral Pattern
)
![](https://i-blog.csdnimg.cn/blog_migrate/63b37f366e5673d80f1afde60535d971.png)
工厂模式的概念
工厂模式(
Factory Pattern
)是最常见的设计模式,该模式设属于创建型模式,它提供了一种简
单、快速、高效而安全地创建对象的方式。工厂模式在接口中定义了创建对象的方法,而将具体的
创建对象的过程在子类中实现,用户只需通过接口创建需要的对象即可,不用关注对象的具体创建
过程。同时,不同的子类可根据需求灵活实现创建对象的不同方法。
通俗地讲,工厂模式的本质就是用工厂方法代替
new
操作创建一种实例化对象的方式,以提供
一种方便地创建有同种类型接口的产品的复杂对象。
如下代码通过
new
关键字实例化类
Class
的一个实例
class
,但如果
Class
类在实例化时需要一些初
始化参数,而这些参数需要其他类的信息,则直接通过
new
关键字实例化对象会增加代码的耦合
度,不利于维护,因此需要通过工厂模式将创建实例和使用实例分开。将创建实例化对象的过程封
装到工厂方法中,我们在使用时直接通过调用工厂来获取,不需要关心具体的负载实现过程:
以创建手机为例,假设手机的品牌有华为和苹果两种类型,我们要实现的是根据不同的传入参
数实例化不同的手机
![](https://i-blog.csdnimg.cn/blog_migrate/05cd4fcdfd5928f550cfba8ad7da8fdd.png)
抽象工厂模式的概念
抽象工厂模式(
Abstract Factory Pattern
)在工厂模式上添加了一个创建不同工厂的抽象接口
(抽象类或接口实现),该接口可叫作超级工厂。在使用过程中,我们首先通过抽象接口创建出不
同的工厂对象,然后根据不同的工厂对象创建不同的对象。
我们可以将工厂模式理解为针对一个产品维度进行分类,比如上述工厂模式下的苹果手机和华
为手机;而抽象工厂模式针对的是多个产品维度分类,比如苹果公司既制造苹果手机也制造苹果笔
记本电脑,同样,华为公司既制造华为手机也制造华为笔记本电脑。
在同一个厂商有多个维度的产品时,如果使用工厂模式,则势必会存在多个独立的工厂,这样
的话,设计和物理世界是不对应的。正确的做法是通过抽象工厂模式来实现,我们可以将抽象工厂
类比成厂商(苹果、华为),将通过抽象工厂创建出来的工厂类比成不同产品的生产线(手机生成
线、笔记本电脑生产线),在需要生产产品时根据抽象工厂生产。
工厂模式定义了工厂方法来实现不同厂商手机的制造。可是问题来了,我们知道苹果公司和华
为公司不仅制造手机,还制造电脑。如果使用工厂模式,就需要实现两个工厂类,并且这两个工厂
类没有多大关系,这样的设计显然不够优雅,那么如何实现呢?使用抽象工厂就能很好地解决上述
问题。我们定义一个抽象工厂,在抽象工厂中定义好要生产的产品(手机或者电脑),然后在抽象
工厂的实现类中根据不同类型的产品和产品规格生产不同的产品返回给用户。
![](https://i-blog.csdnimg.cn/blog_migrate/4040ecdd242cffadf18ac089ae112935.png)
单例模式的概念及
Java
实现
单例模式是保证系统实例唯一性的重要手段。单例模式首先通过将类的实例化方法私有化来防
止程序通过其他方式创建该类的实例,然后通过提供一个全局唯一获取该类实例的方法帮助用户获
取类的实例,用户只需也只能通过调用该方法获取类的实例。
单例模式的设计保证了一个类在整个系统中同一时刻只有一个实例存在,主要被用于一个全局
类的对象在多个地方被使用并且对象的状态是全局变化的场景下。同时,单例模式为系统资源的优
化提供了很好的思路,频繁创建和销毁对象都会增加系统的资源消耗,而单例模式保障了整个系统
只有一个对象能被使用,很好地节约了资源。
单例模式的实现很简单,每次在获取对象前都先判断系统是否已经有这个单例对象,有则返
回,没有则创建。需要注意的是,单例模型的类构造函数是私有的,只能由自身创建和销毁对象,
不允许除了该类的其他程序使用
new
关键字创建对象及破坏单例模式。
单例模式的常见写法有懒汉模式(线程安全)、饿汉模式、静态内部类、双重校验锁,下面一
一解释这些写法。
1.
懒汉模式(线程安全)
懒汉模式很简单:定义一个私有的静态对象
instance
,之所以定义
instance
为静态,是因为静态
属性或方法是属于类的,能够很好地保障单例对象的唯一性;然后定义一个加锁的静态方法获取该
对象,如果该对象为
null
,则定义一个对象实例并将其赋值给
instance
,这样下次再获取该对象时便
能够直接获取了。
懒汉模式在获取对象实例时做了加锁操作,因此是线程安全的,代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/90da784fb7e3fd2375ba9ffb075a8315.png)
2.
饿汉模式
饿汉模式指在类中直接定义全局的静态对象的实例并初始化,然后提供一个方法获取该实例对
象。懒汉模式和饿汉模式的最大不同在于,懒汉模式在类中定义了单例但是并未实例化,实例化的
过程是在获取单例对象的方法中实现的,也就是说,在第一次调用懒汉模式时,该对象一定为空,
然后去实例化对象并赋值,这样下次就能直接获取对象了;而饿汉模式是在定义单例对象的同时将
其实例化的,直接使用便可。也就是说,在饿汉模式下,在
Class Loader
完成后该类的实例便已经
存在于
JVM
中了,代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/21357df02deb2595445adb0688884415.png)
3.
静态内部类
静态内部类通过在类中定义一个静态内部类,将对象实例的定义和初始化放在内部类中完成,
我们在获取对象时要通过静态内部类调用其单例对象。之所以这样设计,是因为类的静态内部类在
JVM
中是唯一的,这很好地保障了单例对象的唯一性,代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/3d6ba24c4f90cb9b54545eff206f31fb.png)
4.
双重校验锁
双锁模式指在懒汉模式的基础上做进一步优化,给静态对象的定义加上
volatile
锁来保障初始化
时对象的唯一性,在获取对象时通过
synchronized (Singleton.class)
给单例类加锁来保障操作的唯一 性。代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/cfcd664f0c25577d491778288c4d1771.png)
4
策略模式的概念
策略模式(
Strategy Pattern
)为同一个行为定义了不同的策略,并为每种策略都实现了不同的
方法。在用户使用的时候,系统根据不同的策略自动切换不同的方法来实现策略的改变。同一个策
略下的不同方法是对同一功能的不同实现,因此在使用时可以相互替换而不影响用户的使用。
策略模式的实现是在接口中定义不同的策略,在实现类中完成了对不同策略下具体行为的实
现,并将用户的策略状态存储在上下文(
Context
)中来完成策略的存储和状态的改变。
我们在现实生活中常常碰到实现目标有多种可选策略的情况,比如下班后可以通过开车、坐公
交、坐地铁、骑自行回家,在旅行时可以选择火车、飞机、汽车等交通工具,在淘宝上购买指定商
品时可以选择直接减免部分钱、送赠品、送积分等方式。
对于上述情况,使用多重
if ...else
条件转移语句也可实现,但属于硬编码方式,这样做不但会使
代码复杂、难懂,而且在增加、删除、更换算法时都需要修改源代码,不易维护,违背了开闭原
则。通过策略模式就能优雅地解决这些问题。
模板方法模式的概念
模板方法(
Template Method
)模式定义了一个算法框架,并通过继承的方式将算法的实现延迟
到子类中,使得子类可以在不改变算法框架及其流程的前提下重新定义该算法在某些特定环节的实
现,是一种类行为型模式。
该模式在抽象类中定义了算法的结构并实现了公共部分算法,在子类中实现可变的部分并根据
不同的业务需求实现不同的扩展。模板方法模式的优点在于其在父类(抽象类)中定义了算法的框
架以保障算法的稳定性,同时在父类中实现了算法公共部分的方法来保障代码的复用;将部分算法
部分延迟到子类中实现,因此子类可以通过继承的方式来扩展或重新定义算法的功能而不影响算法
的稳定性,符合开闭原则。
模板方法模式需要注意抽象类与具体子类之间的协作,在具体使用时包含以下主要角色。
◎
抽象类(
Abstract Class
):定义了算法的框架,由基本方法和模板方法组成。基本方法定义
了算法有哪些环节,模板方法定义了算法各个环节执行的流程。
◎
具体子类(
Concrete Class
):对在抽象类中定义的算法根据需求进行不同的实现。
下面以银行办理业务为例实现一个模板方法模式,我们去银行办理业务都要经过抽号、排队、
办理业务和评价,其中的业务流程是固定的,但办理的具体业务比较多,比如取钱、存钱、开卡
等。其中,办理业务的固定流程就是模板算法中的框架,它常常是不变的,由抽象类定义和实现,
而具体办理的业务是可变的部分,通常交给子类去做具体的实现。
观察者模式的概念
观察者(
Observer
)模式指在被观察者的状态发生变化时,系统基于事件驱动理论将其状态通
知到订阅其状态的观察者对象中,以完成状态的修改和事件传播。这种模式有时又叫作发布
-
订阅
模式或者模型
-
视图模式。
观察者模式是一种对象行为型模式,观察者和被观察者之间的关系属于抽象耦合关系,主要优
点是在观察者与被观察者之间建立了一套事件触发机制,以降低二者之间的耦合度。
观察者模式的主要角色如下。
◎
抽象主题(
Subject
):持有订阅了该主题的观察者对象的集合,同时提供了增加、删除观察
者对象的方法和主题状态发生变化后的通知方法。
◎
具体主题(
Concrete Subject
):实现了抽象主题的通知方法,在主题的内部状态发生变化
时,调用该方法通知订阅了主题状态的观察者对象。
◎
抽象观察者(
Observer
):观察者的抽象类或接口,定义了主题状态发生变化时需要调用的
方法。
◎
具体观察者(
Concrete Observer
):抽象观察者的实现类,在收到主题状态变化的信息后执
行具体的触发机制
责任链模式的概念
责任链(
Chain of Responsibility
)模式也叫作职责链模式,用于避免请求发送者与多个请求处
理者耦合在一起,让所有请求的处理者持有下一个对象的引用,从而将请求串联成一条链,在有请
求发生时,可将请求沿着这条链传递,直到遇到该对象的处理器。
在责任链模式下,用户只需将请求发送到责任链上即可,无须关心请求的处理细节和传递过
程,所以责任链模式优雅地将请求的发送和处理进行了解耦。
责任链模式在
Web
请求中很常见,比如我们要为客户端提供一个
REST
服务,服务端要针对客
户端的请求实现用户鉴权、业务调用、结果反馈流程,就可以使用责任链模式实现。
责任链模式包含以下三种角色。
◎
Handler
接口:用于规定在责任链上具体要执行的方法。
◎
AbstractHandler
抽象类:持有
Handler
实例并通过
setHandler()
和
getHandler()
将各个具体的业务
Handler
串联成一个责任链,客户端上的请求在责任链上执行。
◎
业务
Handler
:用户根据具体的业务需求实现的业务逻辑。