一.设计模式的作用
设计模式是软件从业人员长期总结出来用于解决特定问题的通用性框架,它提高了代码的可维护性、可扩展性、可读性以及复用性。
1.工厂模式
2.抽象工厂模式
3.单例模式
4.建造者模式
5.原型模式
6.适配器模式
7.策略模式
8.模版模式
9.观察者模式
10.代理模式
11.责任链模式
12.迭代器模式
13.解释器模式
14.访问者模式
15.装饰器模式
16.组合模式
17.外观模式
18.享元模式
19.中介模式
20.备忘录模式
21.桥接模式
22.命令模式
23.状态模式
二.设计模式
1.工厂模式
工厂模式提供了创建对象的接口,而无需制定创建对象的具体类,工厂类封装了具体类的创建过程,客户端无需知道具体实现类只需要知道工厂类,通过工厂类来创建具体实现的类。
1).优缺点
优点:
(1).松耦合:无需知道子类具体创建过程,只需要关注工厂类。
(2).可扩展性:扩展性高,如果想增加一个产品,只需要增加子类对应的工厂类即可。
缺点:
每增加一个产品,就需要增加一个具体的类和对应的工厂类,可维护性并不高,增加了代码复杂性。
2).应用场景
使用数据库连接工厂类连接不同的数据库,比如达梦、崖山等
3).类图
4).具体实现过程
(1).创建一个接口
(2).创建实体类
(3).创建工厂类
(4).根据类型调用工厂类连接数据库
2.抽象工厂模式
抽象工厂模式是围绕一个超级工厂创建其他工厂,此超级工厂又称为其他工厂的工厂,抽象工厂模式提供了一个接口,通过此接口及其具体实现类,可以将对象的创建与客户端代码分离,从而实现系统的松耦合。
1).优缺点
优点:
(1).保证产品族一致性:抽象工厂模式创建的产品族类都是同一类产品,客户端使用此族类的产品的时候也都是使用此族类的产品。
(2).实现松耦合:客户端代码只需要通过抽象工厂接口创建产品无需知道产品具体实现代码。
(3).有利于扩展:增加新的产品族类,只需要添加新的具体工厂和产品类即可。
缺点:
每次增加产品族类都要添加一个此族类的工厂代码,还要添加此工厂的具体实现代码,系统和代码复杂性变高。
2).应用场景
比如格力和美的电器都会生产很多产品,空调、洗衣机、冰箱、热水器等,这些都是产品族类。
3).组成
抽象工厂:一般为接口,声明了创建产品对象的方法;
具体工厂:实现了抽象工厂,用于创建产品对象的具体类;
抽象产品:一般为接口或抽象类,声明实现产品的公共方法,具体产品必须要实现此方法;
具体产品:实现了抽象产品;
4).类图
5).具体实现
(1).创建一个接口
(2).创建接口的实体类
(3).创建抽象工厂类
(4).创建抽象工厂类的扩展实现类
(5).创建生成器
(5).使用
3.单例模式
单例模式确保一个类只有一个实例,单例模式还需要提供一个全局访问方法
1).优缺点
优点:
(1).避免一个全局使用的类频繁地创建与销毁
(2).保证了全局状态的一致性
缺点:
(1).过度使用单例可能导致全局状态不可控
2).应用场景
获取打印机去打印图纸,可以使用单例模式去创建打印机;IP访问次数计数等;
3).具体实现
public class Singleton {
//实例私有
private static Singleton instance;
//构造方法私有
private Singleton() {
}
//创建实例的方法公开,如果没创建就创建实例,如果创建了就不在创建
public static Singleton getInstance() {
if (instance == null) {
// 如果实例为空,则创建一个新实例
instance = new Singleton();
}
return instance;
}
}
4.建造者模式
建造者模式提供了一种允许用户逐步设置各种属性来构建对象的方式,很多参数对于一个对象来说可能是可选的,开发者无需传递所有参数去创建对象,对于可选的参数如果用户未传就要去除,所以我们就可以将构建复杂组件的步骤与运用组件构建对象分离,使用builder模式建立。
1).优缺点
优点:
(1).分离构建过程和表示,使得构建过程更加灵活
(2).隐藏具体构建细节,提高封装性
(3).提供代码复用性
缺点:
(1).增加代码复杂性,对于步骤简单的对象没必要使用此模式
2).应用场景
需要生成的对象具有复杂的内部结构,创建者创建对象的步骤和参数过多而且需要更加灵活
3).具体实现
比如一个用户,姓名、电话、性别是必选的,但是住址、年龄、邮箱是可选的,有些用户可能输入年龄,有些用户可能给了地址等;这里通过UserBuilder去实现根据不同的参数创建User对象;
(1).创建User类和UserBuilder类型
public class User {
//名称 必填
private String name;
//性别 必填
private String sex;
//电话 必填
private String phone;
//年龄 可选
private Integer age;
//住址 可选
private String address;
//住址 可选
private String email;
public User(UserBuilder userBuilder) {
this.name = userBuilder.name;
this.sex = userBuilder.sex;
this.phone = userBuilder.phone;
this.age = userBuilder.age;
this.address = userBuilder.address;
this.email = userBuilder.email;
}
public static UserBuilder builder(String name, String sex, String phone){
return new UserBuilder(name,sex,phone);
}
public static class UserBuilder {
//名称
private String name;
//性别
private String sex;
//电话
private String phone;
//年龄
private Integer age;
//住址
private String address;
//住址
private String email;
//构造器
public UserBuilder(String name, String sex, String phone) {
this.name = name;
this.sex = sex;
this.phone = phone;
}
public UserBuilder age(Integer age) {
this.age = age;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public UserBuilder email(String email) {
this.email = email;
return this;
}
public User build(){
return new User(this);
}
}
}
(2).使用UserBuilder创建user
5.原型模式
原型模式通过复制现有对象来创建新对象,也就是拷贝现有的实例对象来创建对象,而不需重头创建对象。
1).优缺点
优点:
(1).提高创建对象的效率,减少创建对象成本
(2).无需知道构造函数内部逻辑,降低了耦合度
缺点:
(1).对于对象内部的引用有循环接口或者对象内部有不支持序列化的引用对象就不支持拷贝
2).应用场景
当类初始化比较复杂、比较耗时耗性能、需要获取权限等的时候需要使用原型模式
3).深拷贝和浅拷贝
(1).浅拷贝:拷贝一个对象后,基本数据类型的变量会重新创建,引用类型指向的还是原对象所指向的
(2).深拷贝:拷贝一个对象后,基本数据类型还有引用对象都是重新创建的,比如clone方法就是深拷贝
4).具体实现
(1).实体类继承Cloneable ,复写clone方法
public class User implements Cloneable {
//名称 必填
private String name;
//性别 必填
private String sex;
//电话 必填
private String phone;
//年龄 可选
private Integer age;
//住址 可选
private String address;
//住址 可选
private String email;
@Override
public User clone() {
try {
return (User) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
(2).使用
6.适配器模式
通过一个适配器类将一个接口转换成另一个接口,使得两个不兼容的对象能够协同工作,从而消除不兼容问题
1).优缺点
优点:
(1).将不同的系统、库、组件适配在一起,而无需修改太多的代码,提高代码的复用性
(2).拥有更高的灵活性
缺点:
(1).过多的适配器会导致代码混乱降低维护性
2).类图
Mp4PlayerImpl专门用于播放MP4文件,Mp3PlayerImpl专门用于播放MP3,如果Mp4PlayerImpl想播放MP3文件那么就需要一个适配器Mp4PlayerAdapter去适配Mp3PlayerImpl的接口,去代理播放MP3文件。
3).具体实现
(1).实现MP4播放功能
(2).实现MP3播放功能
(3).实现MP4的适配器
(4).使用
7.策略模式
根据条件(比如操作类型)而选择不同的行为或者算法,这就是策略模式,如果将不同的行为代码硬编写在一起会非常臃肿同时也不利于拓展。
1).优缺点
优点:
(1).算法可以根据条件选择
(2).利于拓展
(3).可以将公共方法抽出放在父类中,提高代码复用性
缺点:
(1).每个行为或者算法都要有一个类,这导致类的增加,导致测量膨胀
(2).所有策略类都要对外暴露,不太安全
2).应用场景
需要根据条件去选择不同的行为,比如高速公路显示屏可以根据不同指令选择播放语音、播放文字、还是点亮警报灯
3).类图
抽象类OperateHander有三个子类LineOperateHander、MusicOperateHander、PlayOperateHander,通过适配器器OperateManager根据类型去判断使用不同的Hander
4).具体实现
(1).编写Hander
(2).编写枚举
@Getter
@AllArgsConstructor
public enum OperateType {
LINE_OPERATE("line", "lineOperateHander"),
MUSIC_OPERATE("music", "musicOperateHander"),
PLAY_OPERATE("play", "playOperateHander"),
;
private final String type;
private final String handler;
public static OperateType getByType(String type) {
OperateType[] types = values();
for (OperateType syncType : types) {
if (syncType.getType().equals(type)) {
return syncType;
}
}
return null;
}
}
(3).编写适配器
8.模版模式
模板方法模式通过定义一个抽象的父类,其中包含了流程中公共的行为,子类去实现流程中非公共的行为,这样就可以保证核心流程保持不变,同时增加了代码的复用性
1).优缺点
优点:
(1).增加代码复用性
(2).所有子类共享父类的代码,保证代码一致性
(3).利于维护
缺点:
(1).每一个非公共的行为都要增加一个子类,增加了子类的数量
2).应用场景
请假的流程和请病假的流程基本一致,都需要发起流程、主管审批、经理审批,但是请假发起流程的时候需要输入请假理由、请假天数等,但是请病假需要申请理由、请假天数、还需要医院症断书等,我们将公共部分的流程(发起、主管审批、经理审批)抽到父类,具体的差别子类去实现
3).类图
4).具体实现
(1).编写模版代码
(2).编写请假代码
(3).编写请病假代码
9.观察者模式
观察者模式定义了一种一对多的依赖关系,一个被观察者(主题)对应多个观察者,被观察者发生改变时候会通知所有观察者。观察者实现了与观察者之间的解耦。
1).优缺点
优点:
(1).观察者和被观察者是解耦合的。
(2).主题状态改变时会自动通知观察者,减少手动维护通知的工作
缺点:
(1).异步操作可能无法保证顺序一致性
(2).可能存在循环依赖的问题
2).业务场景
老师发布放假信息,所有的学生都会收到通知
3).具体实现
(1).编写观察者代码
(2).编写主题代码
10. 代理模式
代理模式通过代理对象来代表另一个对象的功能,代理对象控制了对原对象的访问,代理对象负责代替原对象接收客户端的信息并对信息进行拦截、修改、增强然后交给原对象处理。
1).优缺点
优点:
(1).通过引入代理对象添加额外的功能或者增强功能,提高了代码的扩展性、可维护性,提高了灵活性
(2).代理模式将控制和逻辑分开,实现了职责分离
缺点:
(1).代理类会影响性能和访问速度
2).应用场景
日志处理:代理对象可以在客户端访问接口的后记录日志,进行监控、统计等;
权限控制:客户端访问接口需要设置权限,可以通过代理类对接口加上额外权限,客户端去访问代理对象从而实现权限控制
3).类图
4).具体实现
(1).编写接口和具体实现
(2).编写代理类
使用ProxyPlayer代理类去播放音乐即可
11.责任链模式
在责任链模式中每个处理对象都包含一个对下一个处理对象的引用,形成一个链式结构。责任链模式避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
1).优缺点
优点:
(1).发送者和接收者之间解耦
(2).增加创建对象的灵活性,同时利于扩展
缺点:
(1).请求未必会被处理
(2).责任链过长会影响性能
2).应用场景
一个请求可能有多个接收者去处理,但是具体不知道是那个接收者去处理,比如java web的过滤链
3).类图
4).具体实现
(1).编写抽象处理器
(2).编写处理器
(3).使用处理器
12.迭代器模式
迭代器模式提供一种顺序访问一个聚合对象中的每个元素的方式,无须暴漏内部实现细节。
迭代器模式的组成:
集合:负责存储元素
迭代器:负责遍历集合并提供统一的接口
1).优缺点
优点:
(1).解耦合,迭代器模式将遍历封装在迭代器,影藏了具体实现细节
(2).迭代方式更加灵活,可以正向也可以反向
缺点
(1).增加了复杂性,每增加一个聚合类就要增加一个迭代器
2).应用场景
将数组、队列、树这些不同的遍历方式的集合统一封装成一个迭代器,保证了迭代的一致性
3).具体实现
(1).定义迭代器
(2).定义集合
(3).具体使用
13. 解释器模式
解释器模型定义了一种语言的文法表示,并提供了一种解释器来解释这种语言的语句;解释器模式适用于需要解释特定语言和表达式,解释器模式将语句表示为抽象语法树然后提供过解释器来执行解释
1).优缺点
优点:
(1).解释器模型使对较为复杂的语言的解释变得简单
(2).可以根据需求灵活添加文法,具有灵活性
缺点
(1).对于复杂的文法维护困难,对于越复杂文法、递归越深,维护难度越大
(2).不太适合与特别复杂的文法
2).应用场景
比如正则表达式的解析、sql语法的解析都是使用了解释器模型
3).类图
4).具体实现
(1).实现表达式代码
(2).使用
14.访问者模式
当一个对象结构中的元素类需要进行多种不同的操作时,常常会导致操作与元素的类相耦合,每个操作都要修改元素类,这样很不好。访问者模式引入一个称为“访问者”的接口或类,将操作从元素类中抽出来,这样使得操作和元素分离开
1).优缺点
优点:
(1).解决了元素类和操作之间的耦合
(2).每个操作封装在一个独立的访问者中,代码易于维护
(3).可以不修改元素类,拓展访问者,提高了灵活性和可扩展性
缺点
(1).元素类变更很可能会引起访问者的改变
2).应用场景
导航系统访问气象服务系统,气象服务提供气象数据,导航系统根据气象数据做出导航判断
3).类图
4).具体实现
(1).定义气象类
(2).定义访问类
(3).使用
15.装饰器模式
装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。装饰器模式通过一个装饰类来包装原始类,装饰类具有与原始类相同的接口,它内部包含一个指向原始对象的引用,并且可以根据需要包装额外的功能。
1).优缺点
优点:
(1).实现装饰类和原始类解耦合
(2).可以动态地添加或撤销功能,具有灵活性
缺点:
(1).不适合多层次的装饰,会增加复杂性
2).应用场景
权限控制、日志采集等
3).类图
4).具体实现
(1).实现原始类
(2).实现装饰类
新建CatEatMouseDecorator,调catchMouse方法即可实现抓老鼠、吃老鼠功能
16.组合模式
组合模式把一组对象视为一个单一的对象,组合模式将对象组合成树状结构,其中树的节点可以是单个对象或对象组合,无论是操作单个对象还是对象组合,都可以使用统一的方式进行操作。组合模式适用于处理对象的整体-部分关系,它提供一种统一方式来处理这些对象,提高了可扩展性、灵活性,简化了客户端操作
1).优缺点
优点:
(1).简化了客户端操作,客户端无需知道操作的对象是个一组合还是一个单一的对象,同时客户端也无需知道操作内部逻辑,具有透明性
(2).易于扩展,只需要新增叶子节点即可扩展
缺点:
(1).不是所有的对象都适合组合模式,有的甚至增加了复杂性
2).应用场景
例如文件夹和文件系统,文件系统可以看作一个树形结构,根目录为树的根节点,各级文件夹和文件可以视为树的叶子节点。可以使用组合模式来表示文件系统,并且对整个文件系统进行一致的操作,比如创建、删除、复制。
3).类图
4).具体实现
(1).实现文件系统
(2).使用
17.外观模式
外观模式通过引入一个外观类,为客户端提供一个访问子系统简易的接口,外观模式影藏了子系统的复杂性。
1).优缺点
优点:
(1).简化了客户端的操作
(2).降低了客户端和子系统之间的耦合
缺点:
(1).由于外观类影藏了子系统的细节,一些问题不易排查
2).应用场景
对于一些大型复杂的系统,引入外观模式可以增加封装性,通过将系统内部逻辑以及系统之间的交互封装在外观类中,可以使客户端更容易调用复杂系统,同事更容易地进行系统的迭代和维护。
3).类图
4).具体实现
(1).编写颜色类
(2).编写外观类
(3).使用
18.享元模式
享元模式就是共享对象的状态,减少对象的创建,以此来提高性能同时减少内存占用。当系统中需要创建大量相似的对象,同时对象的创建销毁消耗很大,并且对象内部状态可以外部化的时候就可以使用享元模式。
享元模式需要分离出外部状态(不能共享)和内部状态(可以共享),内部状态对象是通过map去保存的,系统会根据外部状态去创建内部状态的对象,当外部状态是一样的时候就不创建内部状态的对象,反之就创建,这样就减少了对象的创建。
1).优缺点
优点:
(1).减少对象的创建、减少了内存占用、提高了性能
缺点:
(1).提高了系统的复杂度,需要分离出外部状态(不能共享)和内部状态(可以共享)。
2).应用场景
数据库连接就是用了享元模式,如果数据库连接池有了就不在创建,反之就创建
3).类图
4).具体实现
(1).创建color类
(2).创建工厂类
(3).通过工厂类生成color类
19.中介模式
中介者模式引入了一个中介者对象来协调对象之间的通信,对象不再直接与其他对象通信,而是通过中介者来发送和接收消息,以此来降低对象之间的耦合。
1).优缺点
优点:
(1).从而降低了对象之间的直接依赖,降低了耦合度、利于迭代和维护
(2).集中管理、降低了对象之间的通信、提高了复用性
缺点:
(1).需要兼容所有的对象,兼容性需要考虑,可过展性也需要考虑
2).类图
3).具体实现
(1).编写消息发送中介类
(2).编写User类
20.备忘录模式
备忘录模式即快照模式,备忘录模式会记住对象的内部状态,在需要的时候可以回滚到对象之前的状态,以此实现撤销、回滚操作
1).优缺点
优点:
(1).实现对象备份操作,使管理更加灵活
(2).提高了封装性
缺点:
(1).会消耗内存
2).类图
3).具体实现
(1).备忘录类
(2).原发器类
(3).负责人类
(4).使用
21.桥接模式
桥接模式通过将抽象部分和具体部分分离,使它们可以独立地变化。桥接模式通过组合的方式(而不是继承方式)建立两个类之间的联系,将抽象和实现的部分连接起来。桥接模式的核心在于解耦抽象和实现,避免使用继承导致的类爆炸问题,提供更灵活的扩展方式。
1).优缺点
优点:
(1).实现抽象和现实分离,更加灵活
(2).使代码结构更加清晰,提高了可维护性
缺点:
(1).系统设计可能变得复杂
2).应用场景
(1).在抽象和具体实现之间需要增加更多的灵活性的场景
(2).一个类存在两个或多个独立变化的维度
(3).不希望使用继承,或多层继承导致类爆炸
3).类图
4).具体实现
(1).编写IColor接口以及两个实现类
(2).编写Shape抽象类以及实现类
(3).使用
22.命令模式
命令模式将请求封装成对象的,将请求的发送者与请求的接收者分开,这两者之间通过命令对象进行沟通,这样可以对请求排队、记录日志、撤销、重做等
1).优缺点
优点:
(1).降低请求者和接受者之间的耦合
(2).可以新增新命令,利于拓展
缺点:
(1).还是会增加系统复杂度,增加维护成本
2).应用场景
在需要对行为进行记录、撤销、重做、事务等处理时候,使用命令模式将请求者和接受者解耦合
3).类图
IOrder是命令接口,DogEat和DogDrink就是狗吃吃东西和狗喝水的两个命令类,RemoteControl(远程控制类)就是命令发送者,Dog就是命令接受者,RemoteControl将命令发送给命令类实现狗的吃饭和喝水功能,这样就将命令发送者和命令接受者解耦了
4).具体实现
(1).编写命令接收者Dog类
(2).编写命令类
(3).编写命令发送者
(4).使用
23.状态模式
状态模式运行对象根据状态改变其行为,当对象内部状态改变时采取不同的操作。状态模式将对象的状态抽象成独立的状态类,每个状态类都实现了一组特定状态下的操作,同时创建一个行为随着状态对象改变而改变的上下文对象。
1).优缺点
优点:
(1).增加状态类不会影响当前类,容易扩展
(2).状态类的逻辑可以复用,提高复用性,同时避免了大量的条件判断去切换行为,提高了可维护性、可读性
(3).将不同状态代码封装在状态类中,提高了封装性,易于维护
缺点:
(1).每个状态就要增加一个类,可能导致类过多
(2).实现代码会变得复杂
(3).频繁切换状态会增加开销
2).应用场景
对象需要根据状态改变行为,同时不希望过多的条件语句的时候采用状态模式是一个很好的选择
3).类图
4).具体实现
(1).编写播放、暂停、停止类
(2).编写上下文类
(3).使用