23种设计模式
1. 概述
OOP(面向对象)七大原则
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立
- 依赖倒置原则:要面向接口编程,不要面向实现编程
- 单一职责原则:控制类的粒度大小,将对象解耦、提高其内聚性
- 接口隔离原则:要为各个类建立它们需要的专用接口
- 迪米特法则:只与你的直接朋友交谈,不跟陌生人说话
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
个人解释一下上面的七大原则
- 首先是开闭原则,什么叫扩展,什么叫修改?如果写一段代码就是修改的话那我们什么都不要做了,他显然不是这个意思,个人理解应该是从不同粒度进行分析,如果我想加一个功能,在我原来已经写好的代码(方法、对象等)中间进行修改肯定不是一个好的选择,因为这可能造成一些新的bug,最好是加一个方法或者新建一个类或者模块来处理,那么这就是对扩展开放,对修改关闭
- 里氏替换个人感觉比较简单,如果子类继承了父类(超类),那么父类中拥有的性质在子类中也应该成立,也就是说同一个方法它们的功能和抛出的异常等特性应该是一致的
- 依赖倒置,这个使用spring的同学应该比较熟悉了,不要让用户自己处理依赖,而是交给spring的IOC容器来进行管理;或者说不要管功能是怎么实现的,假定功能已经实现,就是面向接口编程
- 单一职责原则就是尽量让一个类或者功能只负责一个功能,比如说构造函数就是负责初始化,不要在里面加逻辑,否则定位问题变得困难
- 接口隔离原则就是每个类有它自己的专用接口,不要把接口放在别人那里
- 迪米特原则也是为了降低耦合度
- 合成复用原则主要说的是能组合就不要直接继承,比如说电动汽车和汽油汽车,白车黑车红车,这组合起来一共六种情况,如果直接继承那么要写六个类,但是实际上我们只需要写一个机车类作为父类,然后两个子类分别是电动汽车和汽油汽车然后里面有一个color属性即可,这样就只写了两个类,也就是优先考虑组合或者聚合等关联关系来实现,而不是直接继承
23种设计模式
一、创建型
创建型设计模式抽象了实例化过程,它们帮助一个系统独立于如何创建、组合和表示它的那些对象
- 工厂模式(Factory Method):定义一个用于创建对象的接口,让子类决定将哪一个类实例化,Factory Method使一个类的实例化延迟到其子类
- 抽象工厂(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
- 建造者模式(Builder):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
- 原型模式(Prototype):用于创建重复的对象,同时能够保持性能
- 单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点
二、结构型
结构型模式涉及如何组合类和对象以获得更大的结构,结构型模式采用继承机制来组合接口或实现
- 适配器模式(Adapter):把一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作
- 桥接模式(Bridge):将抽象部分与它的实现部分分离,使它们可以独立地变化
- 组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性
- 装饰模式(Decorator):动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活
- 外观模式(Facade):为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一个子系统更加容易使用
- 享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象
- 代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问
三、行为型
行为型模式涉及算法和对象间职责的分配。行为型模式不仅描述对象或类的模式,还描述它们之间的通信模式。这些模式刻画了在运行时难以跟踪的复杂的控制流。它们将你的注意力从控制流转移到对象间的联系方式上来
- 职责链(Chain of Responsibility): 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
- 命令(Command):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作
- 解释器(Interpreter):给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
- 迭代器(Iterator):提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示
- 中介者(Mediator):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
- 备忘录(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态
- 观察者(Observer):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
- 状态(State):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类
- 策略(Strategy):定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换,本模式使得算法可独立于使用它的客户而变化
- 模板方法(Template Method):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
- 访问者(Visitor):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
1. 创建型模式
1.1 工厂模式
1.1.1 简单工厂
- 简单工厂也叫静态工厂,因为工厂类定义了一个静态方法,所谓简单工厂,就是将类实例化的操作与使用对象的操作分开,让使用者不用知道具体参数就能实例化出需要的产品类,从而避免了在客户端显式指定,实现了解耦。换句话说就是正常你创建一个对象就是
new
一下,但是这样你肯定要写参数,但是如果用简单工厂,我就写一个工厂类来处理这些操作,而不需要你去new
- 比如说写一个车接口
package org.example.factory;
/**
* @Author: ComingLiu
* @Date: 2022/10/30 20:53
*/
public interface Car {
void name();
}
- 然后假设我要生产两种车,五菱和特斯拉,那么我就写两个实现如下
package org.example.factory;
/**
* @Author: ComingLiu
* @Date: 2022/10/30 20:55
*/
public class Wuling implements Car{
@Override
public void name(){
System.out.println("五菱!");
}
}
package org.example.factory;
/**
* @Author: ComingLiu
* @Date: 2022/10/30 20:56
*/
public class TeSila implements Car{
@Override
public void name(){
System.out.println("特斯拉!");
}
}
- 那么假设客户需要一辆五菱,如果不用设计模式,可能是下面这样
package org.example.factory;
/**
* @Author: ComingLiu
* @Date: 2022/10/30 20:54
*/
public class Consumer {
public static void main(String[] args) {
Car car = new Wuling();
}
}
- 如果我们用简单工厂来做,那么我们可以像下面这样
package org.example.factory;
/**
* @Author: ComingLiu
* @Date: 2022/10/30 20:54
*/
public class Consumer {
public static void main(String[] args) {
Car car = CarFactory.getCar("五菱");
}
}
- 可以看出我们不需要
new
,这样我们不用填写一些简单参数,就会比较方便 - 但是问题呢?如果我们想要添加一种车,那么我们肯定要修改原来的类了,这样就违反了开闭原则,也就是对扩展开放,对修改关闭
1.1.2 工厂方法
- 工厂方法就是为每一种车设置他自己的工厂,然后创建的时候用他自己的工厂生产车,这样满足了开闭原则,但是问题是增大了代码量
1.1.3 抽象工厂
- 就是使用一个抽象工厂,然后用各自的工厂去实现这个抽象工厂,比如五菱工厂、特斯拉工厂等等,他要去实现抽象工厂的方法,不同产品是一个一个的产品族,但是增加产品(方法)会导致所有其他产品(工厂)都要实现这个接口
1.2. 建造者模式
- 同一套流程可能有不同的顺序,以汽车的开启、结束和鸣笛这三个步骤来说,顺序可能有很多,如何去组织这一过程呢?我们可以先写一下抽象类和逻辑,以特斯拉和五菱为例,我们把构建顺序传进一个
ArrayList
中,
package org.example.builder;
import java.util.ArrayList;
public abstract class Car {
// 方法执行顺序
private ArrayList<String> order = new ArrayList<>();
protected abstract void start();
protected abstract void stop();
protected abstract void alarm();
final public void run(){
for(String action : order){
switch (action){
case "start":{
this.start();
break;
}
case "stop":{
this.stop();
break;
}
case "alarm":{
this.alarm();
break;
}
default:{
break;
}
}
}
}
public Car setOrder(ArrayList<String> order) {
this.order = order;
return this;
}
}
- 特斯拉的逻辑
package org.example.builder;
public class TeSila extends Car{
@Override
protected void start() {
System.out.println("TeSila started.");
}
@Override
protected void stop() {
System.out.println("TeSila stopped.");
}
@Override
protected void alarm() {
System.out.println("TeSila alarmed.");
}
}
- 五菱的逻辑
package org.example.builder;
public class WuLing extends Car{
@Override
protected void start() {
System.out.println("WuLing started.");
}
@Override
protected void stop() {
System.out.println("WuLing stopped.");
}
@Override
protected void alarm() {
System.out.println("WuLing alarmed");
}
}
- 客户端调用
package org.example.builder;
import java.util.ArrayList;
public class Client {
public static void main(String[] args) {
TeSila car = new TeSila();
ArrayList<String> order = new ArrayList<>();
order.add("start");
order.add("alarm");
order.add("stop");
car.setOrder(order);
car.run();
}
}
- 这样写没有问题,但是这样显得客户端和抽象类之间耦合度太高,所以我们加一层建造者,把顺序交给建造者去建造(可以理解为建造一个建筑物)
package org.example.builder;
import java.util.ArrayList;
public abstract class CarBuilder {// 汽车建造者
public abstract void setOrder(ArrayList<String> sequence);
public abstract Car getCar();
}
package org.example.builder;
import java.util.ArrayList;
public class TeSilaBuilder extends CarBuilder{// 特斯拉建造者
private final TeSila car = new TeSila();
@Override
public void setOrder(ArrayList<String> order) {
car.setOrder(order);
}
@Override
public TeSila getCar() {
return car;
}
}
package org.example.builder;
import java.util.ArrayList;
public class WuLingBuilder extends CarBuilder{// 五菱建造者
private final WuLing car = new WuLing();
@Override
public void setOrder(ArrayList<String> order) {
car.setOrder(order);
}
@Override
public WuLing getCar() {
return car;
}
}
- 客户端调用
package org.example.builder;
import java.util.ArrayList;
public class Client {
public static void main(String[] args) {
TeSilaBuilder teSilaBuilder = new TeSilaBuilder();
WuLingBuilder wuLingBuilder = new WuLingBuilder();
ArrayList<String> order = new ArrayList<>();
order.add("start");
order.add("alarm");
order.add("stop");
teSilaBuilder.setOrder(order);
TeSila car = teSilaBuilder.getCar();
car.run();
wuLingBuilder.setOrder(order);
WuLing car2 = wuLingBuilder.getCar();
car2.run();
}
}
- 优点
- 封装性:客户端不必知道产品内部组成的细节
- 独立,易于扩展:各个建造者之间相互独立,对系统的扩展非常有利
- 便于控制细节风险:由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响
应用场景
- JDK中Calendar的getInstance方法
- JDBC中的Connection对象的获取
- Spring中IOC容器创建管理bean对象
- 反射中Class对象的newInstance方法
- 相同的方法,不同的执行顺序,产生不同的执行结果的时候
- 多个零件组装到同一个对象中,但是产生的运行结果又不相同时
- 产品类非常复杂,或者产品类的调用顺序不同产生了不同的效能
1.3. 单例模式
- 确保每个类只有一个实例,也就是说我们每次新建的类对象必须还得是原来那个,分为懒汉和饿汉两种
- 从字面上理解,懒汉就是懒加载,也就是资源延迟分配,饿汉就是申请的时候直接分配空间,我们先写饿汉式单例
package org.example.single;
/**
* @Author: ComingLiu
* @Date: 2022/11/4 0:19
*/
public class Hungry {
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
- 饿汉式的写法,因为要求是我们每次获取的都必须是原来的对象,所以我们首先将构造函数私有化,使得无法通过构造函数来new一个变量,然后在类中new一个静态变量并使用一个函数来控制,这样每次访问的都是这个变量
- 但是这种方法的问题在于每次申请的时候就预先分配空间,造成资源浪费,所以还有改进的懒汉式
package org.example.single;
/**
* @Author: ComingLiu
* @Date: 2022/11/4 0:21
*/
public class LazyMan {
private LazyMan(){
}
private volatile static LazyMan lazyMan;// 防止指令重排
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();// 不是原子操作
/**
* 1. 分配内存空间
* 2. 执行构造方法, 初始化对象
* 3. 把这个对象指向这个空间
* (可能指令重排, 导致没有完成构造方法)
*/
}
}
}
return lazyMan;
}
}
- 这里需要注意几个关键点,第一是我们要使用双重检测锁机制来做这个东西,这是为了解决并发环境下的线程安全问题;第二个是我们要使用volatile来修饰类对象,原因是编译器会进行指令重排,因为
new
一个对象要经历三步,首先是分配内存空间;然后执行构造方法,初始化对象;最后是把这个对象指向这个分配的空间。那么根据happens-before原则,后两步的指令是可以互换的,如果它们发生了互换,那么并发环境下可能出现一个线程尚未初始化对象然后另一个线程访问到了这个对象,当前这个对象尚未初始化但已经分配了内存空间,那么这个对象当然不是空,所以必须禁止这两步之间的指令重排 - 还有静态单例模式
package org.example.single;
public class staticSingleton {
private staticSingleton(){
}
private static class InstanceHolder{
private final static staticSingleton instance = new staticSingleton();
}
public static staticSingleton getInstance(){
return InstanceHolder.instance;
}
}
- 由于Java静态域的初始化和静态代码块的执行只在类加载的时候执行且只执行一次,所以这个可以是天然的单例模式
- 但是上述方法共同的问题是可以使用反射破解,比如下面这样
package org.example.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class singleTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class objectClass = Class.forName("org.example.single.LazyMan");
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazyMan lazyMan = (LazyMan) constructor.newInstance();
System.out.println(lazyMan);
System.out.println(LazyMan.getInstance());
}
}
- 在反射面前几乎一切private都是虚的,但是我们有枚举类型可以抵挡反射,因为反射内部有机制,一旦对方是枚举,那么自身抛出异常,这也算是一物降一物吧
package org.example.single;
public enum enumSingleton {
INSTANCE;
private enumSingleton() {
}
public static enumSingleton getInstance() {
return INSTANCE;
}
}
1.4. 原型模式
- 将构造方法转换为克隆方法,从而加快程序执行效率,但这里面的clone()方法是Object类提供的,是浅拷贝,所以当需要深拷贝的时候,就需要重写克隆方法实现对象的拷贝
package org.example.prototype;
import java.util.Date;
/**
* @Author: ComingLiu
* @Date: 2022/11/4 0:41
*/
public class Video implements Cloneable{
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException{
return super.clone();
}
public Video(){
}
public Video(String name, Date createTime) {
this.name = name;
this.createTime = createTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
package org.example.prototype;
import java.util.Date;
/**
* @Author: ComingLiu
* @Date: 2022/11/4 0:47
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
Video v1 = new Video("hello", date);
Video v2 = (Video) v1.clone();// 浅克隆, 基本类型拷贝, 引用类型指向同一地址
System.out.println(v1);
System.out.println(v2);
date.setTime(123);
System.out.println(v1);
System.out.println(v2);
}
}
- 打印结果会发现这两个是一样的,这说明这是一个浅拷贝,引用类型还是指向那个地址,那么如何实现一个深拷贝呢?应该把引用类型也进行一次克隆
package org.example.prototype;
import java.util.Date;
/**
* @Author: ComingLiu
* @Date: 2022/11/4 0:41
*/
public class Video implements Cloneable{
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException{
// 深克隆, 或者序列化反序列化
Video v = (Video) super.clone();
v.createTime = (Date) this.createTime.clone();// 将这个对象的属性也进行克隆
return v;
}
public Video(){
}
public Video(String name, Date createTime) {
this.name = name;
this.createTime = createTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
- 这样就实现了一次深拷贝
- 原型模式的优点在于不需要调用构造函数就可以实现对象的创建,适用于类实例对象创建开销较大的情况;但是缺点在于深拷贝和浅拷贝容易出问题,需要灵活使用,且必须覆盖
clone()
方法
2. 结构型模式
2.1. 适配器模式
- 最好的例子就是两脚插头和三脚插头,如果插座是两孔的,那么肯定没办法把三脚插头插进去,所以我们需要对接口进行一次转换,使得接口适配
首先写一个类,让它有一个两孔插座
package org.example.adapter;
public class Hotel {
private TwoHole twoHole = new TwoHoleImpl();
public void setThreeHole(TwoHole twoHole){
this.twoHole = twoHole;
}
public void charge(){
twoHole.charge();
}
}
package org.example.adapter;
public interface TwoHole {
void charge();
}
package org.example.adapter;
public class TwoHoleImpl implements TwoHole{
@Override
public void charge() {
System.out.println("使用两孔插头充电");
}
}
写好两孔插座接口和实现,现在调用显然是两孔插座的充电方法,但是我现在只有三孔插头,那我再写一个三孔插头的接口和实现
package org.example.adapter;
public interface ThreeHole {
void charge();
}
package org.example.adapter;
public class ThreeHoleImpl implements ThreeHole{
@Override
public void charge() {
System.out.println("使用三孔插头充电");
}
}
那么现在我要加上一个适配器,把三孔插头转换为两孔插头
package org.example.adapter;
public class TwoHoleAdapter implements TwoHole{
private ThreeHole threeHole = new ThreeHoleImpl();
public TwoHoleAdapter(ThreeHole threeHole){
this.threeHole = threeHole;
}
@Override
public void charge() {
threeHole.charge();
}
}
测试类如下
package org.example.adapter;
public class Adapter {
public static void main(String[] args) {
Hotel hotel = new Hotel();
hotel.charge();
ThreeHole threeHole = new ThreeHoleImpl();
TwoHoleAdapter twoHoleAdapter = new TwoHoleAdapter(threeHole);// 三孔变两孔
hotel.setThreeHole(twoHoleAdapter);// 用三孔充电
hotel.charge();
}
}
可以实现使用两孔的插头给三孔充电器充电
2.2. 桥接模式
- 主要用于解决有两个独立变化的维度问题,适用于系统可能有多个角度分类,且每一种角度都可能变化,在这种情况下,用继承不灵活
- 举一个例子,假设我们要设计一个类,功能是以某种颜色画一个形状,要求能够在运行时确定使用的是哪种颜色,首先我们写一个接口画图
package org.example.bridge;
public interface DrawAPI {
void drawCircle();
}
- 那么我们还需要写一个形状抽象类来控制形状
package org.example.bridge;
public abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI){
this.drawAPI = drawAPI;
}
public abstract void draw();
}
- 然后写一个子类圆
package org.example.bridge;
public class Circle extends Shape{
private final int x, y, r;
protected Circle(int x, int y, int r, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.r = r;
}
@Override
public void draw() {
drawAPI.drawCircle(x, y, r);
}
}
- 那么接下来我们要写两个实现分别是画红色圆和绿色圆
package org.example.bridge;
public class RedCircle implements DrawAPI{
@Override
public void drawCircle(int x, int y, int r) {
System.out.printf("Red x=%d, y=%d, r=%d\n", x, y, r);
}
}
package org.example.bridge;
public class GreenCircle implements DrawAPI{
@Override
public void drawCircle(int x, int y, int r) {
System.out.printf("Green x=%d, y=%d, r=%d\n", x, y, r);
}
}
- 然后调用
package org.example.bridge;
public class BridgeDemo {
public static void main(String[] args) {
Circle redCircle = new Circle(1, 2, 3, new RedCircle());
Circle greenCircle = new Circle(1, 2, 3, new GreenCircle());
redCircle.draw();
greenCircle.draw();
}
}
- 可以发现这样能够实现运行时动态设置类型,这个shape抽象类相当于一个桥
2.3. 组合模式
- 这种设计模式类似菜单和子菜单,从外观上看它们没有区别,我们希望使用一个类解决所有问题
- 换句话说就是一个类里面包含很多个类,而这些类组成了一种树形结构,类和类之间看起来没有区别
比如下面的例子,一个员工会有一些下属
package org.example.composite;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Employee {
private String name;
private String job;
private Integer salary;
List<Employee> subordinates;
public Employee(String name, String job, Integer salary){
this.name = name;
this.job = job;
this.salary = salary;
this.subordinates = new ArrayList<>();
}
public void add(Employee e){
subordinates.add(e);
}
public void remove(Employee e){
subordinates.remove(e);
}
public List<Employee> getSubordinates(){
return subordinates;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", salary=" + salary +
", subordinates=" + subordinates +
'}';
}
}
下属还会有下属,这样递归下去的设计模式就是组合模式
package org.example.composite;
public class EmployeeDemo {
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 e : CEO.getSubordinates()){
System.out.println(e);
for(Employee e2 : e.getSubordinates()){
System.out.println(e2);
}
}
}
}
2.4. 装饰模式
- 比如我们要设计一个机器人,它需要有唱歌的功能,但是我现在想让他走路,怎么办呢?为了不破坏开闭原则,我们应该进行扩展而不是修改,那显然一种方式就是把它装在另一个类里面,然后让新类具备走路功能,这样还有一个好处就是可以动态的添加功能,也就是说可以让它随时走路,这就是装饰模式,下面代码简单演示一下
package org.example.decorator;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/08/18:20
* @Description:
*/
public class RobotDecorator implements Robot{
private final Robot robot;
public RobotDecorator(Robot robot){
this.robot = robot;
}
@Override
public void doSomeThing() {
robot.doSomeThing();
}
public void doMoreThing(){
System.out.println("走路");
}
}
package org.example.decorator;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/08/18:19
* @Description:
*/
public class FirstRobot implements Robot{
@Override
public void doSomeThing() {
System.out.println("唱歌");
}
}
package org.example.decorator;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/08/17:57
* @Description:
*/
public interface Robot {
void doSomeThing();
}
package org.example.decorator;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/08/21:37
* @Description:
*/
public class Decorator {
public static void main(String[] args) {
RobotDecorator decorator = new RobotDecorator(new FirstRobot());
decorator.doMoreThing();
}
}
- 在上面的代码中,我们对 F i r s t R o b o t FirstRobot FirstRobot进行了装饰,使其具备了走路的能力,且可以根据需要添加这个功能
- 可能会感觉装饰模式和静态代理代码好像是一样的,但是个人理解,装饰模式针对自己,给自己套层壳使我具备新功能;而代理模式是找别人,让别人替我做事情,同时他也具备新的功能,而代码层面,别人和壳是一样的,所以这两种设计模式是类似的
- 在Java的IO流中涉及到了许多的装饰模式,比如下面这个
FileInputStream inputStream=new FileInputStream(file);
//把inputStream装饰成BufferedReader来成为具备缓冲能力的reader
BufferedReader reader=new BufferedReader(inputStreamReader);
2.5. 外观模式
- 软件开发中常见的开发方式是将一个系统分为几个子系统,分而治之,可降低系统的复杂性,而外观模式相当于给子系统加了一个界面,也就是外观,这样就降低了子系统间的相互依赖关系
- 示例代码如下
package org.example.facade;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/10/0:32
* @Description:
*/
public class Facade {
System1 system1 = new System1();
System2 system2 = new System2();
System3 system3 = new System3();
boolean prove(){
return system1.module() && system2.module() && system3.module();
}
}
package org.example.facade;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/10/0:29
* @Description:
*/
public class System1 {
boolean module(){
return true;
}
}
package org.example.facade;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/10/0:31
* @Description:
*/
public class System2 {
boolean module(){
return true;
}
}
package org.example.facade;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/10/0:32
* @Description:
*/
public class System3 {
boolean module(){
return true;
}
}
package org.example.facade;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/10/0:29
* @Description:
*/
public class FacadePattern {
public static void main(String[] args) {
Facade facade = new Facade();
if(facade.prove()){
System.out.println("子模块正常");
}else{
System.out.println("子模块异常");
}
}
}
2.6. 享元模式
- 这种模式的核心思想就是共享,在缓存或者池技术中这种设计模式体现比较明显, 比如使用redis就体现了享元模式
- 代码案例如下
package org.example.flyweight;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/11/10:48
* @Description:
*/
abstract public class Bike {
protected Integer state = 0;
/**
* 使用
*/
abstract void ride(String username);
/**
* 归还
*/
abstract void back();
public Integer getState() {
return state;
}
}
package org.example.flyweight;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/11/10:49
* @Description:
*/
public class MoBike extends Bike{
private final String id;
public MoBike(String id){
this.id = id;
}
@Override
void ride(String username) {
state = 1;
System.out.println(username + "骑" + id + "出行");
}
@Override
void back() {
state = 0;
}
}
package org.example.flyweight;
import java.util.HashSet;
import java.util.Set;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/11/10:52
* @Description:
*/
public class BikeFactory {
private static final BikeFactory instance = new BikeFactory();
private final Set<Bike> pool = new HashSet<>();
public static BikeFactory getInstance() {
return instance;
}
private BikeFactory(){// 饿汉式创建单例
for(int i=0;i<2;i++){
pool.add(new MoBike(i + "号"));
}
}
public Bike getBike(){
for(Bike bike : pool){
if(bike.getState() == 0){
return bike;
}
}
return null;
}
}
package org.example.flyweight;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/11/10:55
* @Description:
*/
public class FlyWeight {
public static void main(String[] args) {
Bike bike = BikeFactory.getInstance().getBike();
bike.ride("张三");
Bike bike1 = BikeFactory.getInstance().getBike();
bike1.ride("王五");
bike1.back();
Bike bike2 = BikeFactory.getInstance().getBike();
bike2.ride("李四");
}
}
- 上面的代码实现了对于pool中元素的共享
2.7. 代理模式
- 为其他对象提供一个控制这个对象的访问,比如卖房子,房东会找一个中介帮助他卖房子,那么这种情况下通过房东或者通过中介都可以卖房子,这就是静态代理,在这种情况下中介承担的责任必须事先商定好,也就是卖房子,如果我想让他在卖房子之后把墙壁粉刷一下,就需要重新商议,也就是添加功能;那么如果限制了必须通过中介来租房子而不能直接通过房东,这就是强制代理
- 如果是动态代理,就可以在运行时生成,可以对原有方法进行增强或者添加新的方法,这是动态代理相对于静态代理的优势,spring的AOP就是基于动态代理实现的,因为我们没有在源代码里面进行修改,而是通过Point cut对程序代码进行的增强,具体实现是注解,这样就实现了运行时织入的一个效果,而非预先确定
- 其他代理模式见这篇文章,强制代理例子如下
package org.example.proxy;
public interface RentHouse {
void rentHouse();// 租房子
RentHouse getProxy();// 获取代理
}
package org.example.proxy;
public class IRentHouse implements RentHouse{
private IntermediaryProxy proxy = null;
@Override
public void rentHouse(){
if(hasProxy()){
System.out.println("调用真实角色的方法");
}else{
System.out.println("请使用代理调用");
}
}
@Override
public RentHouse getProxy() {
if(!hasProxy()){
this.proxy = new IntermediaryProxy(this);
}
return this;
}
private boolean hasProxy(){
return proxy != null;
}
}
package org.example.proxy;
public class IntermediaryProxy{
private RentHouse rentHouse = null;
public IntermediaryProxy(RentHouse iRentHouse){
this.rentHouse = iRentHouse;
}
public void rentHouse() {
rentHouse.rentHouse();
}
}
package org.example.proxy;
public class proxyTest {
public static void main(String[] args) {
RentHouse iRentHouse = new IRentHouse();
iRentHouse.getProxy().rentHouse();
}
}
- 强制代理就是在真实角色内部存储了一个代理对象,访问的时候必须要通过代理对象来实现,改为静态代理很简单了,就是去掉代理对象的检查即可
- 那么如果是动态代理要怎么做呢?Java实现动态代理主要有两种方式,jdk(目标类要实现接口)和cglib(没有接口),我们看一下jdk怎么做
package org.example.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class IntermediaryProxy implements InvocationHandler {
private final Object target;
public IntermediaryProxy(Object iRentHouse){
this.target = iRentHouse;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 功能增强
return method.invoke(target, args);
}
}
package org.example.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class proxyTest {
public static void main(String[] args) {
RentHouse iRentHouse = new IRentHouse();
// 定义一个handler
InvocationHandler handler = new IntermediaryProxy(iRentHouse);
// 获得类的class loader
ClassLoader cl = iRentHouse.getClass().getClassLoader();
// 动态产生一个消费者
RentHouse proxy = (RentHouse) Proxy.newProxyInstance(cl, new Class[]{RentHouse.class}, handler);
proxy.getProxy().rentHouse();
}
}
- 这主要用到的是Java的反射机制,创建一个代理类对象,并动态指定要代理的目标类,这要求目标类必须有接口,代理类要完成的功能写在invoke方法中
- 具体步骤如下
- 首先实现invocationHandler接口创建自己的调用处理器
- 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
- 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
- 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
- 代理模式优点是分离了代理对象和目标对象,在一定程度上降低了系统耦合度,扩展性好,也可以起到保护目标对象的作用;同时可以在目标对象基础上增强功能
- 缺点是增加了代理类,加大了系统复杂性,这也是所有设计模式共同的缺点;同时在客户端和目标对象之间增加一个代理对象也会导致系统性能降低
3. 行为型模式
3.1 职责链模式
- 典型的例子是员工请假,请两小时假和小组长说一下就行了,请一天假可能得上报领导,请一周假可能就需要报老板审批了,代码如下
package org.example.chain;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/12/14:59
* @Description: 参与者类
*/
abstract class Handler {
protected Handler nextHandler;
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;// 链上的下一个节点
}
public abstract void process(Integer days);
}
package org.example.chain;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/12/15:04
* @Description:
*/
public class Leader extends Handler{
@Override
public void process(Integer days) {
if(days <= 1) {
System.out.println("Leader处理");
}
else {
nextHandler.process(days);
}
}
}
package org.example.chain;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/12/15:04
* @Description:
*/
public class Boss extends Handler{
@Override
public void process(Integer days) {
System.out.println("Boss处理");
}
}
package org.example.chain;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/12/14:59
* @Description:
*/
public class ChainOfResp {
public static void main(String[] args) {
Handler handler1 = new Leader();
Handler handler2 = new Boss();
handler1.setNextHandler(handler2);
handler1.process(1);
handler1.process(2);
}
}
3.2 命令模式
- 最简单的情景是undo和redo,也就是撤销和重做,如果你想实现类似这种的功能,那么你就需要将实施操作之前将状态存储起来,然后撤销的时候从后往前一步一步的来,可以使用责任链来进行这个过程
- 下面模拟一个字符串拼接的undo操作
package org.example.command;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/16/12:31
* @Description:
*/
public abstract class Command {
public abstract void execute();
public abstract void undo();
}
package org.example.command;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/16/12:32
* @Description:
*/
public class InsertCommand extends Command{
Content c;
String str = "456789";
public InsertCommand(Content c){
this.c = c;
}
@Override
public void execute() {
c.msg = c.msg + str;
}
@Override
public void undo() {
c.msg = c.msg.substring(0, c.msg.length() - str.length());
}
public Content getC() {
return c;
}
}
package org.example.command;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/16/12:33
* @Description:
*/
public class Content {
String msg;
public Content(String msg){
this.msg = msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
}
package org.example.command;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/16/12:53
* @Description:
*/
public class Main {
public static void main(String[] args) {
InsertCommand insertCommand = new InsertCommand(new Content("123"));
insertCommand.execute();
System.out.println(insertCommand.getC().getMsg());
insertCommand.undo();
System.out.println(insertCommand.getC().getMsg());
}
}
3.3 解释器模式
- 这种设计模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子
- 接下来我们通过一个简单的加减法计算器来观察这种设计模式
package org.example.intercepter;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/23/21:44
* @Description:
*/
public class Context {
String sequence;// 当前字符串
long value;// 当前值
public Context(String sequence){
this.sequence = sequence;
}
}
package org.example.intercepter;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/23/21:40
* @Description:
*/
public abstract class Expression {
abstract Long interpret(Context context) throws Exception;
Integer findNextNumber(Context context){// 找下一个数字
int ptr = 0;
int ans = 0;
while(ptr < context.sequence.length() && Character.isDigit(context.sequence.charAt(ptr))){
ans *= 10;
ans += context.sequence.charAt(ptr) - '0';
ptr += 1;
}
context.sequence = context.sequence.substring(ptr);
return ans;
}
Character findNextOpr(Context context) {// 找下一个操作符
int ptr = 0;
while(ptr < context.sequence.length() && Character.isDigit(context.sequence.charAt(ptr))){
ptr += 1;
}
if(ptr < context.sequence.length()) {
Character c = context.sequence.charAt(ptr);
context.sequence = context.sequence.substring(ptr + 1);
return c;
}else {
return null;
}
}
}
package org.example.intercepter;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/23/22:07
* @Description:
*/
public class AddExpression extends Expression{// 加号
private final static MinusExpression minusExpression = new MinusExpression();
private final static EqualsExpression equalsExpression = new EqualsExpression();
@Override
Long interpret(Context context) throws Exception {
int val = findNextNumber(context);
Character c = findNextOpr(context);
assert (c != null);
context.value = context.value + val;
switch (c){
case '+': {
return this.interpret(context);
}
case '-': {
return minusExpression.interpret(context);
}
case '=': {
return equalsExpression.interpret(context);
}
default: {
throw new Exception("error");
}
}
}
}
package org.example.intercepter;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/23/22:08
* @Description:
*/
public class MinusExpression extends Expression{// 减号
private final static AddExpression addExpression = new AddExpression();
private final static EqualsExpression equalsExpression = new EqualsExpression();
@Override
Long interpret(Context context) throws Exception {
int val = findNextNumber(context);
Character c = findNextOpr(context);
assert (c != null);
context.value = context.value - val;
switch (c){
case '+': {
return addExpression.interpret(context);
}
case '-': {
return this.interpret(context);
}
case '=': {
return equalsExpression.interpret(context);
}
default: {
throw new Exception("error");
}
}
}
}
package org.example.intercepter;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/23/22:09
* @Description:
*/
public class EqualsExpression extends Expression{// 等于号
@Override
Long interpret(Context context) {
return context.value;
}
}
package org.example.intercepter;
/**
* Created with IntelliJ IDEA.
*
* @Author: Clarence
* @Date: 2023/02/23/21:51
* @Description:
*/
public class InterpreterDemo {// 测试
public static void main(String[] args) throws Exception {
Context context = new Context("1+2-3+4-5+6=");
System.out.println(new AddExpression().interpret(context));
}
}
- 这种设计模式的优点使易于改变和扩展文法且易于实现,但是难以维护复杂的文法,因为每一种文法都需要一个类
3.4 迭代器模式
- 我们通过简单阅读
J
D
K
JDK
JDK源码中的
ArrayList
的迭代器来研究这种设计模式
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;// 类似金丝雀值, 每次对List做一次操作modCount会+1
// prevent creating a synthetic constructor
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int size = ArrayList.this.size;
int i = cursor;
if (i < size) {
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
for (; i < size && modCount == expectedModCount; i++)
action.accept(elementAt(es, i));
// update once at end to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
- 简单来说就是封装遍历和删除方法,然后进行操作,这样的好处是隐藏数据结构细节,同时可以解决一些线程安全问题,比如使用上面的
Fail-Fast
机制,如果金丝雀值被改变,迭代器会检测到这一异常,所以遍历元素的时候使用迭代器方法可能会更好
3.5 中介者模式
- 用一个中介对象来封装一系列的对象交互,使得对象之间不需要相互引用,降低耦合度,这种设计模式是迪米特法则的典型应用,将一对多改变成了一对一,但是这样做的问题是中介者可能会变得很庞大,从而变得复杂难以维护
- 举个例子,比如说我现在要维护一个通信,实现一个功能,某个用户发出消息其他用户都会收到该消息,使用中介者模式的思路是先弄一个中介者,然后把所有用户都注册到这个中介者上面,然后通过调用某个用户的
send()
方法,让中介者转发到其他用户,代码实现如下
首先定义抽象接口
package org.example.mediator;
public abstract class Mediator {
abstract void communication(Colleague colleague);
abstract void register(Colleague colleague);
}
package org.example.mediator;
public abstract class Colleague {
protected Mediator mediator;
public abstract void receive();
public abstract void send();
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
}
定义两个实体
package org.example.mediator;
public class Colleague1 extends Colleague{
@Override
public void receive() {
System.out.println("Colleague1 receives a message.");
}
@Override
public void send() {
System.out.println("Colleague1 sends a message.");
mediator.communication(this);
}
}
package org.example.mediator;
public class Colleague2 extends Colleague{
@Override
public void receive() {
System.out.println("Colleague2 receives a message.");
}
@Override
public void send() {
System.out.println("Colleague2 sends a message.");
this.mediator.communication(this);
}
}
定义一个中介者
package org.example.mediator;
import java.util.ArrayList;
public class ConcreteMediator extends Mediator{
private ArrayList<Colleague> colleagues;
ConcreteMediator() {
colleagues = new ArrayList<>();
}
@Override
void communication(Colleague colleague) {
colleagues.forEach(coll -> {
if(!coll.equals(colleague)) {
coll.receive();
}
});
}
@Override
void register(Colleague colleague) {
colleagues.add(colleague);
colleague.mediator = this;
}
}
然后调用,实现的功能就是通过中介者实现了消息的相互转发
package org.example.mediator;
public class MediatorDemo {
public static void main(String[] args) {
Colleague1 colleague1 = new Colleague1();
Colleague2 colleague2 = new Colleague2();
ConcreteMediator concreteMediator = new ConcreteMediator();
concreteMediator.register(colleague1);
concreteMediator.register(colleague2);
colleague1.send();
colleague2.send();
}
}
3.6 备忘录模式
- 为了允许用户取消不确定的操作或者从错误中恢复过来,需要实现检查点和取消机制,而要实现这些机制,你必须事先将状态信息保存在某处,使得其状态不能被其他对象访问,也就不可能在该对象之外保存其状态。而暴露其内部状态又将违反封装的原则,可能有损应用的可靠性和可扩展性
- 换句话说就是恢复到之前的状态,那么我们可能想起之前学过一个命令模式,也有一个回滚的效果,那么这两种模式的区别是是什么呢?主要是这个回滚的效果只能一步一步的往前移动,而备忘录我们可以回到任何一个状态,就像我们使用git一样,我们想回到哪个版本就可以回到哪个版本
- 一个备忘录
memento
是一个对象,它存储另一个对象在某个瞬间的内部状态,而后者成为备忘录的原发器originator
,当需要设置原发器的检查点时,取消操作机制会向原发器请求一个备忘录。原发器用描述当前状态的信息初始化该备忘录。只有原发器可以向备忘录中存取信息,备忘录对其他的对象不可见
下面我们来使用备忘录模式实现一个类似版本维护的操作
- 首先写一个配置文件,里面包括版本号、文件内容以及修改时间、操作人等等
package org.example.Memento;
import java.util.Date;
public class ConfigFile {
// 版本号
private String versionNo;
// 内容
private String content;
// 时间
private Date dateTime;
// 操作人
private String operator;
public ConfigFile(String versionNo, String content, Date dateTime, String operator) {
this.versionNo = versionNo;
this.content = content;
this.dateTime = dateTime;
this.operator = operator;
}
public void setContent(String content) {
this.content = content;
}
public void setDateTime(Date dateTime) {
this.dateTime = dateTime;
}
public void setOperator(String operator) {
this.operator = operator;
}
public void setVersionNo(String versionNo) {
this.versionNo = versionNo;
}
public String getContent() {
return content;
}
public Date getDateTime() {
return dateTime;
}
public String getOperator() {
return operator;
}
public String getVersionNo() {
return versionNo;
}
}
- 然后写一个配置备忘录,它的作用是存储原发器对象的内部状态,原发器根据需要决定备忘录存储原发器的哪些内部状态
package org.example.Memento;
public class ConfigMemento {
private ConfigFile configFile;
public ConfigMemento(ConfigFile configFile) {
this.configFile = configFile;
}
public ConfigFile getConfigFile() {
return configFile;
}
public void setConfigFile(ConfigFile configFile) {
this.configFile = configFile;
}
}
- 接下来是一个原发器
originator
,感觉好像和备忘录没什么区别,个人理解备忘录是一个创建出来的东西,原发器是真正存在的,通过原发器操作备忘录
package org.example.Memento;
public class ConfigOrinator {
private ConfigFile configFile;
public void setConfigFile(ConfigFile configFile) {
this.configFile = configFile;
}
public ConfigFile getConfigFile() {
return configFile;
}
public ConfigMemento saveMemento() {
return new ConfigMemento(this.configFile);
}
public void getMemento(ConfigMemento configMemento) {
this.configFile = configMemento.getConfigFile();
}
}
- 然后写一个管理者类
package org.example.Memento;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Caretaker {
private int cursorIndex = -1;// 当前所处的版本游标
private List<ConfigMemento> mementoList = new ArrayList<>();
private Map<String, ConfigMemento> mementoMap = new ConcurrentHashMap<>();
public void append(ConfigMemento configMemento) {
mementoList.add(configMemento);
mementoMap.put(configMemento.getConfigFile().getVersionNo(), configMemento);
cursorIndex += 1;
}
public ConfigMemento undo() {
if(--cursorIndex <= 0) {
return mementoList.get(0);
}
return mementoList.get(cursorIndex);
}
public ConfigMemento redo() {
if(++cursorIndex >= mementoList.size()) {
return mementoList.get(mementoList.size() - 1);
}
return mementoList.get(cursorIndex);
}
public ConfigMemento get(String versionNo) {
return mementoMap.get(versionNo);
}
public int getCursorIndex() {
return cursorIndex;
}
public void setCursorIndex(int cursorIndex) {
this.cursorIndex = cursorIndex;
}
}
- 然后测试一下,能够实现根据版本变更和回滚等操作
package org.example.Memento;
import java.util.Date;
public class MementoDemo {
public static void main(String[] args) {
Caretaker taker = new Caretaker();
ConfigOrinator configOrinator = new ConfigOrinator();
configOrinator.setConfigFile(new ConfigFile("0", "配置0", new Date(), "clarence"));
taker.append(configOrinator.saveMemento());
configOrinator.setConfigFile(new ConfigFile("1", "配置1", new Date(), "clarence"));
taker.append(configOrinator.saveMemento());
configOrinator.setConfigFile(new ConfigFile("2", "配置2", new Date(), "clarence"));
taker.append(configOrinator.saveMemento());
configOrinator.setConfigFile(new ConfigFile("3", "配置3", new Date(), "clarence"));
taker.append(configOrinator.saveMemento());
System.out.println(configOrinator.getConfigFile().getVersionNo());
configOrinator.getMemento(taker.undo());
System.out.println(configOrinator.getConfigFile().getVersionNo());
configOrinator.getMemento(taker.undo());
System.out.println(configOrinator.getConfigFile().getVersionNo());
configOrinator.getMemento(taker.redo());
System.out.println(configOrinator.getConfigFile().getVersionNo());
configOrinator.getMemento(taker.get("0"));
System.out.println(configOrinator.getConfigFile().getVersionNo());
}
}
3.7 观察者模式
- 将一个系统分割成一系列相互协作的类有一个常见的副作用,就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了其可复用性。比如说一个折线图和一个表格对象都显示一组数据,当我们改变表格对象中的数据的时候,折线图也会随着更改,而它们彼此并不知道对方的存在
- 在上述例子中的折线图和表格对象就是观察者
observer
,数据就是目标subject
,一个目标可以有任意数目的依赖于它的观察者,一旦目标的状态发生改变,所有的观察着都得到通知 - 这种交互也称为发布-订阅
publish-subscribe
,我们在消息队列中也接触过这个概念
接下来写个范例,目的是监听消息和简单模拟消息队列收消息
- 一个事件实体类
package org.example.observer;
import java.util.Date;
public class Event {
private String uid;
private String msg;
private Date time;
public Event(String uid, String msg, Date time) {
this.uid = uid;
this.msg = msg;
this.time = time;
}
public String getMsg() {
return msg;
}
public Date getTime() {
return time;
}
public String getUid() {
return uid;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void setTime(Date time) {
this.time = time;
}
public void setUid(String uid) {
this.uid = uid;
}
}
- 然后我们写一个事件监听接口,这就是观察者
package org.example.observer;
public interface EventListener {
void doEvent(Event event);
}
- 然后用两种方式实现这个接口,分别是消息监听和MQ监听
package org.example.observer;
public class MessageEventListener implements EventListener{
@Override
public void doEvent(Event event) {
System.out.println("MessageListener has received" + event.getUid() + "'s message" + event.getMsg());
}
}
package org.example.observer;
public class MqEventListener implements EventListener{
@Override
public void doEvent(Event event) {
System.out.println("MQ records user" + event.getUid() + "'s messages " + event.getMsg());
}
}
- 目标
subject
,提供注册和删除观察者对象的接口
package org.example.observer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EventManager {
Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();
public enum EventType{
MQ, MESSAGE
}
@SafeVarargs
public EventManager(Enum<EventType>... operations) {
for(Enum<EventType> operation : operations) {
this.listeners.put(operation, new ArrayList<>());
}
}
/**
* 订阅
* @param eventType 事件类型
* @param listener 监听
*/
public void subscribe(Enum<EventType> eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.add(listener);
}
/**
* 取消订阅
* @param eventType 事件类型
* @param listener 监听
*/
public void unsubscribe(Enum<EventType> eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.remove(listener);
}
/**
* 通知
* @param eventType 事件类型
* @param event 事件
*/
public void notify(Enum<EventType> eventType, Event event) {
List<EventListener> users = listeners.get(eventType);
for(EventListener listener : users) {
listener.doEvent(event);
}
}
}
- 事件Service抽象类,用于操作事件
package org.example.observer;
public abstract class EventService {
private EventManager eventManager;
public EventService() {
eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.MESSAGE);
eventManager.subscribe(EventManager.EventType.MQ, new MqEventListener());
eventManager.subscribe(EventManager.EventType.MESSAGE, new MessageEventListener());
}
public Event draw(String uid) {
Event event = doDraw(uid);
eventManager.notify(EventManager.EventType.MQ, event);
eventManager.notify(EventManager.EventType.MESSAGE, event);
return event;
}
protected abstract Event doDraw(String uid);
}
- Service实现类
package org.example.observer;
import java.util.Date;
public class EventServiceImpl extends EventService{
@Override
public Event doDraw(String uid) {
String msg = "user uid's messages";
System.out.println("System sends messages to user " + uid);
return new Event(uid, msg, new Date());
}
}
- 测试
package org.example.observer;
import com.alibaba.fastjson2.JSON;
public class ObserverDemo {
public static void main(String[] args) {
EventService eventService = new EventServiceImpl();
Event result = eventService.draw("001");
System.out.println(JSON.toJSON(result));
}
}
3.8 状态模式
- 有的时候我们希望描述一些不同状态下的类的行为,但是如果直接写的话可能我们会使用一些
if-else
或者switch-case
等语句,这样如果之后需要进行状态的修改,这个时候就违背了开闭原则,例如下面这个
package org.example.state;
public class Person {
String name;
private enum State {HAPPY, SAD}
public void smile() {
// switch state
}
public void say() {
// switch state
}
public void cry() {
// switch state
}
}
- 如果我们根据不同的状态控制不同的实现形式,我们就需要去判断每种状态的具体情况然后再分别实现,但是如果我们要扩展一个状态,这个时候就只能再加一个
case
,这是不好的 - 所以我们使用状态模式,先写一个抽象类,然后根据不同状态分别实现抽象方法
package org.example.state;
public abstract class PersonState {
abstract void smile();
abstract void cry();
abstract void say();
}
package org.example.state;
public class HappyState extends PersonState{
@Override
void smile() {
// TODO Auto-generated method stub
}
@Override
void cry() {
// TODO Auto-generated method stub
}
@Override
void say() {
// TODO Auto-generated method stub
}
}
package org.example.state;
public class SadState extends PersonState{
@Override
void smile() {
// TODO Auto-generated method stub
}
@Override
void cry() {
// TODO Auto-generated method stub
}
@Override
void say() {
// TODO Auto-generated method stub
}
}
package org.example.state;
public class Person {
String name;
private PersonState state;
public Person(String name, PersonState state) {
this.name = name;
this.state = state;
}
public void smile() {
state.smile();
}
public void say() {
state.say();
}
public void cry() {
state.cry();
}
}
- 这样如果我们想加一个状态,只需要对
PersonState
抽象类加一个实现类即可,这就是状态模式 - 在阿里巴巴Java开发手册里面,说明如果
if-else
嵌套超过三层,需要使用卫语句、策略模式、状态模式等来实现,卫语句就是在if
里面加一个return
,这样就不需要写else
了,策略模式是接下来要说的
3.9 策略模式
- 这种设计模式简而言之就是用一个类来封装不同类的策略,比如说我现在想给狗按照体重排序,那你可能写一个类就搞定了,但是我想再加一个功能按照身高排序,那你可能要再写一个给狗按照身高排序的方法,那如果我现在想给猫排序呢?如果再写一个类太愚蠢了,能够想到解决办法是泛型,实际上我们只需要封装不同的比较器就可以了
- 比如说现在写一个
Dog
类
package org.example.strategy;
public class Dog {
private int weight;
private int height;
public Dog(int weight, int height) {
this.weight = weight;
this.height = height;
}
@Override
public String toString() {
return "Dog{" + "weight=" + weight + ",height=" + height + "}";
}
public void setHeight(int height) {
this.height = height;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getHeight() {
return height;
}
public int getWeight() {
return weight;
}
}
- 现在我希望按照
Height
排序,怎么做呢?策略模式的方法是写一个排序构造器接口
package org.example.strategy;
public interface Comparator<T> {
int compare(T o1, T o2);
}
package org.example.strategy;
public class DogHeightComparator implements Comparator<Dog>{
@Override
public int compare(Dog o1, Dog o2) {
if(o1.getHeight() < o2.getHeight()) {
return -1;
}
if(o1.getHeight() > o2.getHeight()) {
return 1;
}
return 0;
}
}
package org.example.strategy;
public class Sorter<T> {
private Comparator<T> comparator;
private void quicksort(T[] t0, int l, int r) {
int i = l;
int j = r;
int mid = (r - l >>> 1) + l;
do {
while(comparator.compare(t0[i], t0[mid]) == -1) {
i += 1;
}
while(comparator.compare(t0[j], t0[mid]) == 1) {
j -= 1;
}
if(i <= j) {
swap(t0, i, j);
i += 1;
j -= 1;
}
}while(i <= j);
if(i < r) quicksort(t0, i, r);
if(j > l) quicksort(t0, l, j);
}
private void swap(T[] t0, int i, int j) {
T t1 = t0[i];
t0[i] = t0[j];
t0[j] = t1;
}
public void sort(T[] t0, Comparator<T> comparator) {
int n = t0.length;
this.comparator = comparator;
quicksort(t0, 0, n - 1);
}
}
package org.example.strategy;
public class StrategyDemo {
public static void main(String[] args) {
Dog[] dogs = new Dog[3];
for(int i=0;i<3;i++) {
dogs[i] = new Dog(i, 3 - i);
}
Sorter<Dog> sorter = new Sorter<Dog>();
sorter.sort(dogs, new DogHeightComparator());// 传入一个构造器
for(int i=0;i<3;i++) {
System.out.println(dogs[i]);
}
}
}
- 能够发现这种方法通过泛型和排序策略满足了开闭原则,因为假设我现在要根据
weight
排序,我只需要传入一个新的构造器即可;如果我希望对猫排序,我只需要改变泛型即可,这大大减小了代码的耦合度
3.10 模板方法模式
- 思想很简单,定义一个模板,里面的若干函数事先规定好,但是放在子类中实现,相当于在父类里面定义了一个操作的算法框架
package org.example.templateMethod;
public abstract class F {
public void m() {
op1();
op2();
}
abstract void op1();
abstract void op2();
}
package org.example.templateMethod;
public class F1 extends F{
@Override
void op1() {
System.out.println("F1 - op1");
}
@Override
void op2() {
System.out.println("F1 - op2");
}
}
package org.example.templateMethod;
public class F2 extends F{
@Override
void op1() {
System.out.println("F2 - op1");
}
@Override
void op2() {
System.out.println("F2 - op2");
}
}
3.11 访问者模式
- 如果你要购买一台电脑,你需要购买显卡、主板、内存等等,会有一个打折的策略;对于不同的用户会有不同的打折策略,那么如何实现呢?使用访问者模式实现的思路是让每一个访问者都去实现他各自的打折策略,这里的访问者就相当于每个用户
首先写一个抽象类
package org.example.visitor;
public abstract class ComputerPart {
abstract void accepted(Visitor v);
abstract double getPrice();
}
然后分别将三个部件功能实现
package org.example.visitor;
public class Board extends ComputerPart{
@Override
void accepted(Visitor v) {
v.visitBoard(this);
}
@Override
double getPrice() {
return 800;
}
}
package org.example.visitor;
public class Memory extends ComputerPart{
@Override
void accepted(Visitor v) {
v.visitMemory(this);
}
@Override
double getPrice() {
return 400;
}
}
package org.example.visitor;
public class CPU extends ComputerPart{
@Override
void accepted(Visitor v) {
v.visitCpu(this);
}
@Override
double getPrice() {
return 1000;
}
}
然后写一个访问者接口
package org.example.visitor;
public interface Visitor {
double totalPrice = 0.0;
void visitCpu(CPU cpu);
void visitBoard(Board board);
void visitMemory(Memory memory);
}
package org.example.visitor;
public class PersonVisitor implements Visitor{
private double totalPrice = 0.0;
@Override
public void visitCpu(CPU cpu) {
totalPrice += cpu.getPrice() * 0.9;
}
@Override
public void visitBoard(Board board) {
totalPrice += board.getPrice() * 0.9;
}
@Override
public void visitMemory(Memory memory) {
totalPrice += memory.getPrice() * 0.9;
}
public double getTotalPrice() {
return totalPrice;
}
}
package org.example.visitor;
public class CompanyVisitor implements Visitor {
private double totalPrice = 0.0;
@Override
public void visitCpu(CPU cpu) {
totalPrice += cpu.getPrice() * 0.8;
}
@Override
public void visitBoard(Board board) {
totalPrice += board.getPrice() * 0.8;
}
@Override
public void visitMemory(Memory memory) {
totalPrice += memory.getPrice() * 0.8;
}
public double getTotalPrice() {
return totalPrice;
}
}
- 这样就实现了访问者模式,好处还是将众多的判断换成了具体的类来实现