Loading... # 一、创建型模式 > 这类模式提供创建对象的机制, 能够提升已有代码的灵活性和可复用性。 ## 1. 工厂模式 Factory Pattern > 用来创建对象的设计模式,可以屏蔽对象的创建过程,只需要传入参数即可获取对象。 ### JDK Calendar ```java import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; Calendar calendar1 = Calendar.getInstance(); Calendar calendar2 = Calendar.getInstance(TimeZone.getTimeZone("GMT+8")); Calendar calendar3 = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"), Locale.CHINA); ``` 可以根据不同的参数来获取不同的 Calendar 子类,如 `GregorianCalendar`,`JapaneseImperialCalendar`,`BuddhistCalendar` ## 2. 构造/建造者模式 Builder Pattern > 用来创建对象的设计模式,可以通过链式调用来设置属性。 ### JDK Calendar ```java import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; Calendar calendar = new Calendar.Builder() .setTimeZone(TimeZone.getTimeZone("GMT+8")) .setLocale(Locale.CHINA) .setDate(2020, 0, 1) .build(); ``` 通过内部类的 Builder 来构建对象,可以通过链式调用来设置属性。 ## 3. 单例模式 Singleton Pattern > 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 常见做法是将构造函数私有化,然后提供一个静态方法来获取实例,在这个方法中判断实例是否已经创建,如果没有则创建,如果有则直接返回。不过还有通过枚举,静态内部类等方式来实现,篇幅问题这里不再展开,可以参考 [从JDK中学习设计模式——单例模式](https://juejin.cn/post/7028496241814143007) ### JDK Runtime ```java Runtime runtime = Runtime.getRuntime(); ``` 看下 `Runtime` 的源码: ```java public class Runtime { private static final Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } } ``` ## 4. 原型模式 Prototype Pattern > 使你能够复制已有对象,而又无需使代码依赖它们所属的类。 你可能对上面的 "无需使代码依赖它们所属的类" 感到困惑,其实他是指,复制对象时,无需手动创建一个属于相同类的对象,再遍历原始对象的所有成员变量依次设置到新对象中。 ### JDK Cloneable JDK 中的 `java.lang.Cloneable` 接口就是原型模式的一种实现,通过实现这个接口,然后重写 `clone` 方法来实现原型模式。 ```java public class User implements Cloneable { private final String name; private final int age; public User(String name, int age) { this.name = name; this.age = age; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } } ``` 测试方法 ```java public class PrototypePatternDemo { public static void main(String[] args) throws CloneNotSupportedException { User user = new User("张三", 18); User userClone = (User) user.clone(); System.out.println(user); System.out.println(userClone); System.out.println(user == userClone); } } ``` 结果输出 ``` User{name='张三', age=18} User{name='张三', age=18} false ``` 从结果可以看出,`user` 和 `userClone` 是两个不同的对象,但是他们的属性是一样的,这就是原型模式的作用。当然你也可以在 `clone` 方法中手动设置属性,这样就可以实现深拷贝。 --- # 二、结构型模式 ## 1. 装饰器模式 Decorator Pattern > 在**不改变原有类**的情况下扩展其功能,使用组合代替继承的方式来实现。 ### JDK Collections.unmodifiableList ```java import java.util.ArrayList; import java.util.Collections; import java.util.List; List<String> list = new ArrayList<>(); List<String> unmodifiableList = Collections.unmodifiableList(list); ``` 对原 List 进行装饰,使其不可修改。原理是底层会创建一个 `UnmodifiableList` 对象,将原 List 传入,调用所以获取型方法时,会调用原 List 的方法,但是修改方法会抛出异常避免修改。 ### JDK InputStream ```java import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; InputStream inputStream = new FileInputStream("file.txt"); InputStream bufferedInputStream = new BufferedInputStream(inputStream); ``` 这里对原 `FileInputStream` 进行装饰,使其具有缓冲功能。同理也可以自己创建具有加密,限流,监控等功能的装饰器。 ## 2. 适配器模式 Adapter Pattern > 当一个类不能直接被使用时,可以通过适配器来转换为另一个类。 ### JDK FutureTask `FutureTask` 构造函数一:传入 `Callable` ```java public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable } ``` `FutureTask` 构造函数二:传入 `Runnable` ```java public FutureTask(Runnable runnable, V result) { // 因为 `FutureTask` 有个 `get` 方法,用来获取结果,但 Runnable 的 run 方法没有返回值,所以需要手动指定一个。 this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable } ``` 主要看这里的 `Executors.callable` 返回了一个 `Callable` 类的实例: ```java public static <T> Callable<T> callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); // 这里返回的是一个实现了 Callable 的适配器 return new RunnableAdapter<T>(task, result); } ``` 再来看看 `RunnableAdapter`: ```java // 实现了 Callable static final class RunnableAdapter<T> implements Callable<T> { final Runnable task; final T result; // 传入 Runnable 接口的实现类和返回值 RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { // 在 call 方法中调用 Runnable 的 run 方法,变相实现了 Runnable 转 Callable 的过程. task.run(); return result; } } ``` ## 3. 外观模式/门面模式 Facade Pattern > 创建一个统一的接口,用来屏蔽内部复杂的底层或结构。 > 这个模式有点笼统,很可能你在开发中已经用到了,但是没有意识到,比如我们使用 Spring MVC 开发的 restful 接口,对外只提供了一个统一的接口,但是内部可能调用了很多服务,数据库,缓存等。 ### JDK Executors `Executors` 提供了一系列工厂方法来创建线程池,如 `newFixedThreadPool`,`newCachedThreadPool`,`newSingleThreadExecutor` 等。 ```java public class Executors { public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } } ``` `Executors` 内部其实根据不同的参数创建了不同的线程池,对外提供了统一的接口。其实这个类也算一个工厂模式(静态工厂方法模式)。 ## 4. 代理模式 Proxy Pattern > 为对象创建一个代理,你可以操作代理而不是源对象,代理可以对源对象进行访问,并且可以提供额外的功能,如在访问源对象前后进行一些操作。 ### JDK Proxy `java.lang.reflect.Proxy` 提供了创建动态代理的方法,可以根据传入的接口和处理器来创建代理对象。 JDK 动态代理是基于接口的,所以你需要为被代理对象创建一个接口: ```java interface Image { void display(); } ``` 然后有一个实现类,也就是我们的被代理者: ```java class RealImage implements Image { private final String filename; public RealImage(String filename) { this.filename = filename; } public void display() { System.out.println("Displaying " + filename); } } ``` 再创建一个代理类,实现 `InvocationHandler` 接口: ```java class ImageProxy implements InvocationHandler { private final Image realImage; public ImageProxy(Image realImage) { this.realImage = realImage; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("display")) { System.out.println("Proxying before displaying"); realImage.display(); System.out.println("Proxying after displaying"); } return null; } } ``` 然后演示一下: ```java public class ProxyPatternDemo { public static void main(String[] args) { Image realImage = new RealImage("test.jpg"); Image proxyImage = (Image) Proxy.newProxyInstance( realImage.getClass().getClassLoader(), realImage.getClass().getInterfaces(), new ImageProxy(realImage) ); proxyImage.display(); } } ``` 上面我们先创建了一个 `RealImage` 对象,然后通过 `Proxy.newProxyInstance` 方法创建了一个代理对象,这个代理对象实现了 `Image` 接口,然后调用代理对象的 `display` 方法,实际上是调用了 `ImageProxy` 的 `invoke` 方法,然后在这个方法中调用了 `RealImage` 的 `display` 方法。 ## 5. 桥接模式 Bridge Pattern > 将抽象部分与它的实现部分分离,使它们都可以独立的变化。 这个模式很抽象,不易理解,其实本质上是为了解决多维度的变化,比如一个类有多个维度的变化,如颜色,形状等,如果使用继承的方式来实现,会导致类的爆炸式增长,所以可以使用桥接模式来解决。 ### JDK JDBC 这个有人说是桥接模式,但我自己感觉又不太像,有点生搬硬套的感觉,这里也有个博主写了一篇文章大家可以参考下 [JDBC和桥接模式](https://www.cnblogs.com/ruaa/p/13038076.html) ### Spring JdbcTemplate Spring 的 `JdbcTemplate` 就是一个很好的例子,他将 `DataSource` 这个接口和 `JdbcOperations` 这个接口分离开来,`DataSource` 是用来获取数据库连接的,`JdbcOperations` 是用来执行 SQL 语句的。 ```java import javax.sql.DataSource; public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { public JdbcTemplate(DataSource dataSource) { setDataSource(dataSource); afterPropertiesSet(); } } ``` 如果不这样做,那么每个数据库都需要实现一套 `DataSource` 和 `JdbcOperations`,如有 MySQL,Oracle,PostgreSQL,就需要这样: ``` - DataSource - MySQLDataSource - OracleDataSource - PostgreSQLDataSource ``` 首先每个数据库都需要实现 `DataSource` 接口,然后每个数据源都需要实现 `JdbcOperations` 接口: ``` - DataSource - MySQLDataSource implements JdbcOperations - OracleDataSource implements JdbcOperations - PostgreSQLDataSource implements JdbcOperations ``` 这样会有很多重复的代码,而且如果有新的数据库,还需要新增一套 `DataSource` 和 `JdbcOperations`。 但是现在有了 `JdbcTemplate`,只需要实现 `DataSource` 接口,传递给 `JDBCTemplate`,然后在 `JdbcTemplate` 中实现 `JdbcOperations` 接口,这样增加新的数据库时,只需要实现 `DataSource` 接口即可。 这就对应了 `将抽象部分与它的实现部分分离,使它们都可以独立的变化。`,现在 DataSource 和 JdbcOperations 都可以独立的变化,功能也是分离的,由 `JdbcTemplate` 来桥接。 ## 6. 组合模式 Composite Pattern > 将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。 ### JDK FileSystem `java.nio.file` 包下的文件系统就是一个很好的例子,它将文件和目录都抽象成了 `Path` 对象,然后可以通过 `Files` 类来操作这些对象。 ```java import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class FileSystemDemo { public static void main(String[] args) { Path path = Paths.get("file.txt"); Files.exists(path); Files.isDirectory(path); Files.isRegularFile(path); } } ``` ### JDK File `java.io.File` 也是类似的,它将文件和目录都抽象成了 `File` 对象,然后可以通过 `File` 类来操作这些对象。 ```java import java.io.File; public class FileDemo { public static void main(String[] args) { File file = new File("file.txt"); file.exists(); file.isDirectory(); file.isFile(); } } ``` > 其实就是对于有共性的两种不同类型的事物,抽象成一个接口,而不是创建两个类,在成员变量中可以存放自己所属的类型,这样就可以实现对于两种不同类型的事物的统一处理。 ## 7. 享元模式 Flyweight Pattern > 主要为了减少对象的创建,以用来减少内存占用和提高性能,在不影响程序的情况下可以共享对象来减少对象的创建。 ### JDK Integer JDK 的 Integer 类缓存了 -128 到 127 之间的整数,因为这些整数在程序中使用的频率比较高,所以缓存起来可以减少对象的创建。 ```java public final class Integer extends Number implements Comparable<Integer> { public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } } ``` IntegerCache 类是 Integer 的一个静态内部类,会在 Integer 类加载的时候初始化: ```java public final class Integer extends Number implements Comparable<Integer> { private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) - 1); } catch (NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for (int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() { } } } ``` > 还有池化技术:`线程池`,`连接池` 等,这些都是享元模式的应用,它们都是为了减少对象的创建,提高性能。 --- # 三、行为模式 ## 1. 模板模式 Template Pattern / 模板方法模式 Template Method Pattern > 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。其实就是父类定义一些抽象方法,子类实现这些方法。 ### JDK Comparable ```java public interface Comparable<T> { int compareTo(T o); } ``` `Comparable` 接口中只有一个抽象方法 `compareTo`,这个方法的具体实现是由子类来实现的。如 Integer,String 等类都实现了这个接口,以 Integer 为例: ```java public final class Integer extends Number implements Comparable<Integer> { public int compareTo(Integer anotherInteger) { return compare(this.value, anotherInteger.value); } } ``` ## 2. 观察者模式 Observer Pattern / 发布订阅模式 Publish-Subscribe Pattern > 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。有时也叫做发布-订阅模式。 ### JDK Observer & Observable JDK 中已经提供了 `Observer` 和 `Observable` 接口,分别表示观察者和被观察者。 首先被观察者需要实现 `Observable` 接口: ```java import java.util.Observable; class User extends Observable { private String name; public void setName(String name) { this.name = name; super.setChanged(); // 调用 Observable 提供的方法,标记状态已经改变 super.notifyObservers(name); // 调用 Observable 提供的方法,通知所有观察者 } } ``` 然后观察者需要实现 `Observer` 接口: ```java import java.util.Observer; class UserObserver implements Observer { @Override public void update(Observable observable, Object arg) { System.out.println("观察到用户的名字已经改变为 {}, {}", observable, arg); } } ``` 最后在需要的地方进行注册和通知: ```java public static void main(String[] args) { User user = new User(); UserObserver userObserver = new UserObserver(); user.addObserver(userObserver); user.setName("张三"); } ``` ## 3. 策略模式 Strategy Pattern > 定义一系列算法/策略的接口,每个策略都实现这个接口,这样可以很方便的替换算法/策略。 ### JDK Comparator jdk 中的 `java.util.Comparator` 接口就是一种排序策略接口,你可以根据不同的需求实现不同的排序策略,然后调用 `Collections.sort` 方法时可以传入 `Comparator` 接口的实现类。 先来看看 `Comparator` 接口的源码: ```java @FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); } ``` 这个接口只有一个抽象方法 `compare`,传入两个参数,返回一个整数,表示两个对象的大小关系。 ## 4. 责任链模式 Chain of Responsibility Pattern > 定义一个处理链,每个处理器都可以处理请求,或交给下一个处理器处理。 ### JDK Filter `javax.servlet.Filter` 就是一种责任链模式,它的 `doFilter` 方法中可以调用下一个 `Filter` 的 `doFilter` 方法,也可以决定是否调用下一个 `Filter` 的 `doFilter` 方法。 ```java public interface Filter { void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; } ``` `FilterChain` 接口: ```java public interface FilterChain { void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException; } ``` 这样可以定义多个 `Filter`,使用 FilterChain 来组织成一个责任链并控制调用的顺序,每个 `Filter` 都可以决定是否调用下一个 `Filter`。 > 责任链模式在开发中很常用,且有很多变种或增强的版本,如跳链,更加灵活的控制是否调用下一个处理器等。 > 但是在实际开发中,需要注意责任链模式的性能问题,因为责任链模式的处理是递归的,如果责任链过长,可能会导致栈溢出。 > 责任链的顺序可以通过给责任链增加注解,增加方法,配置文件等方式来实现,这样可以更加灵活的控制责任链的顺序。 ## 5. 状态模式 State Pattern > 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。 这里在 JDK 中没有找到合适的例子,我来用一个示例来说明一下: 现在有一个播放器,有 `初始化 INITIALIZED`,`待播放 PREPARED`,`播放 PLAYING`,`暂停 PAUSED` 四种状态,按钮有 **播放** 和 **暂停**,当播放器处于不同的状态时,对应的按钮的行为也是不同的。 - 当播放器处于 `INITIALIZED` 状态时,所有按钮都是不可用的,如果点击则提示 "正在初始化,请稍后..." - 当播放器处于 `PREPARED` 状态时,**播放** 按钮可用,**暂停** 按钮不可用,如果点击 **播放** 按钮,则播放器进入 `PLAYING` 状态,如果点击 **暂停** 按钮,则提示 "播放器还没有开始播放" - 当播放器处于 `PLAYING` 状态时,**播放** 按钮不可用,**暂停** 按钮可用,如果点击 **播放** 按钮,则提示 "播放器正在播放中",如果点击 **暂停** 按钮,则播放器进入 `PAUSED` 状态 - 当播放器处于 `PAUSED` 状态时,**播放** 按钮可用,**暂停** 按钮不可用,如果点击 **播放** 按钮,则播放器进入 `PLAYING` 状态,如果点击 **暂停** 按钮,则提示 "播放器已经暂停" 先来使用普通的 if-else 来实现: ```java public class VideoPlayer { private String state = INITIALIZED; public static final String INITIALIZED = "INITIALIZED"; public static final String PREPARED = "PREPARED"; public static final String PLAYING = "PLAYING"; public static final String PAUSED = "PAUSED"; public void init() { System.out.println("开始初始化"); state = INITIALIZED; // 假如初始化需要一秒钟 try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("初始化完成"); state = PREPARED; } public void play() { if (Objects.equals(state, PREPARED) || Objects.equals(state, PAUSED)) { System.out.println("播放"); state = PLAYING; } else if (Objects.equals(state, INITIALIZED)) { System.out.println("正在初始化,请稍后..."); } else if (Objects.equals(state, PLAYING)) { System.out.println("播放器正在播放中"); } } public void pause() { if (Objects.equals(state, PLAYING)) { System.out.println("暂停"); state = PAUSED; } else if (Objects.equals(state, INITIALIZED)) { System.out.println("正在初始化,请稍后..."); } else if (Objects.equals(state, PREPARED)) { System.out.println("播放器还没有开始播放"); } else if (Objects.equals(state, PAUSED)) { System.out.println("播放器已经暂停"); } } } ``` 测试类: ```java public class VideoPlayerTest { public static void main(String[] args) { VideoPlayer videoPlayer = new VideoPlayer(); videoPlayer.play(); // Should print "正在初始化,请稍后..." videoPlayer.pause(); // Should print "正在初始化,请稍后..." videoPlayer.init(); // Should print "开始初始化" and then "初始化完成" videoPlayer.pause(); // Should print "播放器还没有开始播放" videoPlayer.play(); // Should print "播放" videoPlayer.play(); // Should print "播放器正在播放中" videoPlayer.pause(); // Should print "暂停" videoPlayer.pause(); // Should print "播放器已经暂停" } } ``` 看着晕么(我写的都晕),这么多判断,代码冗长,而且容易出错,这时候就可以使用状态模式来解决这个问题。 首先需要抽象出状态类: ```java public abstract class State { protected Context context; public void setContext(Context context){ this.context = context; } public abstract void play(); public abstract void pause(); } ``` 然后每个状态都需要实现这个抽象类: ```java public class PrearedState extends State { @Override public void play() { context.setState(Context.playingState); System.out.println("播放"); } @Override public void pause() { System.out.println("播放器还没有开始播放"); } } ``` ```java public class InitializedState extends State { @Override public void play() { System.out.println("正在初始化,请稍后..."); } @Override public void pause() { System.out.println("正在初始化,请稍后..."); } } ``` ```java public class PlayingState extends State { @Override public void play() { System.out.println("播放器正在播放中"); } @Override public void pause() { context.setState(Context.pausedState); System.out.println("暂停"); } } ``` ```java public class PausedState extends State { @Override public void play() { context.setState(Context.playingState); System.out.println("播放"); } @Override public void pause() { System.out.println("播放器已经暂停"); } } ``` 然后有个上下文对象,来存储当前的状态: ```java public class Context { private State state; public final static InitializedState initializedState = new InitializedState(); public final static PausedState pausedState = new PausedState(); public final static PlayingState playingState = new PlayingState(); public final static PrearedState prearedState = new PrearedState(); public void setState(State state) { this.state = state; this.state.setContext(this); } public State getState() { return state; } } ``` 然后播放器中有个上下文对象,再根据当前状态类调用对应的方法。 ```java public class VideoPlayer { private Context context = new Context(); public VideoPlayer() { context.setState(Context.initializedState); } public void init() { System.out.println("开始初始化"); // 假如初始化需要一秒钟 try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("初始化完成"); context.setState(Context.prearedState); } public void play() { this.context.getState().play(); } public void pause() { this.context.getState().pause(); } } ``` 测试类不变: ```java public class VideoPlayerTest { public static void main(String[] args) { VideoPlayer videoPlayer = new VideoPlayer(); videoPlayer.play(); // Should print "正在初始化,请稍后..." videoPlayer.pause(); // Should print "正在初始化,请稍后..." videoPlayer.init(); // Should print "开始初始化" and then "初始化完成" videoPlayer.pause(); // Should print "播放器还没有开始播放" videoPlayer.play(); // Should print "播放" videoPlayer.play(); // Should print "播放器正在播放中" videoPlayer.pause(); // Should print "暂停" videoPlayer.pause(); // Should print "播放器已经暂停" } } ``` ## 6. 命令模式 Command Pattern 这里在 JDK 中没有找到合适的例子,我来用一个示例来说明一下: 先看下命令模式的 UML 图: ![命令模式 UML 图](https://cdn.jun6.net/2024/03/13/65f11a6f86fb1.png?fmt=webp) 命令模式主要是为了解耦请求的发送者和接收者,请求的发送者不需要知道接收者是谁,只需要知道命令是什么,然后发送给调用者,调用者再调用命令的 `execute` 方法,然后命令再调用接收者的方法。 比如我们有一个开关,一个台灯,开关不一定只能控制台灯,还可以控制其他的设备。在这里开关是命令的发送者,台灯是命令的接收者,开关不需要知道台灯是谁,只需要知道命令是什么,台灯也不需要知道开关是谁,只需要执行开关操作。 首先定义一个命令接口: ```java public interface Command { void execute(); } ``` 然后定义一个接收者 (Receiver): ```java public class Light { public void on() { System.out.println("台灯打开"); } public void off() { System.out.println("台灯关闭"); } } ``` 然后定义一个开关命令的实现类 (ConcreteCommand): ```java public class LightOnCommand implements Command { private final Light light; public LightOnCommand(Light light) { this.light = light; } @Override public void execute() { light.on(); } } ``` ```java public class LightOffCommand implements Command { private final Light light; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { light.off(); } } ``` 然后定义一个开关 (Invoker): ```java public class Switch { private Command command; public void setCommand(Command command) { this.command = command; } public void execute() { command.execute(); } } ``` 测试类 (Client): ```java public class CommandPatternDemo { public static void main(String[] args) { Light light = new Light(); Command lightOnCommand = new LightOnCommand(light); Command lightOffCommand = new LightOffCommand(light); Switch s = new Switch(); s.setCommand(lightOnCommand); s.execute(); // Should print "台灯打开" s.setCommand(lightOffCommand); s.execute(); // Should print "台灯打开" } } ``` 看完了这个示例,你可能会觉得这个示例有点简单,甚至有点多此一举,本来可以直接创建一个台灯对象存储起来,然后直接调用台灯的 `on` 和 `off` 方法,为什么要多此一举呢? ```java public class CommandPatternDemo { public static void main(String[] args) { Light light = new Light(); light.on(); // Should print "台灯打开" light.off(); // Should print "台灯关闭" } } ``` 不过我们再深入想想,如果你有很多地方需要控制台灯,那么你就需要在每个地方都创建一个台灯对象,然后调用台灯的 `on` 和 `off` 方法。 但如果我们需要增加个定时关闭的功能,如果直接调用台灯的 `off` 方法,那么你就需要在每个地方都修改代码增加个定时器来实现。或者需要增加个调用日志,定时开启等功能,同理也需要在每个地方都修改代码。 > JDK 中本来想找个 `Runnable` 来举例,但是发现 `Runnable` 并不严格符合命令模式,他没有接收者,只有命令和调用者. > 参考: [详解命令模式本质及其在高复杂调用中的实践案例-阿里技术](https://xie.infoq.cn/article/8c74cb9796ad9fbe8c6493a10) ## 7. 迭代器模式 Iterator Pattern > 提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示 (列表、栈、数组)。 ### JDK Iterator `java.util.Iterator` 就是一个很好的例子,它提供了一种顺序访问集合的方法,而不暴露集合的内部表示。 ```java public interface Iterator<E> { boolean hasNext(); E next(); } ``` `Collection` 接口就实现了 `Iterator` 接口,我们平时使用的 `ArrayList`,`LinkedList` 等都实现了 `Collection` 接口,所以我们可以使用 `Iterator` 来遍历这些集合。 ```java public class IteratorPatternDemo { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("张三"); list.add("李四"); list.add("王五"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } } ``` 其实迭代器模式就是把迭代这个操作抽象出来,然后让集合类实现这个接口。不然的话,每个集合类都自己有一个迭代方法的话,可能方法名都不一样,调用时会很麻烦,也会有很多重复的代码。 而且根据需要我们还可以在迭代器中增加一些方法,如 `previous`,`first`,`last` 等方法,这样就可以实现双向迭代。 ## 8. 备忘录模式 Memento Pattern > 允许不暴露对象实现细节的情况下保存和恢复对象之前的状态。 这个模式在 JDK 中没有找到合适的例子,我来用一个示例来说明一下: 比如我们要保存一个对象的历史状态/快照,可以随时恢复。首先如何给对象创建一个快照呢?可能你会遍历对象的成员变量并将其复制到一个新的对象中,但对象中有些成员变量可能是 private 的,你无法直接访问到它们。而且当你想恢复到某个状态时,就要修改原始对象的成员变量为快照时的状态,这样也会碰到私有成员变量的问题。如果设置成 public 的话,又会暴露对象的实现细节,无法保证对象的封装性。 所以备忘录模式其实就是为了解决这个两个问题,解决的方式也很简单:首先原始对象,也就是需要保存快照的对象称之为原发器 (Originator) 对象,创建快照和恢复快照的方法都放到这个对象中,这样其他对象就不用从该对象内部复制成员变量了,他可以自己读取自己的状态并保存到一个快照对象中。 ```java public class Originator { private String state; public void setState(String state) { this.state = state; } public String getState() { return state; } // 生成一个快照对象(里面包含了当前对象需要记录的状态) public Memento save() { return new Memento(state); } // 从快照对象中恢复状态 public void restore(Memento memento) { state = memento.getState(); } } ``` 其中 `Memento` 就是备忘录对象,它保存了原发器对象的状态,但是不暴露原发器对象的实现细节。 ```java public class Memento { private final String state; public Memento(String state) { this.state = state; } public String getState() { return state; } } ``` 这些 `Mememto` 还需要保存到一个容器中,这里称之为管理者 (Caretaker) 对象,它保存了备忘录对象,但是不会修改备忘录对象。 ```java public class Caretaker { private final List<Memento> mementos = new ArrayList<>(); public void add(Memento memento) { mementos.add(memento); } public Memento get(int index) { return mementos.get(index); } } ``` 测试类: ```java public class MementoPatternDemo { public static void main(String[] args) { Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); originator.setState("State #1"); caretaker.add(originator.save()); System.out.println("current State: " + originator.getState()); originator.setState("State #2"); caretaker.add(originator.save()); System.out.println("current State: " + originator.getState()); originator.setState("State #3"); caretaker.add(originator.save()); System.out.println("current State: " + originator.getState()); originator.restore(caretaker.get(0)); System.out.println("First saved State: " + originator.getState()); originator.restore(caretaker.get(1)); System.out.println("Second saved State: " + originator.getState()); } } ``` > 这个模式其实核心就是为了不暴露对象实现细节,将一些职责放到了原发器对象中,这样就可以保证对象的封装性。 ## 9. 访问者模式 Visitor Pattern > 封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作。 ## 10. 中介者模式 Mediator Pattern > 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变他们之间的交互。 ### Spring ApplicationEvent Spring 中 `ApplicationEvent` 也可以认为是一个中介者,用来封装一系列的对象交互,当一个对象发生变化时,可以发布一个事件,然后其他对象可以监听这个事件,从而实现对象之间的交互。 具体可以参考 [Spring 使用事件监听来解耦代码](https://www.zhaojun.vip/archives/105/),这里不再赘述。 ### 自实现中介者模式 如有一个数据同步的功能,有三个数据源,分别是 MySQL,Redis,ES,同步策略如下: - MySQL 数据源发生变化时,需要同步到 Redis 和 ES - Redis 数据源发生变化时,无需同步 - ES 数据源发生变化时,需要同步到 MySQL 使用中介模式来实现: 首先定义一个数据库抽象类: ```java public abstract class AbstractDatabase { protected AbstractMediator mediator; // 中介者 public AbstractDatabase(AbstractMediator mediator) { this.mediator = mediator; } // 仅添加数据 public abstract void addData(String data); // 添加数据并按策略同步 public abstract void addDataAndSync(String data); } ``` 然后定义三个数据库的实现类: ```java public class EsDatabase extends AbstractDatabase { private final List<String> dataset = new ArrayList<>(); public EsDatabase(AbstractMediator mediator) { super(mediator); } @Override public void addData(String data) { System.out.println("ES 添加数据:" + data); this.dataset.add(data); } @Override public void addDataAndSync(String data) { addData(data); this.mediator.sync(this, data); // 数据同步作业交给中介者管理 } public void count() { int count = this.dataset.size(); System.out.println("Elasticsearch 统计,目前有 " + count + " 条数据,数据:" + this.dataset.toString()); } } ``` ```java public class MysqlDatabase extends AbstractDatabase { private List<String> dataset = new ArrayList<>(); public MysqlDatabase(AbstractMediator mediator) { super(mediator); } @Override public void addData(String data) { System.out.println("Mysql 添加数据:" + data); this.dataset.add(data); } @Override public void addDataAndSync(String data) { addData(data); this.mediator.sync(this, data); // 数据同步作业交给中介者管理 } public void select() { System.out.println("Mysql 查询,数据:" + this.dataset.toString()); } } ``` ```java public class RedisDatabase extends AbstractDatabase { private List<String> dataset = new ArrayList<>(); public RedisDatabase(AbstractMediator mediator) { super(mediator); } @Override public void addData(String data) { System.out.println("Redis 添加数据:" + data); this.dataset.add(data); } @Override public void addDataAndSync(String data) { addData(data); this.mediator.sync(this, data); // 数据同步作业交给中介者管理 } public void cache() { System.out.println("Redis 缓存的数据:" + this.dataset.toString()); } } ``` 然后定义一个中介者接口: ```java @Data public abstract class AbstractMediator { protected MysqlDatabase mysqlDatabase; protected RedisDatabase redisDatabase; protected EsDatabase esDatabase; public abstract void sync(AbstractDatabase sender, String data); } ``` 然后定义一个中介者的实现类: ```java public class SyncMediator extends AbstractMediator { @Override public void sync(AbstractDatabase sender, String data) { if (sender instanceof MysqlDatabase) { // mysql 同步到 redis 和 Elasticsearch this.redisDatabase.addData(data); this.esDatabase.addData(data); } else if (sender instanceof RedisDatabase) { // redis 缓存同步,不需要同步到其他数据库 } else if (sender instanceof EsDatabase) { // Elasticsearch 同步到 Mysql this.mysqlDatabase.addData(data); } } } ``` 测试类: ```java public class Client { public static void main(String[] args) { AbstractMediator syncMediator = new SyncMediator(); MysqlDatabase mysqlDatabase = new MysqlDatabase(syncMediator); RedisDatabase redisDatabase = new RedisDatabase(syncMediator); EsDatabase esDatabase = new EsDatabase(syncMediator); syncMediator.setMysqlDatabase(mysqlDatabase); syncMediator.setRedisDatabase(redisDatabase); syncMediator.setEsDatabase(esDatabase); mysqlDatabase.addDataAndSync("1"); // mysql 添加数据 1,将同步到Redis和ES中 printCurrentData(mysqlDatabase, redisDatabase, esDatabase); redisDatabase.addDataAndSync("2"); // Redis添加数据 2,将不同步到其它数据库 printCurrentData(mysqlDatabase, redisDatabase, esDatabase); esDatabase.addDataAndSync("3"); // ES 添加数据 3,只同步到 Mysql printCurrentData(mysqlDatabase, redisDatabase, esDatabase); } private static void printCurrentData(MysqlDatabase mysqlDatabase, RedisDatabase redisDatabase, EsDatabase esDatabase) { mysqlDatabase.select(); redisDatabase.cache(); esDatabase.count(); } } ``` > 从更抽象的层面看,其实就是 N 个组件需要相互调用,但是这样直接调用会导致耦合度过高,调用关系复杂,所以可以使用中介者模式来解决这个问题。 > 扩展:之前参加某个项目,其中有个组件叫 EBS (Event Bus Service),各个系统之间的接口不允许互相调用,只能通过 EBS 来调用,这也是一种中介者模式的应用。 > 但是中介者模式也有缺点,因为所有的交互都集中在了中介者中,中介者会变得很复杂。 # 四、参考 - [菜鸟教程 - 设计模式](https://www.runoob.com/design-pattern/) - [Refactoring.Guru 设计模式](https://refactoringguru.cn/design-patterns/catalog) 最后修改:2024 年 03 月 14 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请我喝杯咖啡吧。