23种设计模式在Java中的体现
1. 单例模式(Singleton Pattern)
保证一个类只有一个实例,并提供一个全局访问点。
在Java中,常用的应用单例模式的场景是线程池和缓存。线程池需要控制线程数量,避免创建过多线程导致系统崩溃,同时需要共享线程池实例,以便于控制和监控线程池的运行状况。缓存可以将数据缓存在内存中,提高数据访问的速度,而且需要保证只有一个缓存实例,避免数据不一致的问题。
例如,Java中的Runtime类就是一个典型的单例模式。因为每个Java应用程序只有一个Runtime类实例,所以在多次调用该类的getRuntime()方法时,都将返回同一个Runtime对象。这个对象可以用于控制应用程序的运行环境。
2. 工厂模式(Factory Pattern)
定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法模式使得一个类的实例化延迟到其子类。
在Java中,工厂模式常用于创建复杂对象或者对象创建的过程比较耗时的情况。使用工厂模式可以将对象的创建和使用分离,提高系统的灵活性和可维护性。
例如,Java中的Calendar类就是一个典型的工厂模式。Calendar是一个抽象类,用于处理日期和时间。具体的日历实现类由具体的工厂方法(如getInstance())来创建,可以根据需要动态选择不同的日历实现类。这种方式提高了系统的灵活性,可以根据具体的应用场景选择不同的日历实现类。
3. 抽象工厂模式(Abstract Factory Pattern)
用于创建一组相关或依赖对象的接口,而无需指定它们具体的类。
在Java中,抽象工厂模式通常用于创建一组相关的产品,例如,用户界面工具包(UI Toolkit)就是一个典型的应用场景。用户界面工具包通常包括各种组件(如按钮、文本框、标签等)和布局管理器(如网格布局、边框布局等),这些组件和布局管理器需要相互配合使用,才能构建出一个完整的用户界面。因此,使用抽象工厂模式可以将这些相关的组件和布局管理器组织起来,使得它们可以相互配合使用。
例如,Java中应用抽象工厂模式进行数据库访问。数据库访问通常需要支持不同的数据库系统,例如MySQL、Oracle、SQL Server等。我们可以使用抽象工厂模式来创建数据库访问的组件,其中抽象工厂接口定义了创建数据库连接、命令对象、结果集等组件的方法,具体工厂实现了这些方法来创建针对不同数据库系统的组件。这样,我们就可以轻松地切换不同的数据库系统,而不需要修改应用程序的代码。
4. 建造者模式(Builder Pattern)
将一个复杂对象的构建过程和表示分离,使得同样的构建过程可以创建不同的表示。
在Java中,建造者模式常被用于创建复杂的对象,例如StringBuilder、DocumentBuilder和SAXBuilder等。这些类需要构建复杂的数据结构或XML文档,但是其构建过程可以通过一系列简单的步骤来完成。
例如,Java中的StringBuilder类就是一个典型的建造者模式。StringBuilder可以用于动态地构建字符串,其构建过程是通过一系列简单的操作(如append()方法)来完成的,最终构建出一个复杂的字符串对象。下面是一个简单的Java代码示例:
StringBuilder builder = new StringBuilder(); builder.append("Hello"); builder.append(" "); builder.append("World"); String result = builder.toString(); System.out.println(result); // 输出 "Hello World"
在这个例子中,我们使用StringBuilder类构建一个字符串,首先向其中添加字符串"Hello",接着添加一个空格,最后再添加"World"。最终通过调用toString()方法来获取构建好的字符串对象。
5. 原型模式(Prototype Pattern)
是一种对象创建型设计模式,它允许通过复制现有对象来创建新对象,而不是通过实例化来创建。简单来说,原型模式就是通过复制现有对象来创建新对象的一种模式。
在Java中,常用的应用原型模式的场景是需要创建大量的相似对象,但是每个对象又有一些细微的差别,比如颜色、大小等。这时候,使用原型模式可以避免重复创建对象,提高程序的性能。
例如,Java中的Cloneable接口就是原型模式的应用。Cloneable接口是一个标记接口,它告诉JVM可以对该对象进行克隆操作。在实现Cloneable接口的类中,可以通过重写clone()方法来实现克隆操作,从而实现原型模式。例如:
public class MyObject implements Cloneable { private int id; private String name; public MyObject(int id, String name) { this.id = id; this.name = name; } @Override public MyObject clone() throws CloneNotSupportedException { return (MyObject) super.clone(); } // getters and setters }
上面的代码定义了一个MyObject类,并实现了Cloneable接口,重写了clone()方法。现在可以通过调用clone()方法来创建新的MyObject对象。例如:
MyObject obj1 = new MyObject(1, "obj1");
MyObject obj2 = obj1.clone();
在上面的代码中,首先创建了一个MyObject对象obj1,然后通过调用obj1的clone()方法来创建一个新的对象obj2。由于MyObject类实现了Cloneable接口,并重写了clone()方法,所以obj2是obj1的一个副本。
6. 适配器模式(Adapter Pattern)
是一种结构型设计模式,用于将不兼容的接口转换成客户端所期望的接口。
在Java中,适配器模式的应用场景是在已有的类库中找到合适的接口来满足新需求。通常情况下,这些类库已经被广泛使用,并且不可能对其进行修改。
举个例子,Java中的InputStream和OutputStream类提供了对不同类型数据源(如文件、网络、内存等)进行读写操作的支持。但是如果需要将一段数据写入到一个Writer对象中,就需要将InputStream适配成Reader类,这就需要使用适配器模式。Java中的InputStreamReader就是一个适配器模式的例子,它将InputStream转换成了Reader对象。
另外一个例子是在Android开发中常见的ListView和RecyclerView组件。在某些情况下,我们需要使用一种不同的数据结构来代替默认的数据源,这就需要实现一个适配器(Adapter)来将新的数据结构转换成ListView或RecyclerView所期望的数据结构。这里的适配器就是一个典型的适配器模式的应用。
总之,适配器模式的主要目的是提供一个中间层,将已有的类库或接口转换成客户端所期望的接口,从而实现不同接口之间的无缝衔接。
7. 桥接模式(Bridge Pattern)
是一种结构型设计模式,它将一个抽象类和实现类分离开来,使它们可以独立变化。桥接模式的核心思想是将抽象部分和实现部分分离,从而可以在两者之间建立一个桥梁,使得它们可以独立地变化。
在Java中,桥接模式的应用场景通常是在需要多个不同维度的变化时,使用桥接模式可以将这些维度分离出来,使得它们可以独立变化,从而更容易扩展和维护。
例如,一个绘图软件可以支持多种不同的绘图工具(如画笔、橡皮擦等),同时也可以支持多种不同的绘图对象(如直线、圆形、矩形等)。这时候可以使用桥接模式,将绘图工具和绘图对象分别抽象出来,并建立一个桥梁来连接它们。这样,就可以轻松地扩展新的绘图工具和绘图对象,而不用担心它们之间的耦合度问题。
另一个例子是JDBC(Java Database Connectivity)。JDBC允许Java程序通过标准接口来访问不同的数据库,例如MySQL、Oracle等。JDBC的核心就是将数据库驱动和Java程序分离开来,通过一个标准接口来连接它们。这个标准接口就是桥梁,它使得Java程序可以独立于数据库驱动变化,而不需要修改程序。
8. 组合模式(Composite Pattern)
是一种结构型设计模式,它允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
在Java中,常用的应用组合模式的场景是树形结构的数据组织和处理,例如文件系统、GUI界面的控件、菜单等。这些场景都具有明显的层次结构,并且树的每个节点可以看做一个对象,可以使用组合模式来处理这些节点。
例如,在Java AWT/Swing中,容器(如Panel和Frame)和组件(如Button和Label)就是一种组合模式的应用。容器和组件可以组合成一个复杂的GUI界面,同时又可以以一致的方式对待单独的组件和容器。例如,可以对整个容器进行布局和渲染,也可以对单独的组件进行事件处理和属性设置。
9.装饰模式(Decorator Pattern)
是一种结构型设计模式,它允许在运行时动态地给一个对象添加额外的功能,而不需要修改原始对象的结构。
在Java中,常用的应用装饰模式的场景是I/O流,例如BufferedInputStream和DataInputStream都是InputStream的装饰器。BufferedInputStream在InputStream的基础上添加了缓冲功能,DataInputStream在InputStream的基础上添加了对基本数据类型的读取功能。这些装饰器可以通过堆叠来组合使用,例如可以用DataInputStream包装一个BufferedInputStream来同时享受两个装饰器的功能。
另一个常用的应用场景是GUI中的图形组件,例如Swing中的JComponent和其子类。JComponent定义了许多基本的图形组件功能,如大小、位置和绘制等,而其子类(如JButton、JLabel)可以通过扩展JComponent并添加特定的功能来实现更高级的图形组件。
下面是一个使用装饰模式的Java例子,假设有一个基础的Logger类用于输出日志信息,现在我们需要扩展Logger类的功能以记录日志的时间:
public interface ILogger { void log(String message); } public class Logger implements ILogger { public void log(String message) { System.out.println(message); } } public abstract class LoggerDecorator implements ILogger { protected ILogger logger; public LoggerDecorator(ILogger logger) { this.logger = logger; } public void log(String message) { logger.log(message); } } public class TimeLogger extends LoggerDecorator { public TimeLogger(ILogger logger) { super(logger); } public void log(String message) { String time = new Date().toString(); logger.log("[" + time + "] " + message); } }
在上面的例子中,Logger类是一个基础的日志记录器,它实现了ILogger接口。LoggerDecorator是一个抽象类,它也实现了ILogger接口,并包含一个ILogger类型的成员变量logger。TimeLogger是LoggerDecorator的子类,它在基础的日志记录器基础上添加了记录日志时间的功能。使用装饰模式,我们可以通过将Logger对象传递给TimeLogger的构造函数来创建一个新的TimeLogger对象,从而实现日志记录器的功能扩展。
10. 外观模式(Facade Pattern)
是一种结构型设计模式,它提供了一个统一的接口,用来访问子系统中的一群接口。它隐藏了子系统的复杂性,让客户端能够更加简单地使用系统。
在Java中,外观模式通常应用于简化复杂系统的访问,特别是当系统中存在许多复杂的类和接口时,外观模式可以提供一个简单的接口来隐藏所有的复杂性。
一个常见的应用场景是在框架或库中,对外部开发者提供简单易用的接口,同时又能满足复杂的内部实现。例如,Hibernate框架提供了一个SessionFactory类,用于管理Hibernate的会话工厂。SessionFactory隐藏了复杂的内部实现,提供了一个简单易用的接口,让开发者可以轻松地创建和管理Hibernate的会话。
以下是一个简单的Java代码示例,演示了如何使用外观模式来隐藏系统中的复杂性:
public class CPU { public void freeze() { ... } public void jump(long position) { ... } public void execute() { ... } } public class Memory { public void load(long position, byte[] data) { ... } } public class HardDrive { public byte[] read(long lba, int size) { ... } } public class Computer { private CPU cpu; private Memory memory; private HardDrive hardDrive; public Computer() { this.cpu = new CPU(); this.memory = new Memory(); this.hardDrive = new HardDrive(); } public void startComputer() { cpu.freeze(); memory.load(BOOT_ADDRESS, hardDrive.read(BOOT_SECTOR, SECTOR_SIZE)); cpu.jump(BOOT_ADDRESS); cpu.execute(); } } public class ComputerFacade { private Computer computer; public ComputerFacade() { this.computer = new Computer(); } public void start() { computer.startComputer(); } }
在上面的代码中,Computer类表示一个计算机,它包含了CPU、Memory和HardDrive三个子系统。而ComputerFacade类则是外观类,它提供了一个简单的接口start(),用于启动计算机。这样,客户端就可以通过ComputerFacade类来访问计算机,而无需关心计算机内部的复杂实现细节。
11. 享元模式(Flyweight Pattern)
通过共享对象来减少内存占用,提高性能。
在Java中,常用的应用享元模式的场景是需要创建大量对象的情况下,通过共享已有的对象来减少内存占用。比如说字符串常量池就是一个典型的享元模式应用,Java中的字符串常量池中只保存一个拥有相同内容的字符串对象,这些对象可以被多个引用共享,从而减少内存占用。
另外,Java中的集合框架中的一些类也使用了享元模式,例如Integer类,其内部维护了一个缓存数组,用于保存-128到127之间的整数对象,这些对象可以被多个引用共享,从而避免了重复创建对象的开销。
下面是一个简单的Java代码例子,使用享元模式实现字符串对象的共享:
public class StringFactory { private static final Map<String, String> pool = new HashMap<>(); public static String getString(String s) { if (pool.containsKey(s)) { return pool.get(s); } else { String str = new String(s); pool.put(s, str); return str; } } }
上面的代码中,使用了一个静态的HashMap来存储已经创建的字符串对象,如果需要创建新的字符串对象,首先检查HashMap中是否已经存在相同内容的字符串对象,如果存在则返回已有对象,否则创建新对象并添加到HashMap中。这样就可以在不断创建字符串对象的过程中,共享已有的对象,减少内存占用。
12. 代理模式(Proxy Pattern)
是指为一个对象提供一个代理以控制该对象的访问。代理对象通常充当被代理对象的中介,客户端通过代理对象间接地访问被代理对象,从而可以在不改变原有代码的基础上,增强被代理对象的功能,或者控制对被代理对象的访问。
在Java中,代理模式的应用场景很多,例如远程代理、虚拟代理、安全代理、缓存代理等。其中,远程代理可以将对象的访问转换为网络请求,实现分布式应用;虚拟代理可以在对象被真正调用前进行懒加载,延迟对象的初始化过程,提高程序的性能;安全代理可以控制访问对象的权限,保障系统的安全性;缓存代理可以在访问对象时,先从缓存中获取数据,如果缓存中不存在,则从数据库或其他数据源中获取数据,并将数据存储到缓存中,提高数据访问的效率。
例如,Java中的动态代理就是代理模式的典型应用。动态代理可以在运行时动态地生成代理对象,不需要事先定义代理类,从而避免了编写大量的代理类代码。动态代理主要用于框架开发,如Spring框架中的AOP(面向切面编程)就是使用动态代理实现的。
13. 模板方法模式(Template Method Pattern)
定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。模板方法模式将算法的通用步骤定义在父类中,而将具体实现细节交由子类去实现。
在Java中,模板方法模式通常用于实现一个算法的骨架,让子类去实现具体的细节。例如,Java中的Servlet就使用了模板方法模式。Servlet是一个Web应用程序中的组件,用于处理来自客户端的请求和响应。在Servlet中,父类定义了Servlet处理请求的算法骨架,而具体的Servlet类则提供了对请求的具体处理逻辑。
一个简单的Java中的模板方法模式的例子是排序算法。Java的Arrays类中提供了一些常用的排序算法,如sort()和parallelSort()。这些排序算法使用了模板方法模式,其中父类定义了排序算法的框架,而子类则实现了排序算法的具体细节。例如,Arrays类中的sort()方法会调用子类实现的compareTo()方法来比较元素的大小,以实现排序。这样,使用者可以通过简单地调用Arrays.sort()方法来实现排序,而无需关心排序算法的具体实现细节。
14. 命令模式(Command Pattern)
是一种行为型设计模式,它将请求的发送者和接收者解耦,使得发送者不需要知道请求的具体接收者是谁,同时也让接收者不需要知道请求的具体内容是什么。
在命令模式中,请求被封装成一个命令对象,该命令对象包含了具体的请求内容和接收者,可以被存储和传递。命令模式包括三个主要角色:Command(命令接口),ConcreteCommand(具体命令类)和Receiver(命令接收者)。
在Java中,命令模式可以应用于很多场景,比如GUI中的菜单项、按钮等,以及日志记录系统、事务处理系统等。
举个例子,我们可以考虑一个简单的文本编辑器,其中包含了一些常见的操作,比如打开、保存、复制、粘贴等。我们可以为每个操作创建一个命令对象,并将其与具体的接收者(文本编辑器)关联。这样,在文本编辑器中,只需要维护一个命令队列,并在需要执行某个操作时,将相应的命令对象加入到队列中即可。这样,文本编辑器就不需要知道具体的操作是什么,只需要执行命令队列中的命令即可。这种方式可以更加灵活地管理和维护命令队列,同时也可以方便地添加新的操作。
15. 迭代器模式(Iterator Pattern)
它提供一种方法来访问聚合对象中的元素,而不需要暴露聚合对象的内部表示。
在Java中,迭代器模式被广泛应用于集合框架中。Java的集合框架提供了多种类型的集合,如List、Set、Map等,每个集合都实现了Iterable接口,该接口提供了一个iterator()方法,用于返回一个Iterator对象,通过该对象可以访问集合中的元素。
例如,我们可以使用迭代器遍历Java中的ArrayList集合:
List<String> list = new ArrayList<>(); list.add("foo"); list.add("bar"); list.add("baz"); Iterator<String> iterator = list.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next()); }
在这个例子中,我们创建了一个ArrayList对象,并向其中添加了三个元素。然后,我们通过调用iterator()方法获取了一个Iterator对象,并使用while循环遍历该集合中的元素。在每次循环中,我们使用hasNext()方法判断集合中是否还有下一个元素,如果有,则使用next()方法获取下一个元素并输出。
16. 观察者模式(Observer Pattern)
是一种软件设计模式,用于在对象之间建立一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖它的对象都会得到通知并自动更新。
在Java中,观察者模式被广泛应用于事件处理机制。比如,Swing框架中的事件处理机制就是基于观察者模式实现的。在这个机制中,事件源对象充当了被观察者的角色,而事件监听器则充当了观察者的角色。当事件源对象的状态发生改变时,它会自动通知所有注册了监听器的对象,并调用监听器对象中相应的方法来处理事件。
另外,Java中的JavaBeans规范也是基于观察者模式实现的。在这个规范中,JavaBean是一种可重用的软件组件,它可以发出属性变化事件,监听器对象可以注册到JavaBean中,当JavaBean属性发生改变时,所有注册的监听器对象都会收到通知并更新自身状态。
举个例子,假设我们有一个数据模型类,当模型数据发生改变时,我们需要更新UI界面上的显示。这时,我们可以将UI界面上的组件对象注册为模型类的监听器,当模型数据发生改变时,模型类就会自动通知所有注册的监听器对象,并调用相应的方法来更新UI界面上的显示。
17. 中介者模式(Mediator Pattern)
是一种行为型设计模式,它定义了一个中介者对象来封装一系列对象之间的交互,使得这些对象之间不需要显式地相互引用,从而降低耦合度,增加可维护性和可扩展性。
在Java中,中介者模式常用于GUI应用程序中。GUI应用程序通常有很多窗口组件,这些组件之间需要相互通信,例如当一个按钮被点击时,需要更新另一个文本框的内容。使用中介者模式可以将这些组件的交互逻辑封装在一个中介者对象中,从而减少组件之间的耦合度。
一个典型的Java应用中,Swing框架中的JComboBox就使用了中介者模式。JComboBox是一个下拉列表框组件,当用户选择某个选项时,JComboBox会自动更新显示的内容。JComboBox使用了一个ListDataListener中介者对象来监听列表数据的变化,并通知所有相关的组件更新显示内容。
18. 备忘录模式(Memento Pattern)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象到原先的状态。
在Java中,备忘录模式常用于需要撤销或回滚操作的场景,比如文本编辑器中的撤销/重做功能。在该功能中,文本编辑器需要在每次修改文本内容之前保存当前文本内容的备份,以便用户撤销时恢复到之前的状态。此时,备忘录模式可以将文本内容保存在一个备忘录对象中,然后将备忘录对象存储在一个栈中,用户撤销时从栈中取出上一个备忘录对象,将文本内容恢复到之前的状态。
一个Java中的例子是Java的Swing框架中的UndoManager类,它提供了撤销/重做功能。在每次操作之前,UndoManager会创建一个Memento对象保存当前状态,在用户撤销时,UndoManager从栈中取出上一个Memento对象,将组件的状态恢复到之前的状态。
19. 解释器模式(Interpreter Pattern)
是一种行为设计模式,它定义了一种语言和该语言的解释器,用于解释语言中的表达式。该模式将语言中的每个表达式都表示为对象,并提供了解释器来解释这些表达式。解释器模式通常用于编写编译器或解释器。
在Java中,解释器模式通常用于处理文本或XML文件等数据格式。例如,Java中的正则表达式引擎就是一个典型的解释器模式的应用场景。正则表达式通常用于在文本中搜索、替换或提取特定的内容,Java提供了java.util.regex包来支持正则表达式的处理,该包中的Pattern和Matcher类就是典型的解释器类。
另外一个常见的应用场景是XML解析器。在Java中,常用的XML解析器有DOM解析器和SAX解析器。DOM解析器将整个XML文档解析成一个树形结构,然后允许使用DOM API来操作该树形结构;SAX解析器则是一种基于事件驱动的解析器,它将XML文档解析为一系列事件,当遇到XML元素的开始、结束、属性等事件时,SAX解析器会触发相应的回调方法,可以在这些回调方法中对XML文档进行处理。这两种XML解析器都是解释器模式的应用。
20. 状态模式(State Pattern)
是一种行为型设计模式,它允许一个对象在其内部状态改变时改变其行为。状态模式通过将对象的行为委托到表示不同状态的对象中,实现了状态转换的灵活性,同时避免了使用大量的条件判断语句。
在Java中,状态模式常用于处理对象在不同状态下的行为差异,特别是那些需要频繁变更状态的对象。例如,一个状态机就可以通过状态模式来实现,状态机中的每个状态都可以用一个类来表示,并在状态转换时更新当前状态。
Java中的线程状态就是一个典型的状态模式的应用场景。Java线程可以有不同的状态,如NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED等。每个状态都对应一个Java枚举类,表示线程在该状态下的行为。例如,线程在BLOCKED状态下等待锁,线程在WAITING状态下等待其他线程的通知。
举一个Java中的状态模式的例子,假设我们要实现一个简单的电梯系统。电梯有三个状态:开门状态、关门状态和运行状态。在开门状态下,电梯门会保持打开状态;在关门状态下,电梯门会保持关闭状态;在运行状态下,电梯会根据当前目的地的楼层上下运行,并在到达目的地后停下来。我们可以使用状态模式来实现电梯系统,每个状态都对应一个类,表示电梯在该状态下的行为,状态转换可以通过调用电梯类的方法来完成。
21.策略模式(Strategy Pattern)
是一种行为设计模式,它允许在运行时选择算法的行为。它定义了一系列算法,封装每个算法,并使它们可互换。该模式让算法独立于使用它的客户端。
在 Java 中,策略模式的应用场景有很多,比如排序算法、支付方式等。其中支付方式可以是多种选择,比如信用卡支付、支付宝支付、微信支付等,而且这些支付方式可以动态的进行添加和删除。
举个 Java 中的例子,假设我们有一个商店类,它有一个支付方式属性,可以根据不同的支付方式进行支付。我们可以定义一个 PaymentStrategy 接口,它有一个 pay() 方法,然后定义不同的支付方式类实现该接口,比如 CreditCardStrategy、AlipayStrategy、WechatPayStrategy 等,每个类实现自己的 pay() 方法,具体实现可以参考下面的代码:
// 定义支付策略接口 public interface PaymentStrategy { public void pay(double amount); } // 信用卡支付方式 public class CreditCardStrategy implements PaymentStrategy { public void pay(double amount) { System.out.println("Paying " + amount + " using credit card"); } } // 支付宝支付方式 public class AlipayStrategy implements PaymentStrategy { public void pay(double amount) { System.out.println("Paying " + amount + " using Alipay"); } } // 微信支付方式 public class WechatPayStrategy implements PaymentStrategy { public void pay(double amount) { System.out.println("Paying " + amount + " using WeChat Pay"); } } // 商店类 public class Store { private PaymentStrategy paymentStrategy; public Store(PaymentStrategy paymentStrategy) { this.paymentStrategy = paymentStrategy; } public void pay(double amount) { paymentStrategy.pay(amount); } }
然后我们可以在使用商店类的时候,根据需要选择不同的支付方式:
Store store = new Store(new CreditCardStrategy()); store.pay(100); // 输出:Paying 100.0 using credit card store = new Store(new AlipayStrategy()); store.pay(50); // 输出:Paying 50.0 using Alipay store = new Store(new WechatPayStrategy()); store.pay(200); // 输出:Paying 200.0 using WeChat Pay
22. 职责链模式(Chain of Responsibility Pattern)
是一种行为型设计模式,其目的是将请求的发送者和接收者解耦,并且在发送者和接收者之间建立一个处理请求的链。
职责链模式通常由多个处理器组成,每个处理器都有一个指向下一个处理器的引用,当请求从发送者进入到处理器链时,每个处理器会判断是否自己能够处理这个请求,如果能够处理就直接处理掉,如果不能处理就将请求传递给下一个处理器,直到请求被处理掉或者没有处理器能够处理为止。
在Java中,职责链模式的应用场景非常广泛,例如Servlet容器中的Filter链就是一种典型的职责链模式。Servlet容器中的每个请求都需要经过Filter链中的多个Filter处理,每个Filter都有一个指向下一个Filter的引用,当请求进入Filter链时,每个Filter会检查自己是否能够处理这个请求,如果能够处理就直接处理掉,如果不能处理就将请求传递给下一个Filter,直到请求被处理掉或者没有Filter能够处理为止。
另一个Java中职责链模式的应用是Spring框架中的拦截器(Interceptor)链。在Spring框架中,每个请求都需要经过拦截器链中的多个拦截器处理,每个拦截器都有一个指向下一个拦截器的引用,当请求进入拦截器链时,每个拦截器会检查自己是否能够处理这个请求,如果能够处理就直接处理掉,如果不能处理就将请求传递给下一个拦截器,直到请求被处理掉或者没有拦截器能够处理为止。
以下是一个简单的Java代码示例,演示了职责链模式的实现:
bstract class Logger { public static int ERR = 3; public static int NOTICE = 5; public static int DEBUG = 7; protected int mask; // 设置下一个处理者 protected Logger next; public Logger setNext(Logger log) { next = log; return log; } public void message(String msg, int priority) { if (priority <= mask) { writeMessage(msg); } if (next != null) { next.message(msg, priority); } } abstract protected void writeMessage(String msg); } class EmailLogger extends Logger { public EmailLogger(int mask) { this.mask = mask; } protected void writeMessage(String msg) { System.out.println("Sending via email: " + msg); } } class StdoutLogger extends Logger { public StdoutLogger(int mask) { this.mask = mask; } protected void writeMessage(String msg) { System.out.println("Writing to stdout: " + msg); } } class StderrLogger extends Logger { public StderrLogger(int mask) { this.mask = mask; } protected void writeMessage(String msg) { System.err.println("Sending to stderr: " + msg); } }
上述代码中,Logger
是抽象类,定义了处理日志消息的基本行为。具体的处理者,即 EmailLogger
、StdoutLogger
和 StderrLogger
继承自 Logger
类,实现了各自的消息处理逻辑。在 Logger
类中,使用了链表的结构来链接处理者,当某个处理者无法处理某个消息时,会将消息传递给下一个处理者,直到找到合适的处理者为止。
这种职责链模式的实现方式,可以很好地解耦消息发送者和接收者之间的关系,使得它们之间可以动态地进行组合和拆分,提高了系统的灵活性和可维护性。在实际应用中,这种模式可以用于日志记录、异常处理、权限验证等方面。
23. 访问者模式(Visitor Pattern)
是一种行为型设计模式,它允许你定义一些操作,而不需要改变要进行操作的对象。它将操作和对象结构分离开来,使得操作可以在不改变对象结构的前提下添加到对象中。
在访问者模式中,有两个主要的角色:访问者和被访问者。被访问者是一个包含一些元素的对象结构,而访问者是一个定义了访问被访问者的方法的对象。访问者对象可以访问被访问者中的元素,从而对它们进行一些操作。
在Java中,访问者模式经常用于解决对象结构中元素的操作问题。例如,一个包含多种类型图形对象的图形编辑器,可以使用访问者模式对这些图形对象进行操作。
下面是一个简单的 Java 代码示例,展示了访问者模式的实现:
// 抽象元素类 interface Element { void accept(Visitor visitor); } // 具体元素类A class ConcreteElementA implements Element { @Override public void accept(Visitor visitor) { visitor.visitConcreteElementA(this); } // 元素A特有的方法 public String operationA() { return "具体元素A的操作"; } } // 具体元素类B class ConcreteElementB implements Element { @Override public void accept(Visitor visitor) { visitor.visitConcreteElementB(this); } // 元素B特有的方法 public String operationB() { return "具体元素B的操作"; } } // 抽象访问者 interface Visitor { void visitConcreteElementA(ConcreteElementA concreteElementA); void visitConcreteElementB(ConcreteElementB concreteElementB); } // 具体访问者类 class ConcreteVisitor implements Visitor { @Override public void visitConcreteElementA(ConcreteElementA concreteElementA) { System.out.println(concreteElementA.operationA() + "被ConcreteVisitor访问"); } @Override public void visitConcreteElementB(ConcreteElementB concreteElementB) { System.out.println(concreteElementB.operationB() + "被ConcreteVisitor访问"); } } // 对象结构类 class ObjectStructure { private List<Element> elements = new ArrayList<>(); public void add(Element element) { elements.add(element); } public void remove(Element element) { elements.remove(element); } public void accept(Visitor visitor) { for (Element element : elements) { element.accept(visitor); } } } // 客户端测试代码 public class Client { public static void main(String[] args) { ObjectStructure objectStructure = new ObjectStructure(); objectStructure.add(new ConcreteElementA()); objectStructure.add(new ConcreteElementB()); Visitor visitor = new ConcreteVisitor(); objectStructure.accept(visitor); } }
在这个例子中,我们定义了两个具体的元素类 ConcreteElementA 和 ConcreteElementB,这两个类实现了 Element 接口,提供了 accept() 方法用于接受访问者的访问。然后,我们定义了一个抽象访问者接口 Visitor 和一个具体的访问者类 ConcreteVisitor,ConcreteVisitor 类实现了 Visitor 接口,实现了 visitConcreteElementA() 和 visitConcreteElementB() 方法,用于对不同类型的元素进行访问。最后,我们定义了一个对象结构类 ObjectStructure,用于维护元素的集合,并提供了 accept() 方法用于接受访问者的访问。在客户端测试代码中,我们创建了一个 ObjectStructure 对象,并添加了两个元素对象,然后创建了一个 ConcreteVisitor 对象,并将它传递给 ObjectStructure 对象的 accept() 方法,让访问者对元素进行访问。
好,23种设计模式到此结束了!