系列文章目录
文章目录
前言
`
设计模式有两种分类方法,即根据模式的目的来分和根据模式的作用的范围来分。
-
根据目的来分
根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式 3 种。
1.创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
2结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF 中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。
3.行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。GoF 中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。 -
根据作用范围来分
根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式和对象模式两种。
1.类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。GoF中的工厂方法、(类)适配器、模板方法、解释器属于该模式。
2.对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。GoF 中除了以上 4 种,其他的都是对象模式。
提示:以下是本篇文章正文内容,下面案例可供参考
一、策略模式
1.业务场景:
我们想让鸭子可以飞,可以在抽象类Duck中添加fly()方法
业务发展,现在我们新增一种鸭子叫木头鸭,既不会飞,也不会叫,我们可以让木头鸭继承Duck ,覆盖fly()和quack(),实现不会飞也不会叫。
但是通过继承来提供Duck的行为,有以下缺点:
1.代码在多个子类中重复
2.运行时的行为不容易改变
3.很难知道鸭子的所有行为
4.改变会牵一发而动全身,造成其它鸭子不想要的改变
利用接口如何?
需要飞行或者会叫的鸭子分别实现Flyable和Quackable这两个接口,虽然解决了新增的鸭子需要什么行为就实现什么接口的问题,但是还是无法解决代码复用的问题,因为如果大多数的鸭子会飞的行为一致,在每一个鸭子类中都需要实现同样的方法。
设计原则
- 找出应用可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码放在一起
我们知道Duck类中fly()和quack()会随着鸭子的不同而改变,为了要把这两个行为从Duck中分开,我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了,我们也可以新增一些行为,不会影响到既有的行为类,也不会影响到使用到行为类的鸭子了。
同时也引出新的设计原则,这样在运行时才指定具体实现的类。
设计原则
2. 针对接口编程,而不是针对实现编程
关键在于,鸭子将飞行和呱呱叫的行为委托(delegate)给别人处理,而不是定义在鸭子类中的方法。
同时,还支持动态设定行为
完整类图:
设计原则
3. 多用组合,少用继承
2.定义:
定义算法族,分别封装起来,让他们可以相互替换,让算法变化对客户端透明。
3.项目应用:
多平台入驻项目有十几个平台,都需要对入参进行校验,校验逻辑不一样,但其他环节一致,这时候抽出校验算法的接口,提供一组实现该接口的策略类。
二、观察者模式
1.业务场景
气象站通过感应装置获取实时天气数据,然后更新到看板上
weatherData对象获取气象站更新的数据,并更新三个布告板:目前状况,气象统计,天气预报,业务必须支持扩展新的布告板。
缺点:
1.针对具体实现编程,而非针对接口。
2.对于每个新的布告板,得修改代码。
3.无法在运行时动态的增加或删除布告板。
4.没有封装改变的部分。
2.定义
定义对象之间的一对多依赖,当一端对象发生变换,它的所有依赖者都会收到通知并自动更新。
发布者 + 订阅者 = 观察者模式
类图:
当两个对象之间松耦合,他们依然可以交互,但是不太清楚彼此的细节,观察者模式提供了这样一种对象之间松耦合的设计。
因为主题只知道观察者实现了observe接口,并不知道具体的实现类是什么,只依赖一个实现了该接口的观察者列表,无论是往改列表中新增或删除观察者,主题都不受影响。
设计原则
4. 为了交互对象之间的松耦合设计而努力
具体实现代码:
除了像上面主题的数据发生变化就像观察者推送数据外,还可以让观察者主动拉取数据,
而java内置的观察者模式两张方式都支持。
如何把对象变成观察者:
实现观察者接口,然后调用任何Observable对象的addObserver()方法,不想当观察者时,调用deleteObserver()方法就行了
可观察者要如何送出通知:
1.先调用setChanged()方法,标记状态已经改变的事实。
2.然后调用两种notifyObservers()或notifyObservers(Objecr arg)
观察者如何接收通知
观察者实现了更新的方法:
如果你想推数据给观察者,你可以把数据当做数据对象传送给notifyObservers(Objecr arg)方法,否则,观察者就必须从可观察者对象中拉数据,如何拉数据?
java内置的观察者模式的局限性:
1.Observable 是一个类,因为java不支持多重继承,如果一个类还想继承其它类,这限制了Observable的复用能力。
2.Observabel将关键的setChanged()方法保护起来了(被定义为protected),这导致Observabel无法被组合到其它类中,违背了设计原则:多用组合,少用继承。
综上,我们也可根据业务自己实现一整套观察者模式。
三、装饰者模式
1.业务场景
计算出每种咖啡的价格,购买咖啡时,也可以向其加入调料,计算总共的价格
一种错误的实现方式:
通过继承,得到每种调料和咖啡的价格汇总,但是会造成类数量庞大,维护困难,调料价格和新增调料时,都需要修改代码。
其它的尝试:
这样虽然减少了大量的调料类,但是还存在一下缺点:
1.调料价钱变化,需要修改代码
2.增加新的调料,需要加的新的方法,并改变超类中的cost()方法
3.新的调料,子类不适合,子类也需要继承那些不适合的方法
4.当顾客需要双份调料时,现有代码不支持
设计原则
5. 类应该对扩展开放,对修改关闭。
用装饰者构造饮料订单
1.以DorkRoast对象开始
2.顾客想要摩卡(Mocha),所以建立一个摩卡对象,并用它将DorkRoast对象包(Wrap)起来
3.顾客也想要奶泡(Whip),所以需要建立一个Whip装饰者,并用它将mocha对象包起来。
4.现在给顾客算钱,通过调用最外围的装饰者(Whip)的cost()就可以办得到,Whip的cost()会先委托它装饰的对象(也就是Mocha)计算出价钱,在加上奶泡的价钱。
1.装饰者和被装饰者拥有相同的类型
2.你可以用一个或多个装饰者包装对象
3.在任何需要被装饰过的场合,都可以使用装饰者对象代替它
4.装饰者可以在被装饰者的前后,加上自己的行为
5.对象可以在任何时候被装饰,所以可以在运行时动态的使用装饰者来装饰对象
2.定义:
动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
类图:
代码实现:
装饰者通过继承达到和被装饰者类型匹配,再通过组合获得其行为,使用组合,可以把调料和饮料互相混合。
应用场景:
Java I/O
编写一个装饰者,把输入流内所有的大写字符都转成小写:
装饰者模式的局限性:
1.会引入很多小类,会让程序变得复杂,不利于理解整体设计
2.插入被装饰对象容易插错,需要小心谨慎
四、工厂模式
1.业务场景
根据不同的输入制作不同类型的披萨
业务发展,现在我们需要增加一些流行的披萨,而一些卖的不好的披萨要去除
如果实例化披萨出现问题,将使得整个制作披萨过程失败,且对修改 没有关闭,应该将变化的部分抽出来封装在一个新对象中,这个对象负责创建披萨,我们称这个新对象为工厂
创建一个简单工厂
简单工厂类图:
业务发展,现在需要在其它地方开加盟店,但是不同区域的口味不一样,可以创建各个区域的简单工厂去制作披萨,还可以多一些质量控制,加入自己的流程。
允许子类做决定,制作适合自己区域口味的披萨
之前是由一个对象负责所有具体类的实例化,现在变成由一群子类来实例化,
披萨类:
2.定义:
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,工厂方法让类把实例化推迟到子类。
类图:
工厂方法模式通过让子类来决定该创建的对象是什么
设计原则
6. 要依赖抽象,不要依赖具体类
这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层组件还是底层组件,两者都应该依赖于抽象。
反面案例:
正确方式:
几个方针能帮你避免在OO设计中违反此原则
1.变量不可以持有具体类的引用
2.尽量不要让类派生自具体类
3.尽量不要覆盖基类中已实现的方法
业务发展,不同区域的披萨需要使用不同的原料来制作,以迎合当地人的口味。
3.定义:
抽象工厂模式提供了一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
类图:
工厂方法和抽象工厂的区别?
工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象,允许类将实例化延迟到子类进行。
抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴露出来的方法中,创建相关的对象家族,而不需要依赖它们的具体类。
五、单例模式
1.业务场景
有一些对象其实我们只需要一个,比方说:线程池,缓存,注册表,日志对象等
2.定义
单例模式确保一个类只有一个实例,并提供一个全局访问点。
单例模式注意点:
1.私有化构造函数
2.提供一个全局访问点
实现方式:
- 饿汉模式
在这里插入代码片
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
- 懒汉模式
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 双重检查模式 (DCL)
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){
}
public static Singleton getInstance() {
if (instance== null) {
synchronized (Singleton.class) {
if (instance== null) {
instance= new Singleton();
}
}
}
return singleton;
}
}
- 静态内部类模式
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
- 枚举单例
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}
3.应用场景:
servlet单例、struts2多例、springmvc单例,数据库连接池,线程池,⽹站的计数器,日志应用,web应用配置文件等
(1)资源共享的情况下,避免由于资源操作时导致的性能问题或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
六、命令模式
1.业务场景
设计一个家电自动化遥控器的API,这个遥控器有7个插槽,分别都有对应的开关,还具体一个整体的撤销按钮。
希望能够创建一组控制遥控器的API,让每个插槽都能控制一个或一组装置,同时也能够控制未来可能出现的装置。
遥控器应该知道如何解读按钮被按下的动作,然后发出请求,但是不需要知道这些家电自动化的细节。
命令模式将动作的请求者和执行者解耦,遥控器是请求者,执行者是厂商类之一的实例。
命令接口:
2.定义
命令模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持撤销的操作
扩展,遥控器加上撤销命令,和宏命令(一组命令的行为)应该怎么设计?
3.应用场景:
JDK源码解析 Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法
//命令接口(抽象命令角色)
public interface Runnable {
public abstract void run();
}
//调用者
public class Thread implements Runnable {
private Runnable target;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
}
会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。
/**
* jdk Runnable 命令模式
* TurnOffThread : 属于具体
*/
public class TurnOffThread implements Runnable{
private Receiver receiver;
public TurnOffThread(Receiver receiver) {
this.receiver = receiver;
}
public void run() {
receiver.turnOFF();
}
**
* 测试类
*/
public class Demo {
public static void main(String[] args) {
Receiver receiver = new Receiver();
TurnOffThread turnOffThread = new TurnOffThread(receiver);
Thread thread = new Thread(turnOffThread);
thread.start();
}
}
七、适配器模式
1.业务场景
优点,不需要修改两边的代码,让两边的接口解耦了。
案例:
定义鸭子
一个绿头鸭实现类
定义火鸡
火鸡实现类
现在没有鸭子,需要用火鸡来假冒鸭子
2.定义
适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
适配器有两种,一种是面向对象适配器,一种是类适配器
区别:对象适配器使用组合,类适配器使用继承
使用组合,可以扩展使用不同类型的被装饰者
使用继承,可以复用被装饰者的方法,少些一些代码
JAVA中的适配器
3.应用场景
1.封装有缺陷的接口,统一多个类的接口,兼容老版本接口
2.JDBC定义了一套操作数据库的抽象接口,每个厂商提供了各自的驱动实现这套接口。
八、外观模式
1.业务场景
在观看家庭影院是,需要做一些列的操作:
缺点:
1.看完电影需要关掉怎么办,全部反向操作一次吗
2.如果听歌也会这么麻烦吗
3.升级系统,还需要重新实现一套不同的过程
外观不仅实现了接口,也将客户从子系统解耦
外观模式
2.定义
外观模式,也叫门面模式,提供了一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让子系统更容易使用。
设计原则
7. 最少知识原则:只和你的密友交谈
最少知识原则,也叫 得墨弥耳定律
先来看看这段代码耦合了多少类
在对象的方法内,我们只应该调用以下范围的方法
1.该对象本身
2.方法入参传入的参数
3.此方法创建或实例化的对象
4.对象的任何组件
适配器将一个对象包装起来已改变其接口,
装饰者将一个对象包装起来以增加新的行为和责任,
而外观将一群对象包装起来简化其接口
九、模板模式
1.业务场景
星巴克制作咖啡和茶的方法实现
子类代码重复,算法改变,需要修改子类代码
2.定义
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
类图:
可以预埋钩子方法,由子类选择是否覆盖
设计原则
8.别调用我们,我们会调用你
模板方法与策略区别:策略模式和模板方法模式第一封装算法,一个用组合,一个用继承
总结
1.策略模式(Strategy)
定义算法,将他们分别封装起来,让他们可以相互替换,让算法变化对客户端透明。
2.观察者模式(Observer)
解耦一系列对象的通知状态。定义对象之间的一对多依赖,当一端对象发生变换,通知多端。
3.装饰模式(Decorator)
动态将责任附加到对象上。对扩展开放,对修改封闭。
4.工厂模式(Factory)
工厂方法:定义一个创建对象的接口,由子类实现这个接口决定怎样创建具体类。工厂方法把对象的创建延迟到子类。
抽象工厂:定义一个接口,用于创建相关或依赖对象的家族,而不明确指定具体类。
5.单例模式(Singleton)
确保一个类只有一个实例,并且提供一个安全的全局访问点。
如果对多线程没有要求,可以直接在静态方法中创建。
如果存在资源竞争,采用“饿汉”方式创建。
如果jdk4之后,可有采用double checked locking
6.命令模式(Command)
将请求封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。
7.适配器模式(Adapter)
改变一个接口成为一个客户端希望的样子。让原本不兼容的接口能够相互合作。
8.外观模式(Facade)
简化系统接口,对客户端提供一个简单接口。
9.模板方法模式(Template Method)
在方法中定义一个算法的骨架,而将一些步骤延迟到子类实现。使子类在不改变算法结构的情况下,重新定义算法的某些步骤。
10.迭代器模式(Iterator)
提供一种方法顺序访问集合中的每个元素,而又不暴露其内部的表示。
11.组合模式(Composite)
允许你将对象组成树形结构来表现“整体/部分”的层次结构。组合能让客户端以一致的方式处理个别对象和对象组合。
12.状态模式(State)
允许对象内部状态改变时,改变它的行为,对象看起来就行修改了它的类。
13.代理模式(Proxy)
为对象提供一个替身或者占位符来访问这个对象。
14.复合模式
结合多个模式,组成一个解决方案。
MVC中:
M:观察者模式
V:组合模式
C:策略模式