Design Pattern [ X ] —— OOP七大原则 + 23种设计模式
设计模式的本质是 OOP 面向对象
封装、继承、多态以及类的关联关系+组合关系
架构师 抽象能力
我认为:设计模式只有在重构的时候 与传统方法的对比中,才能清晰体现优势
OOP七大原则
23种设计模式 Design Pattern
七个最常用的设计模式(详细介绍、含源码):
Design Pattern [1] —— 单例模式 Singleton
Design Pattern [2] —— 工厂模式 +抽象工厂模式 Factory
Design Pattern [3] —— 建造者模式 Builder
Design Pattern [4] —— 原型模式 Prototype
Design Pattern [5] —— 观察者模式 Observer / 发布订阅模式
Design Pattern [6] —— 代理模式 Proxy
Design Pattern [7] —— 适配器模式 Adapter
一、创建型模式
1)单例模式
定义:一个类只能构造一个实例对象("构造器私有")
场景:
Spring中的Bean(缓存中取bean很快,减少jvm垃圾回收)(当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象)
Windows任务管理器、回收站;
项目中,配置文件的类;
Servlet; 数据库连接池;
方法:
饿汉式单例 ==》上来直接new对象实例化。坏处是:大量浪费不必要的资源
懒汉式单例 ==》私有构造器(但是不符合线程安全,可能构造了多个对象,从而不符合“单例”)
双重锁检测DCL
静态内部类
枚举=》防止反射,但是没有懒加载
方法一:双重锁检测 DCL
第一次改进:
public static Singleton getInstance() {
if (instance == null) { //外层判断
synchronized (Singleton.class) { //将Singleton类 加锁
if (instance == null) { //原有判断
instance = new Singleton();
}
}
}
return instance;
}
//加上两层:一层if、一层锁
//这样就能保证单例在多线程下的唯一性
//双重检查模式:DCL (Double Check)
第二次改进:
private volatile static Singleton instance = null; //由于instance = new Singleton()的非原子性(new对象3步),所以需要volatile保证强制同步一致
==》volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。
方法二:用静态内部类
public class Holder {
private Holder(){}
public static Holder getInstance() {return InnerClass.HOLDER;}
private static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
这个方法也能被反射破坏单例的唯一性
方法三:枚举
通过查看源码可知,枚举时,无法反射。
public enum EnumSingleton { //这里是enum而不是class
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
详见:Design Pattern [1] —— 单例模式 Singleton
2)工厂模式(含抽象工厂)
定义:
创建者和调用者分离( " 解耦 " ==》开闭原则 )
可扩展性强
使用场景:
FactoryBean ==》IOC
日志门面框架slf4j private final static Logger logger = LoggerFactory.getLogger(HelloWord.class);
JDK 的 Calendar (简单工厂) Calendar calendar = Calendar.getInstance();
JDBC的 Connection
分类:
1)简单工厂:不用new对象,但需要根据传参来判断需要生产的对象类型
2)工厂方法:
加一层,为每个类型写一个new的工厂;
增加了代码量,但是符合OOP原则:开闭原则(新增功能不用改原来的代码)
例子:不同品牌的汽车工厂
3)抽象工厂:
抽象工厂("工厂的工厂")负责创建各个工厂,是一个超级工厂
适用场景:m个工厂(“品牌”)* n种产品
优缺点:符合的OOP原则:开闭原则、依赖倒转原则(针对接口,而不是针对实现)、迪米特法则(只与直接朋友通信)
类图:
1)简单工厂:
2)工厂方法:
3)抽象工厂:
详见:Design Pattern [2] —— 工厂模式 +抽象工厂模式 Factory
3)建造者模式 (生成器)
定义
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可创建不同的表示。
使用场景
-
相同的方法,不同的执行顺序。
-
要初始化的对象十分复杂,如参数多且都具有默认值。
例子:
组装电脑的配置
肯德基的套餐配置
Java中的StringBuilder
ps组合使用:工厂模式建造零件,建造者模式创建复杂对象)
优缺点:
优点:
-
良好的封装性,可使调用者不必知道产品内部组成的细节。(控制细节风险)
-
建造者独立,容易扩展。
缺点:
- 会产生多余的Builder对象以及Director对象,消耗内存。
类图:
worker决定有哪些方法;director决定方法执行的 顺序&数量
详见:Design Pattern [3] —— 建造者模式 Builder
4)原型模式 (克隆)
定义:对象的克隆(含参数)
场景:
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 安全要求的场景。
- 一个对象多个修改者的场景。
- 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
例子:克隆人
关键代码:实现克隆操作,在 JAVA 继承 Cloneable,重写 clone()
要点:clone出来的对象的hashCode和原对象是不同的; 修改属性后,两个对象就不同了,说明不是引用,而是真实复制了副本;
优缺点:
优点:
- 克隆对象会将对象已经设置的属性带出来,而不用在new之后去一个个重新设置。
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
缺点:
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易:特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
- 必须实现 Cloneable 接口。
详见:Design Pattern [4] —— 原型模式 Prototype
二、结构型模式
1)适配器模式(封装器)
定义:适配器模式是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。
例子:插座转换器
优缺点:
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
关键代码:适配器继承已有的对象,实现想要的目标接口。
类图:
详见:Design Pattern [7] —— 适配器模式 Adapter
2)代理模式
定义:代理类在不改变原有类(也就是被代理类)的情况下,对其功能进行扩展.
场景:AOP
分类:静态代理 & 动态代理
静态代理编译时便生成class文件;动态代理运行时通过反射生成对应的class文件
静态代理需要实现接口的所有方法,一个代理类能服务的目标类有限;动态代理可以只对某些方法进行处理,一个代理处理器可以服务多个目标类
对于一些接口比较简单、或者自动生成的通用性代码,可以选择使用静态代理;对于一些庞大的接口,频繁地需要改动接口,你已经觉得改得很烦,可以选择使用动态代理。
优缺点:
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
类图:
1)静态代理
2)动态代理
详见:Design Pattern [6] —— 代理模式 Proxy
3)装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。它是作为现有的类的一个包装。
抽象类继承接口,并增加 "装饰方法"
重点:
4)组合模式(对象树)
核心思想是一个Object会附带一个List<Object>,同一个种类的多个附庸,组成一种类似树状结构
例子:
看程序写结果:
import java.util.ArrayList; import java.util.List; public class Employee { private String name; private String dept; private int salary; private List<Employee> subordinates; //构造函数 public Employee(String name,String dept, int sal) { this.name = name; this.dept = dept; this.salary = sal; subordinates = new ArrayList<Employee>(); } public void add(Employee e) { subordinates.add(e); } public void remove(Employee e) { subordinates.remove(e); } public List<Employee> getSubordinates(){ return subordinates; } public String toString(){ return ("Employee :[ Name : "+ name +", dept : "+ dept + ", salary :" + salary+" ]"); } }
public class CompositePatternDemo { public static void main(String[] args) { Employee CEO = new Employee("John","CEO", 30000); Employee headSales = new Employee("Robert","Head Sales", 20000); Employee headMarketing = new Employee("Michel","Head Marketing", 20000); Employee clerk1 = new Employee("Laura","Marketing", 10000); Employee clerk2 = new Employee("Bob","Marketing", 10000); Employee salesExecutive1 = new Employee("Richard","Sales", 10000); Employee salesExecutive2 = new Employee("Rob","Sales", 10000); CEO.add(headSales); CEO.add(headMarketing); headSales.add(salesExecutive1); headSales.add(salesExecutive2); headMarketing.add(clerk1); headMarketing.add(clerk2); //打印该组织的所有员工 System.out.println(CEO); for (Employee headEmployee : CEO.getSubordinates()) { System.out.println(headEmployee); for (Employee employee : headEmployee.getSubordinates()) { System.out.println(employee); } } } }
输出结果:
Employee :[ Name : John, dept : CEO, salary :30000 ] Employee :[ Name : Robert, dept : Head Sales, salary :20000 ] Employee :[ Name : Richard, dept : Sales, salary :10000 ] Employee :[ Name : Rob, dept : Sales, salary :10000 ] Employee :[ Name : Michel, dept : Head Marketing, salary :20000 ] Employee :[ Name : Laura, dept : Marketing, salary :10000 ] Employee :[ Name : Bob, dept : Marketing, salary :10000 ]
5)外观模式(门面模式)
目的:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
例子: 1、去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。 2、JAVA 的三层开发模式。
关键代码:在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
优缺点:
优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。
缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
6)桥接模式
例子:
共需要3x2=6个类啊,大杯原味和加糖,中杯原味和加糖,小杯原味和加糖。过段时间万一那二笔客户又要出加奶,加蜂蜜等等口味,说不定还有迷你杯,女神杯等等规格的咖啡,那我这边的类不就爆炸了吗?看来的去找个设计模式了。。。
此场景桥接模式正合适,这里有两个变化维度,咖啡的容量和口味,而且都需要独立变化。
如果使用继承的方式,随着变化类就会急剧的增加。
你可以将容量理解为抽象部分,而口味理解为实现部分,这两个部分需要桥接。
==》一个维度用抽象类继承extends,另一个用接口实现implement
7)享元模式(缓存)
目的:减少创建对象的数量。在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建对象。
使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。
例子:
1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。
2、数据库的数据池。
关键代码:用 HashMap 存储这些对象。
==》用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
import java.util.HashMap;
public class ShapeFactory {
private static final HashMap<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle)circleMap.get(color);//先从HashMap里面找
if(circle == null) {
circle = new Circle(color);
circleMap.put(color, circle); //没有才会创建对象
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}
三、行为型模式
1)观察者模式(发布-订阅模式:Event-Subscriber)
定义:对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新(广播)。
例子:订报纸、公众号
优缺点:
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
类图:
详见:Design Pattern [5] —— 观察者模式 Observer / 发布订阅模式
2)策略模式
描述:一个类的行为或其算法可以在运行时更改
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
public class Context { private Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public int executeStrategy(int num1, int num2){ return strategy.doOperation(num1, num2); } }
public class StrategyPatternDemo { public static void main(String[] args) { Context context = new Context(new OperationAdd()); System.out.println("10 + 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationSubtract()); System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationMultiply()); System.out.println("10 * 5 = " + context.executeStrategy(10, 5)); } }
3)迭代器模式
意图:
在不暴露集合底层数据结构的情况下,遍历集合中所有的元素
上面代码可以发现,用 iterator来遍历的时候,确实没有暴露底层数据结构。
如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。
应用实例:JAVA 中的 iterator;foreach遍历
优点:
1、它支持以不同的方式遍历一个聚合对象。
2、迭代器简化了聚合类。
3、在同一个聚合上可以有多个遍历。
4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
代码:
第一部分——造轮子:
public interface Iterator {public Object next(); //遍历到下一个元素 public boolean hasNext(); //是否已经遍历到尾部 }
import java.util.Vector; public class MyIterator implements Iterator { private Vector vector = new Vector(); public int cursor = 0; //定义当前游标 public MyIterator(Vector vector) { this.vector = vector; } @Override public Object next() { Object result = null; if (this.hasNext()) result = this.vector.get(this.cursor ++); else result = null; return result; } @Override public boolean hasNext() { if (this.cursor == this.vector.size()) return false; else return true; } }
public interface Aggregate { //集群 public void add(Object object); public Iterator iterator(); }
import java.util.Vector; public class MyAggregate implements Aggregate { private Vector vector = new Vector(); @Override public void add(Object object) { this.vector.add(object); } @Override public Iterator iterator() { return new MyIterator(this.vector); } }
第二部分——测试:
public class Test {
public static void main(String[] args) {
//集群aggregate相关:
Aggregate aggregate = new MyAggregate();
aggregate.add("Hello!!");
aggregate.add("青杨风");
aggregate.add("2199");
//用iterator迭代器来遍历:
Iterator iterator = aggregate.iterator();
while (iterator.hasNext()) {
System.out.print(" >> " + iterator.next());
}
}
}
输出结果:
类图:
4)命令模式(事务)
意图:
命令队列 +可撤销操作
无法抵御变化的紧耦合是不合适的
如何解决:
通过调用者调用接受者执行命令,顺序:调用者→命令(下面例子中的broker)→接受者。
使用场景:
命令:如GUI按钮
事务:可撤销操作
关键代码:
import java.util.ArrayList;
import java.util.List;
public class Broker { //broker的意思是:经纪人
private List<Order> orderList = new ArrayList<Order>();
public void takeOrder(Order order){ //获取命令列表
orderList.add(order);
}
public void placeOrders(){
for (Order order : orderList) { //执行命令列表
order.execute();
}
orderList.clear();
}
}
public class Test {
public static void main(String[] args) {
//茅台股票
Stock MaoTaiStock = new Stock("茅台");
BuyStock buyStockOrder1 = new BuyStock(MaoTaiStock,100);
SellStock sellStockOrder1 = new SellStock(MaoTaiStock,50);
//Tesla股票
Stock TeslaStock =new Stock("Tesla");
BuyStock buyStockOrder2 = new BuyStock(TeslaStock,120);
SellStock sellStockOrder2 = new SellStock(TeslaStock,40);
SellStock sellStockOrder3 = new SellStock(TeslaStock,40);
//broker:经纪人
Broker broker = new Broker();
//命令开始排队
broker.takeOrder(buyStockOrder1);
broker.takeOrder(buyStockOrder2);
broker.takeOrder(sellStockOrder2);
broker.takeOrder(sellStockOrder1);
broker.takeOrder(sellStockOrder3);
//开始执行
broker.placeOrders();
}
}
运行结果:
类图:
如上图,不加设计模式时,只要Stock和Test就可以完成基本的功能。
加上一层解耦后 可以:对命令排队+支持撤销操作
5)模板模式
描述:写抽象类作为父类,然后多个子类重写父类方法
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
//模板
public final void play(){ //play()是模板方法,而里面具体的方法是要重写的方法;final修饰play(),这样就确定了play里面的子结构=》子类只能重写子函数,而不能重写play函数
//初始化游戏
initialize();
//开始游戏
startPlay();
//结束游戏
endPlay();
}
}
public class Football extends Game {
@Override
void endPlay() { //重写
System.out.println("Football Game Finished!");
}
@Override
void initialize() { //重写
System.out.println("Football Game Initialized! Start playing.");
}
@Override
void startPlay() { //重写
System.out.println("Football Game Started. Enjoy the game!");
}
}
类图:
6)状态模式
意图:能够根据对象状态的变化,改变其行为
使用场景:如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。
优点:
- 单一职责原则。 将与特定状态相关的代码放在单独的类中。
- 开闭原则。 无需修改已有状态类和上下文就能引入新状态。
- 消除臃肿的状态机if-else条件语句,生成状态类层次结构,通过将公用代码抽取到抽象基类中来减少重复。
状态模式vs策略模式:
相同点:状态模式可被视为策略模式的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。
不同点:策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。
例子:
首先有个抽象类state作为父类,里面4个方法;
然后实例化3个子类,每个子类都有state状态属性,不同状态下,对4种方法进行不同的重写
在4个方法里面,有改变现有状态的能力 + 执行现有状态下应有操作的能力
public abstract class State { Player player; State(Player player) { this.player = player; } public abstract String onLock(); public abstract String onPlay(); public abstract String onNext(); public abstract String onPrevious(); }
public class LockedState extends State { LockedState(Player player) { super(player); player.setPlaying(false); } @Override public String onLock() { if (player.isPlaying()) { player.changeState(new ReadyState(player)); return "Stop playing"; } else { return "Locked..."; } } @Override public String onPlay() { player.changeState(new ReadyState(player)); return "Ready"; } @Override public String onNext() { return "Locked..."; } @Override public String onPrevious() { return "Locked..."; } }
public class ReadyState extends State { public ReadyState(Player player) { super(player); } @Override public String onLock() { player.changeState(new LockedState(player)); return "Locked..."; } @Override public String onPlay() { String action = player.startPlayback(); player.changeState(new PlayingState(player)); return action; } @Override public String onNext() { return "Locked..."; } @Override public String onPrevious() { return "Locked..."; } }
public class PlayingState extends State { PlayingState(Player player) { super(player); } @Override public String onLock() { player.changeState(new LockedState(player)); player.setCurrentTrackAfterStop(); return "Stop playing"; } @Override public String onPlay() { player.changeState(new ReadyState(player)); return "Paused..."; } @Override public String onNext() { return player.nextTrack(); } @Override public String onPrevious() { return player.previousTrack(); } }
7)职责链模式
意图:职责链模式是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。在职责链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。
if-else语句虽然简单,但难以重构:
-
LeaveApproval
类比较庞大,各个上级的审批方法都集中在该类中,违反了 "单一职责原则",测试和维护难度大 -
当需要修改该请假流程,譬如增加当天数大于30天时还需提交给董事长处理,必须修改该类源代码(并重新进行严格地测试),违反了 "开闭原则"
-
该流程缺乏灵活性,流程确定后难以修改,客户端无法定制流程
Pipeline
和 ChannelHandler
@Data
public abstract class Handler {
protected String name; // 处理者姓名
protected Handler nextHandler; // 下一个处理者;这里使得连成一条链,非常便于扩充
public Handler(String name) {
this.name = name;
}
public abstract boolean process(LeaveRequest leaveRequest); // 处理请假
}
测试:
public class Test {
public static void main(String[] args) {
Handler zhangsan = new Director("张三");
Handler lisi = new Manager("李四");
Handler wangwu = new TopManager("王五");
// 创建责任链
zhangsan.setNextHandler(lisi);
lisi.setNextHandler(wangwu);
// 发起请假申请
boolean result1 = zhangsan.process(new LeaveRequest("青杨风", 1));
System.out.println("最终结果:" + result1 + "\n");
boolean result2 = zhangsan.process(new LeaveRequest("青杨风", 4));
System.out.println("最终结果:" + result2 + "\n");
boolean result3 = zhangsan.process(new LeaveRequest("青杨风", 8));
System.out.println("最终结果:" + result3 + "\n");
}
}
类图:
8)中介者模式(控制器Controller)
意图:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
何时使用:多个类相互耦合,形成了网状结构。
如何解决:将上述网状结构分离为星型结构。
关键代码:对象 Colleague 之间的通信封装到一个类中单独处理。
应用实例: 1、中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。 2、机场调度系统。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。
优点: 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。
缺点:中介者会庞大,变得复杂难以维护。
9)访问者模式
上图中,Computer、Keyboard、Mouse、Monitor作为 "稳定的" 数据结构,是不宜变化的:增加一个就会要改动Visitor接口及所有Visitor对象;
而ComputerPartDisplayVisitor等对象,作为 "变化的" 数据操作,是要不断增加实例对象的。
意图:
将数据结构与数据操作分离。
提供一个作用于某种对象结构上的各元素的操作方式,可以使我们在不改变元素结构的前提下,定义作用于元素的新操作。
何时使用:稳定的数据结构 + 易变的操作
关键代码:在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。
优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。
缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。
10)备忘录模式(快照Snapshot)
意图:保存一个对象的某个状态,以便在适当的时候恢复对象。
主要解决:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
何时使用:很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。
如何解决:通过一个备忘录类专门存储对象状态。
关键代码:客户不与备忘录类耦合,与备忘录管理类耦合
应用实例:
1、Git管理:git reset --hard 版本号。
2、打游戏时的存档。
3、Windows 里的 Ctrl +Z 和 Ctrl +Y
4、IE 中的后退。
5、数据库的事务管理:备份与还原。
优点: 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点:消耗空间资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
使用场景: 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。
注意事项: 1、为了符合迪米特原则,还要增加一个管理备忘录的类。 2、为了节约内存,可使用原型模式+备忘录模式。
11)解释器模式
意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
主要解决:对于一些固定文法构建一个解释句子的解释器。
何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
如何解决:构建语法树,定义终结符与非终结符。
关键代码:构建环境类,包含解释器之外的一些全局信息,一般是 HashMap。
应用实例:
编译器、运算表达式计算。
SQL 解析、符号处理引擎
优点: 1、可扩展性比较好,灵活。 2、增加了新的解释表达式的方式。 3、易于实现简单文法。
缺点: 1、可利用场景比较少。 2、对于复杂的文法比较难维护。 3、解释器模式会引起类膨胀。 4、解释器模式采用递归调用方法。
使用场景: 1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。 2、一些重复出现的问题可以用一种简单的语言来进行表达。 3、一个简单语法需要解释的场景。
参考:https://refactoringguru.cn/design-patterns/ ==》一个超赞的教程
https://juejin.cn/user/3896324938269278/posts ==》写了很多应用实例的源码分析
https://www.runoob.com/design-pattern/ 菜鸟教程
我的Github里有 文中例子的源码,可以clone下来自己跑下:https://github.com/Yang2199/Design-Pattern/tree/master/src