Java常用设计模式(三)
Java设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结
组合模式(Composite Pattern)
组合模式是一种结构型设计模式,它允许将对象组合成树形结构,以表示“部分-整体”的层次关系。组合模式可以让客户端使用统一的方式处理单个对象和组合对象,从而简化了客户端的代码。
使用场景
-
当需要表示一个对象的部分-整体层次结构时,可以使用组合模式。例如,可以使用组合模式来表示一个文件夹,它包含多个文件和其他文件夹。
-
当客户端代码需要统一地处理单个对象和组合对象时,可以使用组合模式。这样客户端代码可以使用相同的代码来处理单个对象和组合对象,而不需要进行特判。
-
当需要对树形结构进行递归遍历时,可以使用组合模式。组合模式可以让代码更加简洁,而且可以轻松地遍历整个树形结构
-
当需要动态地添加或删除树形结构中的子节点时,可以使用组合模式。组合模式可以让你方便地添加或删除子节点,而且不会影响其他节点的行
代码实现
以下是另一个使用Java组合模式的例子,它表示一个文件系统:
import java.util.ArrayList;
import java.util.List;
public class FileSystem {
private String name;
private boolean isFile;
private List<FileSystem> children = new ArrayList<>();
public FileSystem(String name, boolean isFile) {
this.name = name;
this.isFile = isFile;
}
public void add(FileSystem fileSystem) {
children.add(fileSystem);
}
public void remove(FileSystem fileSystem) {
children.remove(fileSystem);
}
public void display(int depth) {
System.out.println("-".repeat(depth) + name);
for (FileSystem fileSystem : children) {
fileSystem.display(depth + 2);
}
}
public static void main(String[] args) {
FileSystem root = new FileSystem("C:", false);
FileSystem windowsFolder = new FileSystem("Windows", false);
FileSystem programFilesFolder = new FileSystem("Program Files", false);
FileSystem notepadFile = new FileSystem("notepad.exe", true);
FileSystem javaFolder = new FileSystem("Java", false);
FileSystem eclipseFolder = new FileSystem("Eclipse", false);
FileSystem eclipseExeFile = new FileSystem("eclipse.exe", true);
root.add(windowsFolder);
root.add(programFilesFolder);
programFilesFolder.add(javaFolder);
javaFolder.add(eclipseFolder);
eclipseFolder.add(eclipseExeFile);
windowsFolder.add(notepadFile);
root.display(0);
}
}
在这个例子中,FileSystem
表示一个文件系统中的文件或文件夹,它有一个name
属性和一个isFile
属性,用于判断它是文件还是文件夹。add
和remove
方法用于添加或删除子节点,display
方法用于递归地打印整个文件系统。
在main
方法中,我们创建了一个C:
盘的文件系统,并添加了Windows
文件夹、Program Files
文件夹、notepad.exe
文件、Java
文件夹、Eclipse
文件夹和eclipse.exe
文件。我们将这些文件和文件夹按照层次结构组织起来,方便用户查看整个文件系统。
运行这个程序会输出以下结果:
C:
--Windows
----notepad.exe
--Program Files
----Java
------Eclipse
--------eclipse.exe
使用小结
组合模式可以用于处理具有树形结构的对象集合。比如组织架构图、文件系统、图形场景图等。使用组合模式可以方便地组合对象,以实现复杂的功能。
外观模式(Facade Pattern)
外观模式是一种结构型设计模式,它提供了一个简单的接口,隐藏了一组复杂的子系统的复杂性,使得客户端可以更容易地使用这个子系统。
外观模式的核心思想是通过提供一个简化的接口,将系统的复杂性封装起来,从而降低系统的耦合性,并使得系统更易于维护和扩展。
使用场景
-
系统中包含多个模块,每个模块都有自己的接口和实现,需要对外提供一个统一的接口
-
系统中存在多个复杂的对象或类,需要对外提供简单的接口
-
需要解耦系统的各个组件,使得它们可以独立变化
-
需要隔离系统的变化,使得系统的不同模块可以独立变化
代码实现
一个常见的外观模式的例子是电脑开机过程。在电脑开机的过程中,有许多子系统需要被初始化,例如 CPU、内存、硬盘等等。如果每个子系统都要直接与客户端进行交互,那么客户端的代码会非常复杂。
使用外观模式可以将所有子系统的初始化过程封装到一个简单的接口中,从而使得客户端只需要与外观对象交互,就可以完成所有的初始化过程。
// CPU 子系统
public class CPU {
public void start() {
System.out.println("CPU is starting...");
}
}
// 内存子系统
public class Memory {
public void start() {
System.out.println("Memory is starting...");
}
}
// 硬盘子系统
public class HardDrive {
public void start() {
System.out.println("HardDrive is starting...");
}
}
// 外观类
public class Computer {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public Computer() {
cpu = new CPU();
memory = new Memory();
hardDrive = new HardDrive();
}
// 提供一个简单的接口,封装了所有子系统的初始化过程
public void start() {
System.out.println("Computer is starting...");
cpu.start();
memory.start();
hardDrive.start();
System.out.println("Computer has started...");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
computer.start();
}
}
在这个例子中,CPU
、Memory
、HardDrive
分别代表电脑开机过程中的 CPU、内存、硬盘子系统,Computer
则是外观类,它封装了所有子系统的初始化过程,提供了一个简单的 start
方法。客户端只需要与 Computer
对象交互即可完成电脑的开机过程,而不需要了解每个子系统的实现细节。
使用小结
在Java中,外观模式经常被用于封装底层的复杂子系统,提供一个更加简单、易于使用的接口给上层模块调用。
-
JDBC
是Java中访问关系型数据库的标准接口,它封装了许多与数据库操作相关的底层细节,使得程序员可以通过简单的接口来进行数据库操作。JDBC
的Connection、Statement、ResultSet
等接口和类就是外观模式的典型应用,它们提供了一个简单的接口,封装了与数据库底层的交互细节。 -
Spring
其中的许多组件都使用了外观模式来隐藏底层的复杂性。例如,Spring
中的AOP
(面向切面编程)模块提供了一个简单的接口,封装了底层的动态代理、拦截器等复杂细节;Spring
中的JDBC
模块也提供了一个简单的接口,封装了与数据库操作相关的底层细节。 -
Java网络编程中的
Socket
类和ServerSocket
类也是外观模式的典型应用,它们提供了一个简单的接口,封装了与网络通信相关的底层细节。 -
Java虚拟机中的类加载器、内存管理、线程调度等子系统都是外观模式的典型应用,它们提供了一个简单的接口,封装了与虚拟机底层相关的复杂细节。
享元模式(Flyweight Pattern)
享元模式是一种结构型设计模式,它通过共享对象来减少内存占用和提高系统性能。享元模式适用于大量细粒度的对象,这些对象具有相似的属性,而且这些属性可以被共享。
在享元模式中,我们将对象分为两种:
-
内部状态指对象共享的部分,可以被多个对象共享;
-
外部状态则指对象独有的部分,不能被共享。
通过将内部状态抽取出来,我们可以减少系统中需要创建的对象数量,从而降低内存消耗。
使用场景
-
当一个应用程序需要创建大量的相似对象时,使用享元模式可以减少内存占用和提高系统性能
-
当一个对象的状态可以被拆分为内部状态和外部状态时,使用享元模式可以将内部状态共享,从而减少对象数量和内存消耗。
-
当一个应用程序需要使用缓存来提高性能时,可以使用享元模式来实现缓存功能,从而提高系统性能。
代码实现
使用享元模式实现一个简单的字符串存储库,它可以存储大量的字符串,同时尽可能节省内存。
首先定义一个 Flyweight
接口,它表示共享的字符串对象。具体的字符串对象将实现这个接口。
public interface Flyweight {
void print(String str);
}
接下来,定义一个具体的 Flyweight 实现类,它包含一个字符串值和一个计数器,用于记录该字符串的使用次数
public class ConcreteFlyweight implements Flyweight {
private String value;
private int count;
public ConcreteFlyweight(String value) {
this.value = value;
this.count = 0;
}
@Override
public void print(String str) {
System.out.println("Printing " + value + " for " + str);
count++;
}
public int getCount() {
return count;
}
}
然后,定义一个 FlyweightFactory
工厂类,它负责管理 Flyweight
对象并根据需要创建它们。
import java.util.HashMap;
import java.util.Map;
public class FlyweightFactory {
private static final Map<String, Flyweight> flyweights = new HashMap<>();
public static Flyweight getFlyweight(String value) {
Flyweight flyweight = flyweights.get(value);
if (flyweight == null) {
flyweight = new ConcreteFlyweight(value);
flyweights.put(value, flyweight);
}
return flyweight;
}
}
最后,定义一个 Client
类,它负责使用 Flyweight
对象来存储字符串
public class Client {
public static void main(String[] args) {
String[] strings = {"hello", "world", "hello", "java", "world"};
for (String str : strings) {
Flyweight flyweight = FlyweightFactory.getFlyweight(str);
flyweight.print(str);
}
System.out.println("Flyweight count: " + FlyweightFactory.flyweights.size());
for (Flyweight flyweight : FlyweightFactory.flyweights.values()) {
System.out.println("String: " + ((ConcreteFlyweight) flyweight).value +
", Count: " + ((ConcreteFlyweight) flyweight).getCount());
}
}
}
在这个示例中,使用 FlyweightFactory
来管理共享的字符串对象。每个具体的 Flyweight
实现类都包含一个字符串值和一个计数器,用于记录该字符串的使用次数。当 Client
类使用 FlyweightFactory
来存储字符串时,它将创建或获取一个共享的 Flyweight
对象,并使用它来存储字符串。最后,我们可以查看 FlyweightFactory
中保存的所有 Flyweight
对象,以及它们的使用次数。
使用小结
使用享元模式可以显著降低内存消耗并提高系统的性能。比如:
-
Java 中的字符串池是一个存储字符串对象的池子,可以共享字符串对象,而不是为每个字符串都创建一个新的实例。
-
Java 中的数据库连接池可以缓存一些已经建立好的数据库连接,当需要使用数据库时,可以直接从连接池中获取连接,而不需要每次都创建一个新的连接。这样可以大大提高数据库的访问效率和性能。
代理模式(Proxy Pattern)
代理模式是一种结构型设计模式,它为其他对象提供一个代理以控制对该对象的访问。代理是一个具有与原始对象相同的接口的对象,客户端不必知道它与原始对象交互的方式。代理可以拦截对原始对象的访问,并在某些情况下将请求传递给原始对象。
代理模式有两种主要形式:
-
静态代理:在编译时就已经确定了代理类和被代理类之间的关系,通常需要为每个被代理类都编写一个对应的代理类,并实现相同的接口。 静态代理的优点是简单易懂,缺点是不灵活,代码冗余。
-
动态代理:在运行时动态生成代理对象,并根据反射机制调用被代理类的方法。 动态代理可以使用
Java
原生API或者第三方框架来实现,如JDK Proxy、CGLIB、
等。 动态代理的优点是灵活高效,缺点是复杂难懂。
使用场景
代理模式的主要目的是通过代理对象来控制对原始对象的访问,并提供一些额外的功能。比如:
-
当我们想要隐藏某个类时,可以为其提供代理类。例如,我们想要访问一个远程对象,但是不想暴露其网络细节,就可以使用代理类来封装网络通信
-
当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现。例如,我们想要限制某些用户对某些方法的访问,就可以在代理类中进行权限检查。
-
当我们要扩展某个类的某个功能时,可以使用代理模式。 例如,我们想要在调用某个方法之前或之后添加日志、缓存、事务等功能,就可以在代理类中实现
代码实现
静态代理
一个简单的 Java 静态代理例子,它模拟了一个银行账户的操作:
// 定义一个账户接口
public interface Account {
void deposit(double amount);
void withdraw(double amount);
}
// 实现账户接口的类
public class BankAccount implements Account {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
public void deposit(double amount) {
balance += amount;
System.out.println("Deposited " + amount + ", balance is now " + balance);
}
public void withdraw(double amount) {
if (balance >= amount) {
balance -= amount;
System.out.println("Withdrew " + amount + ", balance is now " + balance);
} else {
System.out.println("Sorry, insufficient balance");
}
}
}
// 定义一个账户代理类
public class AccountProxy implements Account {
private BankAccount bankAccount;
public AccountProxy(BankAccount bankAccount) {
this.bankAccount = bankAccount;
}
public void deposit(double amount) {
bankAccount.deposit(amount);
}
public void withdraw(double amount) {
bankAccount.withdraw(amount);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 创建一个银行账户实例
BankAccount account = new BankAccount(1000.0);
// 创建一个账户代理实例
Account accountProxy = new AccountProxy(account);
// 使用账户代理进行操作
accountProxy.deposit(500.0);
accountProxy.withdraw(200.0);
accountProxy.withdraw(2000.0);
}
}
在上面的例子中,BankAccount
是账户接口 Account
的实现类,它负责实际的存款和取款操作。而 AccountProxy
则是账户代理类,它实现了账户接口,并在其中持有一个 BankAccount
实例。在 AccountProxy
的 deposit
和 withdraw
方法中,它会将操作转发给 BankAc
。
在 Main
方法中,客户端使用代理来执行操作,而无需直接操作实际对象。
动态代理
使用 Java 内置的 java.lang.reflect.Proxy
类来创建动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义一个账户接口
public interface Account {
void deposit(double amount);
void withdraw(double amount);
}
// 实现账户接口的类
public class BankAccount implements Account {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
public void deposit(double amount) {
balance += amount;
System.out.println("Deposited " + amount + ", balance is now " + balance);
}
public void withdraw(double amount) {
if (balance >= amount) {
balance -= amount;
System.out.println("Withdrew " + amount + ", balance is now " + balance);
} else {
System.out.println("Sorry, insufficient balance");
}
}
}
// 定义一个账户代理类
public class AccountProxy implements InvocationHandler {
private BankAccount bankAccount;
public AccountProxy(BankAccount bankAccount) {
this.bankAccount = bankAccount;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("deposit")) {
System.out.println("Before deposit");
method.invoke(bankAccount, args);
System.out.println("After deposit");
} else if (method.getName().equals("withdraw")) {
System.out.println("Before withdraw");
method.invoke(bankAccount, args);
System.out.println("After withdraw");
}
return null;
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 创建一个银行账户实例
BankAccount account = new BankAccount(1000.0);
// 创建一个账户代理实例
AccountProxy accountProxy = new AccountProxy(account);
// 使用动态代理创建一个代理对象
Account proxy = (Account) Proxy.newProxyInstance(
Account.class.getClassLoader(),
new Class<?>[] { Account.class },
accountProxy
);
// 使用代理对象进行操作
proxy.deposit(500.0);
proxy.withdraw(200.0);
proxy.withdraw(2000.0);
}
}
BankAccount
和 AccountProxy
类的定义与静态代理例子相同。不同之处在于,我们创建了一个实现了 InvocationHandler
接口的 AccountProxy
类,它的 invoke
方法用来处理代理对象的方法调用。在 invoke
方法中,我们可以根据被调用的方法名称来添加一些额外的功能。在这个例子中,我们为 deposit
和 withdraw
方法添加了前置和后置操作。
在 Main
类中,创建了一个银行账户实例,并将它传递给了一个账户代理实例。然后,我们使用 Proxy.newProxyInstance
方法创建一个动态代理对象,该对象实现了 Account
接口,并在其方法调用时会调用 AccountProxy
的 invoke
方法。最后,我们使用代理对象进行存款和取款操作。
使用小结
代理模式在Java
中的应用比较广泛,比如Spring
的AOP
实现、远程RPC
调用等。代理模式可以在不修改原始接口的情况下,对目标对象进行增强或者替换
责任链模式(Chain of Responsibility Pattern)
责任链模式是一种行为型设计模式,它允许多个对象来处理请求,并且将这些对象连成一条链。当请求到来时,它会依次经过链上的对象,直到有一个对象能够处理请求为止。
责任链模式和过滤器模式都是行为型设计模式,它们的主要目的是将处理请求的对象组织成一个链,并让请求沿着这个链传递,直到找到能够处理该请求的对象为止。
那他们有什么区别吗?
-
用途不同:责任链模式的主要目的是让多个对象都有机会处理同一个请求,而过滤器模式的主要目的是在一个对象中实现多个过滤器,并依次对请求进行处理。
-
处理方式不同:责任链模式中,每个处理器都会判断自己是否能够处理请求,如果可以则处理请求,并将请求传递给下一个处理器;如果不能处理,则将请求传递给下一个处理器。而过滤器模式中,每个过滤器都是独立的,它们按照一定的顺序依次处理请求,如果某个过滤器不能处理请求,则直接返回。
-
对象关系不同:责任链模式中,处理器之间通常是单向关系,即每个处理器只知道下一个处理器是谁,而不知道上一个处理器是谁。而过滤器模式中,过滤器之间通常是相互独立的,它们不需要知道彼此的存在。
-
实现方式不同:责任链模式通常是通过继承或组合的方式实现的,而过滤器模式通常是通过组合的方式实现的。
使用场景
责任链模式通常用于以下场景:
- 请求的处理涉及多个对象,并且处理流程需要按顺序进行。
- 请求的处理方式需要动态决定,或者需要在不同时间处理请求。
- 在请求处理过程中,需要将请求拆分成多个部分分别处理,最后将它们合并。
- 需要在不影响客户端的情况下增强处理流程。
- 需要在系统中动态添加或删除处理器。
- 处理器之间需要解耦,避免它们之间的直接依赖关系。
代码实现
假设我们有一个处理HTTP
请求的框架,其中有多个过滤器(Filter
),每个过滤器都可以对请求进行处理,如果当前的过滤器不能处理请求,就将请求交给下一个过滤器。代码如下:
public interface Filter {
void doFilter(Request request, Response response, FilterChain chain);
}
public class FilterA implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
System.out.println("过滤器A处理请求");
// 处理请求
chain.doFilter(request, response);
// 处理响应
}
}
public class FilterB implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
System.out.println("过滤器B处理请求");
// 处理请求
chain.doFilter(request, response);
// 处理响应
}
}
public class FilterChain {
private List<Filter> filters = new ArrayList<>();
private int index = 0;
public void addFilter(Filter filter) {
filters.add(filter);
}
public void doFilter(Request request, Response response) {
if (index == filters.size()) {
return;
}
Filter filter = filters.get(index);
index++;
filter.doFilter(request, response, this);
}
}
public class Request {}
public class Response {}
public class Main {
public static void main(String[] args) {
Request request = new Request();
Response response = new Response();
FilterChain chain = new FilterChain();
chain.addFilter(new FilterA());
chain.addFilter(new FilterB());
chain.doFilter(request, response);
}
}
定义了一个Filter接口和两个实现类 FilterA
和 FilterB
,它们都可以对请求进行处理。我们还定义了一个 FilterChain
类,它持有所有的过滤器,当请求到来时,它会依次将请求交给每个过滤器进行处理,直到有一个过滤器能够处理请求为止。
过滤器A处理请求
过滤器B处理请求
使用小结
责任链模式在Java中应用场景比较广泛,例如:
-
在
Java Servlet
框架中,请求被Servlet
容器传递给过滤器链,每个过滤器都可以对请求进行处理,如果需要将请求传递给下一个过滤器,则需要调用chain.doFilter()
方法,直到最终到达Servlet
为止。 -
在
Spring
框架中,责任链模式被广泛应用于AOP
和数据源路由等方面。在AOP
方面,每个切面可以定义多个切点,并且可以定义切点的顺序,每个切点都可以对方法进行前置或后置处理,或者抛出异常;在数据源路由方面,可以根据不同的业务需求,将请求路由到不同的数据源进行处理。 -
在
MyBatis
框架中,责任链模式被用于SQL
解析和SQL
执行等方面。在解析方面,将SQL解析成AST(Abstract Syntax Tree)
树,并且可以通过责任链模式,将不同的解析器组合起来,实现多种SQL语法的解析
解释器模式(Interpreter Pattern)
解释器模式是一种行为型设计模式,它定义了一种语言语法,以及解释器,可以解释这种语法。解释器模式通常用于编译器、解析器、表达式计算器等领域。例如,正则表达式引擎就是一个常见的使用解释器模式的例子。
使用场景
-
当有一个语言需要解释执行,并且可以将该语言表示为一个抽象语法树时,解释器模式是一个很好的选择。
-
当需要处理一些规则或配置时,解释器模式也可以作为一种替代方案。例如,编写一个计算器程序时,可以使用解释器模式来处理表达式。
-
当需要实现一些复杂的规则或策略时,解释器模式也是一种不错的选择。例如,编写一个自然语言处理程序时,可以使用解释器模式来解析和理解语言的语法结构。
代码实现
下面是一个简单的Java解释器模式的例子,用于计算加法表达式的值:
interface Expression {
int interpret();
}
class NumberExpression implements Expression {
private final int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret() {
return number;
}
}
class PlusExpression implements Expression {
private final Expression leftExpression;
private final Expression rightExpression;
public PlusExpression(Expression leftExpression, Expression rightExpression) {
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
@Override
public int interpret() {
return leftExpression.interpret() + rightExpression.interpret();
}
}
class InterpreterDemo {
public static void main(String[] args) {
Expression expression = new PlusExpression(new NumberExpression(1), new NumberExpression(2));
System.out.println(expression.interpret()); // Output: 3
}
}
这里定义了两种表达式:NumberExpression
表示一个数字,PlusExpression
表示加法表达式。PlusExpression
包含左表达式和右表达式,它们可以是数字或其他表达式。interpret
方法用于计算表达式的值。
在实际应用中,解释器模式可能会更加复杂,但这个例子可以帮助我们了解解释器模式的基本思想和实现方式。
使用小结
在Java
中,解释器模式常常应用于编译器、正则表达式、模板引擎、SQL解析器等方面。常用的Java
模板引擎,如FreeMarker和Thymeleaf
等,都是基于解释器模式实现的。
解释器模式的缺点是:它可能会导致类的数量增加,并且在解析复杂的语法时,可能会导致性能问题。因此,在选择解释器模式时,需要考虑这些因素。
迭代器模式(Iterator Pattern)
迭代器模式是一种行为设计模式,它提供一种统一的方法来遍历一个容器中的所有元素,而不用暴露容器的内部结构。
使用迭代器模式可以将遍历容器和容器本身的实现分离开来,从而可以在不影响容器的情况下更改遍历算法。此外,迭代器模式可以简化遍历代码,并提供更加通用的遍历方法。
使用场景
-
需要遍历一个容器中的所有元素,但是不想暴露容器的内部结构。
-
需要提供一个通用的遍历方法,而不需要知道容器的具体实现细节。
-
需要支持多种不同的遍历方式,例如正序、倒序、随机等。
-
需要对容器的遍历算法进行更改,但是不想影响容器本身的实现。
-
需要在遍历过程中同时进行修改操作
代码实现
下面是一个使用Java
迭代器模式的简单例子,以遍历一个名字列表为例:
首先定义一个名字列表接口,包含两个方法:添加名字和获取迭代器。
public interface NameList {
void addName(String name);
Iterator<String> iterator();
}
然后定义一个名字列表实现类,实现添加名字和获取迭代器的方法。
在这个实现类中,定义了一个内部类 NameIterator
,实现了 Iterator
接口中的 hasNext、next 和 remove
方法。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class NameListImpl implements NameList {
private List<String> names = new ArrayList<>();
public void addName(String name) {
names.add(name);
}
public Iterator<String> iterator() {
return new NameIterator();
}
private class NameIterator implements Iterator<String> {
private int index = 0;
public boolean hasNext() {
return index < names.size();
}
public String next() {
return names.get(index++);
}
public void remove() {
names.remove(--index);
}
}
}
下面是一个使用迭代器遍历名字列表的示例代码:
public class IteratorDemo {
public static void main(String[] args) {
NameList nameList = new NameListImpl();
nameList.addName("Alice");
nameList.addName("Bob");
nameList.addName("Charlie");
Iterator<String> iterator = nameList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
输出结果:
Alice
Bob
Charlie
使用小结
在Java
中,迭代器模式已经被广泛应用在集合类中。Java
集合类中都实现了Iterator
接口,提供了一种通用的迭代器实现方式,可以方便地遍历集合中的所有元素。
中介者模式(Mediator Pattern)
中介者模式用于降低多个对象之间的耦合,通过将多个对象之间的通信封装在一个中介者对象中来实现。中介者对象可以独立地管理对象之间的交互,并且可以让这些对象互相通信而不必显示地引用彼此。
使用场景
-
多个对象之间存在复杂的相互关系,但是它们的交互逻辑比较分散和混乱。
-
当一个对象需要和其他多个对象进行交互时,如果每个对象都直接和其他对象进行交互,那么会产生复杂的调用关系,导致代码难以维护和扩展。
-
当一个对象的改变会影响到其他多个对象的状态时,如果每个对象都需要手动处理状态变化,那么也会产生复杂的交互逻辑和调用关系。
代码实现
假设正在开发一个聊天室应用程序,我们可以使用中介者模式来协调多个聊天用户之间的交互。在这个应用程序中,每个聊天用户都可以发送消息给其他用户,而中介者将负责将这些消息传递给其他用户。
具体实现中,我们可以定义一个中介者接口,其中包含一个发送消息的方法。然后我们可以创建一个具体的中介者实现,该实现将负责将消息从一个聊天用户传递到另一个聊天用户。我们还需要创建一个聊天用户接口和具体的聊天用户实现,以便我们可以让多个聊天用户加入到中介者中,并相互交互。
以下是一个简单的 Java 中介者模式示例,其中包含一个中介者接口(ChatMediator
),聊天用户接口(ChatUser
)和具体实现(UserImpl
),以及一个具体的中介者实现(ChatMediatorImpl
):
// 中介者接口
public interface ChatMediator {
void sendMessage(String message, ChatUser user);
}
// 聊天用户接口
public interface ChatUser {
void receiveMessage(String message);
void sendMessage(String message);
}
// 具体的聊天用户实现
public class UserImpl implements ChatUser {
private ChatMediator mediator;
private String name;
public UserImpl(ChatMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
@Override
public void receiveMessage(String message) {
System.out.println(name + " received message: " + message);
}
@Override
public void sendMessage(String message) {
mediator.sendMessage(message, this);
}
}
// 具体的中介者实现
public class ChatMediatorImpl implements ChatMediator {
private List<ChatUser> users;
public ChatMediatorImpl() {
this.users = new ArrayList<>();
}
@Override
public void sendMessage(String message, ChatUser user) {
for (ChatUser u : users) {
if (u != user) {
u.receiveMessage(message);
}
}
}
public void addUser(ChatUser user) {
users.add(user);
}
}
// 测试代码
public class MediatorPatternDemo {
public static void main(String[] args) {
ChatMediator mediator = new ChatMediatorImpl();
ChatUser user1 = new UserImpl(mediator, "Alice");
ChatUser user2 = new UserImpl(mediator, "Bob");
ChatUser user3 = new UserImpl(mediator, "Charlie");
mediator.addUser(user1);
mediator.addUser(user2);
mediator.addUser(user3);
user1.sendMessage("Hi, everyone!");
user2.sendMessage("Hello, Alice.");
user3.sendMessage("What's up, guys?");
}
}
控制台输出:
Bob received message: Hi, everyone!
Charlie received message: Hi, everyone!
Alice received message: Hello, Alice.
Charlie received message: Hello, Alice.
Alice received message: What's up, guys?
Bob received message: What's up, guys?
使用小结
在MVC
框架中,中介者模式被广泛使用,模型和视图之间的通信和控制都可以通过中介者来实现,降低模块之间的耦合度,提高整个系统的可维护性和可扩展性。
中介者模式将复杂的交互转化为中介者对象和其他对象之间的简单交互,从而使系统易于维护和扩展。
备忘录模式(Memento Pattern)
备忘录模式是一种设计模式,它允许在不破坏封装性的情况下保存和恢复对象的状态。这种模式通常用于需要提供撤销或恢复操作的应用程序中。
使用场景
-
需要提供撤销操作的应用程序,例如文本编辑器等。在这种情况下,备忘录模式可以用于保存对象的历史状态,并在需要时恢复对象的状态。
-
需要保存对象状态以进行后续恢复的应用程序。例如,游戏保存和恢复状态。
-
需要在不破坏对象封装性的情况下保存和恢复对象状态的应用程序。
-
需要跟踪和记录对象状态变化的应用程序。例如,监控和记录系统状态变化。
代码实现
假设我们有一个文本编辑器类,需要提供撤销和恢复操作:
import java.util.Stack;
public class TextEditor {
private String text;
private Stack<TextEditorMemento> undoStack;
private Stack<TextEditorMemento> redoStack;
public TextEditor(String text) {
this.text = text;
this.undoStack = new Stack<>();
this.redoStack = new Stack<>();
}
public void setText(String text) {
undoStack.push(new TextEditorMemento(this.text));
this.text = text;
redoStack.clear();
}
public String getText() {
return text;
}
public void undo() {
if (!undoStack.isEmpty()) {
redoStack.push(new TextEditorMemento(this.text));
this.text = undoStack.pop().getText();
}
}
public void redo() {
if (!redoStack.isEmpty()) {
undoStack.push(new TextEditorMemento(this.text));
this.text = redoStack.pop().getText();
}
}
private static class TextEditorMemento {
private final String text;
public TextEditorMemento(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
}
public class Main {
public static void main(String[] args) {
TextEditor editor = new TextEditor("Hello World");
System.out.println(editor.getText()); // output: Hello World
editor.setText("Goodbye World");
System.out.println(editor.getText()); // output: Goodbye World
editor.undo();
System.out.println(editor.getText()); // output: Hello World
editor.redo();
System.out.println(editor.getText()); // output: Goodbye World
}
}
TextEditor
是文本编辑器类,它有一个文本属性text
,并且有两个栈undoStack
和redoStack
,用于保存备忘录。setText()
方法用于设置文本,并将旧的文本备份到undoStack
中。undo()
方法用于撤销操作,从undoStack
中取出上一个备忘录,并将当前文本备份到redoStack
中。redo()
方法用于恢复操作,从redoStack
中取出上一个备忘录,并将当前文本备份到undoStack
中
使用小结
通过使用备忘录模式,我们可以轻松地提供撤销和恢复操作,同时保持其封装性和简洁性。如果需要实现多次撤销和恢复操作,我们只需要保存多个备忘录即可。
转载自:https://juejin.cn/post/7202820042688528445