跳至主要內容

结构型设计模式

red-velet原创计算机基础设计模式约 17784 字大约 59 分钟

结构型设计模式就是利用类与类之间的关系(继承、组合),形成一种类与类之间的结构,通过这种结构提高代码的可拓展性、可维护性和可重用性

结构性设计模式

结构型设计模式:利用类与类之间的关系(继承、组合),形成一种类与类之间的结构,通过这种结构提高代码的可拓展性、可维护性和可重用性。

一、代理设计模式

代理设计模式(Proxy Design Pattern)是一种结构型设计模式,它为其他对象提供一个代理,以控制对这个对象的访问。根据不同时期生成的代理对象,分为:

  • 静态代理:指代理类在编译时就已经确定。
  • 动态代理:指代理类在运行时动态生成。

代理模式可以用于实现懒加载、安全访问控制、日志记录等功能,其核心就是:屏蔽掉对原始对象的直接访问,为原始对象的能力提高增强。

其大致流程如下:

  1. 创建一个接口,定义代理类和被代理类共同实现的方法。
  2. 创建被代理类,实现这个接口,并且在其中定义实现方法。
  3. 创建代理类,也要实现这个接口,同时在其中定义一个被代理类的对象作为成员变量。
  4. 在代理类中实现接口中的方法,方法中调用被代理类中的对应方法。
  5. 通过创建代理对象,并调用其方法,方法增强。 这样,被代理类的方法就会被代理类所覆盖,实现了对被代理类的增强或修改。

1、静态代理

简介:

在静态代理中,需要手动创建代理类和被代理类,并且它们实现相同的接口或继承相同的父类。

基本流程:

  1. 创建一个接口 / 抽象父类 / 父类:定义代理类和被代理类共同实现的方法。
  2. 创建被代理类:实现上述接口,并在其中定义实现方法。
  3. 创建代理类:同样实现上述接口,并在其中定义一个被代理类的对象作为成员变量。
  4. 在代理类中实现接口中的方法:在这些方法中,调用被代理类对象的对应方法。
  5. 通过创建代理对象并调用其方法,实现对被代理类方法的增强或修改。

当涉及到继承关系时,我们可以使用静态代理来实现对继承类的功能增强。

继承实现代理设计模式示例代码:

以下是一个示例代码,演示了如何使用静态代理来实现继承类的功能增强:

首先,我们有一个基础类 BaseClass,它定义了一些基本的操作:

// 基础类
class BaseClass {
    public void performOperation() {
        System.out.println("Performing base operation...");
    }
}

接下来,我们创建一个代理类 ProxyClass,它继承自基础类,并在其方法中添加额外的逻辑:

// 代理类,继承自基础类
class ProxyClass extends BaseClass {
    @Override
    public void performOperation() {
        // 在调用父类方法之前添加额外的逻辑
        System.out.println("Before performing operation...");

        // 调用父类方法
        super.performOperation();

        // 在调用父类方法之后添加额外的逻辑
        System.out.println("After performing operation...");
    }
}

在代理类中,我们重写了基础类的 performOperation() 方法,并在方法中通过调用 super.performOperation() 来执行基础类的功能。同时,在调用父类方法之前和之后,我们添加了额外的逻辑。

最后,我们可以使用代理类来执行操作,并观察功能增强的效果:

public class Main {
    public static void main(String[] args) {
        ProxyClass proxy = new ProxyClass();
        proxy.performOperation();
    }
}

在上述示例中,我们创建了 ProxyClass 的实例,并调用其 performOperation() 方法。在执行该方法时,代理类将在调用父类方法之前和之后添加额外的逻辑。这样,我们就实现了对继承类功能的增强,而不需要修改基础类的代码。

通过静态代理,我们可以在继承关系中对基础类的功能进行增强,而不影响基础类的原有实现。这样,我们可以通过代理类在调用父类方法之前或之后添加额外的逻辑,实现功能的灵活扩展。


接口实现代理设计模式示例代码:

下面是一个使用接口实现静态代理的示例代码:

首先,我们定义一个共同的接口 Image,它包含一个方法 display()

// 共同的接口
interface Image {
    void display();
}

接下来,我们创建一个具体的接口实现类 RealImage,实现了 Image 接口:

// 接口实现类
class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        System.out.println("Displaying image: " + filename);
    }
}

然后,我们创建一个代理类 ImageProxy,它同时实现了 Image 接口,并拥有一个 RealImage 对象作为成员变量:

// 代理类
class ImageProxy implements Image {
    private RealImage realImage;

    public ImageProxy(String filename) {
        this.realImage = new RealImage(filename);
    }

    @Override
    public void display() {
        System.out.println("Loading image: " + realImage.getFilename());
        realImage.display();
    }
}

在代理类中,我们在 display() 方法中先输出加载图片的信息,然后调用 RealImage 对象的 display() 方法来显示图片。

最后,我们可以使用代理类来显示图片,并观察输出结果:

public class Main {
    public static void main(String[] args) {
        Image image = new ImageProxy("example.jpg");
        image.display();
    }
}

在上述示例中,我们创建了 ImageProxy 的实例,并调用其 display() 方法来显示图片。在执行该方法时,代理类会输出加载图片的信息,并通过调用 RealImage 对象的 display() 方法来实际显示图片。

通过使用接口实现静态代理,我们可以在代理类中控制对实现接口的对象的访问,并在调用其方法前后添加额外的逻辑。这样,我们可以对接口实现对象的方法进行增强、修改或限制,以满足特定的需求。

优点和缺点:

优点:

  • 可以在不修改原始代码的情况下,通过代理对象对被代理对象进行功能增强、安全访问控制、日志记录等操作;也可以在代理对象中进行一些额外的操作,如记录日志、缓存等,以增强被代理对象的功能。
  • 代理对象可以隐藏被代理对象的具体实现,实现了客户端和被代理对象的解耦。

缺点:

  • 静态代理在编译时就已经确定代理类,后续维护可能修改源代码
  • 每个被代理类都需要手动创建一个代理类,当代理类较多或变动频繁时,会增加代码量和维护成本。

**使用场景:**下列是使用chatgpt学习中回答的使用场景和代码示例

  1. 访问控制和安全性:静态代理可以用于控制对被代理对象的访问权限,确保只有具有合适权限的客户端可以访问被代理对象。
  2. 日志记录:静态代理可以用于记录对被代理对象的操作日志,方便后续的分析和监控。
  3. 性能监控:静态代理可以用于监控被代理对象的性能,统计方法的执行时间、调用次数等指标。
  4. 缓存:静态代理可以用于实现对被代理对象的结果进行缓存,提高系统响应速度。
  5. 事务管理:静态代理可以用于实现对被代理对象的事务管理,保证操作的原子性和一致性。
  6. 远程代理:静态代理可以用于实现远程对象的访问,隐藏底层的网络通信细节。

缓存代理示例代码:

示例-缓存代理当涉及到数据库查询时,可以使用静态代理来实现查询缓存的功能。下面是一个简单的示例代码,演示了如何使用静态代理来实现数据库查询缓存的功能:

首先,我们需要定义一个共同的接口,代表数据库操作:

// 定义数据库操作的接口
interface Database {
    String queryData(String query);
}

然后,我们创建一个具体的数据库操作类,实现上述接口,用于执行实际的数据库查询:

// 实现数据库操作的具体类
class DatabaseImpl implements Database {
    @Override
    public String queryData(String query) {
        // 模拟执行数据库查询
        System.out.println("Executing database query: " + query);

        // 返回查询结果
        return "Result for query: " + query;
    }
}

接下来,我们创建一个代理类,用于添加查询缓存的逻辑:

// 创建代理类,添加查询缓存的逻辑
class DatabaseProxy implements Database {
    private Database database;
    private Map<String, String> cache; // 查询缓存

    public DatabaseProxy() {
        this.database = new DatabaseImpl();
        this.cache = new HashMap<>();
    }

    @Override
    public String queryData(String query) {
        // 先检查缓存中是否存在查询结果
        if (cache.containsKey(query)) {
            System.out.println("Retrieving cached result for query: " + query);
            return cache.get(query);
        }

        // 如果缓存中不存在查询结果,则执行实际的数据库查询
        String result = database.queryData(query);

        // 将查询结果存入缓存
        cache.put(query, result);

        return result;
    }
}

在代理类中,我们在queryData()方法中先检查缓存中是否存在查询结果。如果存在,直接从缓存中返回结果;如果不存在,代理类会调用实际的数据库操作类执行查询,并将查询结果存入缓存中。

最后,我们可以使用代理类来执行数据库查询,并观察缓存的效果:

public class Main {
    public static void main(String[] args) {
        Database database = new DatabaseProxy();

        // 第一次执行查询,将结果存入缓存
        String result1 = database.queryData("SELECT * FROM table1");
        System.out.println("Result 1: " + result1);

        // 第二次执行相同的查询,从缓存中获取结果
        String result2 = database.queryData("SELECT * FROM table1");
        System.out.println("Result 2: " + result2);
    }
}

在上述示例中,第一次执行查询时,会调用实际的数据库操作类执行查询,并将结果存入缓存。第二次执行相同的查询时,直接从缓存中获取结果,而不会再次执行数据库查询。

缓存代理-示例-输出结果展示
缓存代理-示例-输出结果展示

通过静态代理,我们实现了数据库查询缓存的功能,可以提高查询性能,减少对数据库的访问。这样,在相同的查询被频繁执行时,可以直接从缓存中获取结果,避免了重复的数据库查询操作。

安全代理示例代码:

示例-安全代理当涉及到安全性验证时,可以使用静态代理来实现安全代理的功能。下面是一个简单的示例代码,演示了如何使用静态代理来实现安全代理:

首先,我们需要定义一个共同的接口,代表敏感操作:

// 定义敏感操作的接口
interface SensitiveOperation {
    void performOperation();
}

然后,我们创建一个具体的敏感操作类,实现上述接口,用于执行实际的敏感操作:

// 实现敏感操作的具体类
class SensitiveOperationImpl implements SensitiveOperation {
    @Override
    public void performOperation() {
        System.out.println("Performing sensitive operation...");
    }
}

接下来,我们创建一个代理类,用于添加安全验证的逻辑:

// 创建代理类,添加安全验证的逻辑
class SecurityProxy implements SensitiveOperation {
    private SensitiveOperation sensitiveOperation;
    private String password; // 安全验证密码

    public SecurityProxy(String password) {
        this.sensitiveOperation = new SensitiveOperationImpl();
        this.password = password;
    }

    @Override
    public void performOperation() {
        // 进行安全验证
        if (authenticate()) {
            sensitiveOperation.performOperation();
        } else {
            System.out.println("Access denied! Invalid password.");
        }
    }

    private boolean authenticate() {
        // 进行安全验证的逻辑,比较输入密码和预设密码是否匹配
        String inputPassword = getPasswordFromUser();
        return inputPassword.equals(password);
    }

    private String getPasswordFromUser() {
        // 模拟从用户输入获取密码的逻辑
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter password: ");
        return scanner.nextLine();
    }
}

在代理类中,我们在performOperation()方法中进行安全验证。首先,用户需要输入密码进行验证;如果验证通过,则调用实际的敏感操作类执行敏感操作;如果验证失败,则拒绝访问。

最后,我们可以使用代理类来执行敏感操作,并观察安全验证的效果:

public class Main {
    public static void main(String[] args) {
        String password = "password123"; // 设置安全验证密码
        SensitiveOperation operation = new SecurityProxy(password);

        // 执行敏感操作,需要通过密码验证
        operation.performOperation();
    }
}

在上述示例中,执行敏感操作时,用户需要输入密码进行安全验证。只有当输入的密码与预设密码匹配时,才能执行实际的敏感操作。否则,将拒绝访问。

通过静态代理,我们实现了安全代理的功能,可以在执行敏感操作前进行安全验证,保护敏感操作的安全性。这样,在需要对敏感操作进行访问控制和验证的场景下,可以使用安全代理来确保只有经过验证的用户才能执行敏感操作。

远程代理示例代码:

示例-远程代理 当涉及到远程对象的访问时,可以使用静态代理来实现远程代理的功能。下面是一个简单的示例代码,演示了如何使用静态代理来实现远程代理:

首先,我们需要定义一个共同的接口,代表远程服务:

// 定义远程服务的接口
interface RemoteService {
    void performTask();
}

然后,我们创建一个具体的远程服务类,实现上述接口,用于执行实际的远程任务:

// 实现远程服务的具体类
class RemoteServiceImpl implements RemoteService {
    @Override
    public void performTask() {
        System.out.println("Performing remote task...");
    }
}

接下来,我们创建一个代理类,用于封装远程通信的逻辑:

// 创建代理类,封装远程通信的逻辑
class RemoteProxy implements RemoteService {
    private RemoteService remoteService;

    public RemoteProxy() {
        // 在代理类中创建远程服务对象
        this.remoteService = new RemoteServiceImpl();
    }

    @Override
    public void performTask() {
        // 在代理类中添加远程通信的逻辑,模拟网络请求
        System.out.println("Sending request to remote server...");

        // 调用远程服务对象的方法
        remoteService.performTask();

        // 在代理类中添加远程通信的逻辑,模拟网络响应
        System.out.println("Received response from remote server...");
    }
}

在代理类中,我们在performTask()方法中添加远程通信的逻辑,模拟网络请求和响应的过程。首先,发送请求到远程服务器;然后,调用远程服务对象的方法执行远程任务;最后,接收远程服务器的响应。

最后,我们可以使用代理类来执行远程任务,并观察远程通信的效果:

public class Main {
    public static void main(String[] args) {
        RemoteService remoteService = new RemoteProxy();

        // 执行远程任务
        remoteService.performTask();
    }
}

在上述示例中,执行远程任务时,代理类将负责封装远程通信的逻辑。在调用远程服务对象的方法之前和之后,代理类会进行网络请求和响应的模拟操作。

通过静态代理,我们实现了远程代理的功能,可以封装远程通信的逻辑,隐藏底层的网络细节。这样,在需要访问远程对象时,可以使用远程代理来进行网络请求和响应的处理,简化了远程通信的操作。

2、动态代理

简介:

动态代理(Dynamic Proxy)是一种在运行时动态生成代理类的设计模式。与静态代理不同,动态代理不需要手动编写代理类,而是通过Java的反射机制在运行时动态生成代理类,从而实现对被代理对象的代理

  • 基于JDK实现的动态代理,基于接口实现。
  • 基于CGLIB使用的动态代理,基于继承实现。

基本流程:

  1. 定义一个接口,该接口是被代理类和代理类共同实现的接口。
  2. 创建一个实现了InvocationHandler接口的代理处理器类,该类中包含对方法的增强逻辑。
  3. 使用Proxy类的静态方法newProxyInstance()来创建代理对象,该方法接收三个参数:类加载器、被代理类实现的接口数组、代理处理器对象。
  4. 通过代理对象调用方法,代理处理器中的invoke()方法会被触发,并执行相应的增强逻辑。

优点和缺点:

优点:

  • 动态代理可以在运行时动态地创建代理对象,适用于不同的接口和被代理类。
  • 它允许在不修改现有代码的情况下,对方法进行统一的增强或拦截,比如性能监控、事务管理、缓存等。
  • 动态代理可以减少代码量,避免手动编写大量的代理类。

缺点:

  • 动态代理的性能相对较低,因为在运行时需要使用反射机制来生成代理类和调用方法。
  • 动态代理只能代理接口,无法代理具体类,因为Java的单继承限制。

使用场景:

  • 动态代理常用于AOP(面向切面编程)领域,可以通过动态代理在运行时动态地为目标对象添加横切逻辑,如日志记录、事务管理等。
  • 动态代理还可以用于远程方法调用(RMI)、缓存代理、延迟加载等场景,以实现更灵活、可扩展的系统架构。

基于 JDK 实现的动态代理

这个例子是基于 JDK 实现的动态代理示例。它演示了如何使用动态代理在运行时为目标对象添加额外的逻辑处理,而无需修改目标对象的代码:

/**
 * Author: shawn
 * Description: 定义一个共同的接口,代表数据库操作
 */
public interface DataBase {
    /**
     * 执行数据库查询操作
     *
     * @param query 查询语句
     * @return 查询结果
     */
    String query(String query);

    /**
     * 执行数据库删除操作
     *
     * @param delete 删除语句
     * @return 删除结果
     */
    String delete(String delete);
}

/**
 * Author: shawn
 * Description: 数据库操作的具体实现类
 */
public class DataBaseImpl implements DataBase {
    @Override
    public String query(String query) {
        // 模拟执行数据库查询
        System.out.println("Executing database query: " + query);

        // 返回查询结果
        return "Result for query: " + query;
    }

    @Override
    public String delete(String delete) {
        // 模拟执行数据库删除
        System.out.println("Executing database delete: " + delete);

        // 返回删除结果
        return "Result for delete: " + delete;
    }
}

/**
 * Author: shawn
 * Description: 该代理处理器类负责处理代理对象的方法调用,并在方法调用前后执行额外的逻辑操作。
 */
public class DatabaseInvocationHandler implements InvocationHandler {
    private Object target;

    public DatabaseInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * invoke()方法用于处理代理对象的方法调用。
     *
     * @param proxy  代理对象本身
     * @param method 被调用的方法对象
     * @param args   方法的参数数组
     * @return 方法的返回结果
     * @throws Throwable 异常信息
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法调用前的逻辑处理
        // 如:判断方法名,查看是否需要做记录日志
        String methodName = method.getName();
        if ("delete".equals(methodName)) {
            //是删除操作才做增强,不然还是调用原方法
            System.out.println("Recording deletion operation log");
        }
        System.out.println("Before method: " + methodName);
        System.out.println("Arguments: " + args[0]);

        // 调用被代理对象的方法
        Object result = method.invoke(target, args);

        // 在方法调用后的逻辑处理
        System.out.println("Result: " + result);
        System.out.println("After method: " + method);

        // 返回方法的返回结果
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建目标对象
        DataBase target = new DataBaseImpl();

        // 创建代理处理器
        DatabaseInvocationHandler handler = new DatabaseInvocationHandler();

        // 创建代理对象
        DataBase proxy = (DataBase) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler
        );

        // 调用代理对象的方法
        String queryResult = proxy.query("SELECT * FROM table");
        System.out.println("------------------------");
        String deleteResult = proxy.delete("DELETE FROM table WHERE id = 1");
        System.out.println("------------------------");


        // 输出方法的返回结果
        System.out.println("Query Result: " + queryResult);
        System.out.println("------------------------");
        System.out.println("Delete Result: " + deleteResult);
    }
}

在上面的代码中,我们使用了动态代理来为 DataBase 接口生成了一个代理类,并在代理类中增加了日志记录的功能。

具体地说,在 DatabaseInvocationHandler 类中的 invoke 方法中,我们根据方法名进行判断,只有当方法名是 "delete" 时才会进行日志记录。这样,在调用代理对象的 delete 方法时,会先输出 "Recording deletion operation log" 的提示信息,表示进行了删除操作的日志记录。

其他方法(比如 query 方法)没有满足条件,所以不会进行日志记录,只会输出方法调用前的提示信息和参数信息。

通过这种方式,我们实现了对原有接口的增强,根据不同的方法名来决定是否进行日志记录,从而实现了按需添加日志记录功能的动态代理类。这样的设计使得我们可以在不修改原有接口和实现类的情况下,为特定方法或特定场景添加额外的功能。

【下列是运行输出】:

Before method: query
Arguments: SELECT * FROM table
Executing database query: SELECT * FROM table
Result: Result for query: SELECT * FROM table
After method: public abstract java.lang.String structuralDesignPattern.proxy.dynamicProxy.DataBase.query(java.lang.String)
------------------------
Recording deletion operation log
Before method: delete
Arguments: DELETE FROM table WHERE id = 1
Executing database delete: DELETE FROM table WHERE id = 1
Result: Result for delete: DELETE FROM table WHERE id = 1
After method: public abstract java.lang.String structuralDesignPattern.proxy.dynamicProxy.DataBase.delete(java.lang.String)
------------------------
Query Result: Result for query: SELECT * FROM table
------------------------
Delete Result: Result for delete: DELETE FROM table WHERE id = 1

基于 CGLIB 实现的动态代理

这是一个基于CGLIB实现的动态代理示例。下面是代码的解析:

public class DataBaseImpl {
    public String query(String query) {
        // 模拟执行数据库查询
        System.out.println("Executing database query: " + query);

        // 返回查询结果
        return "Result for query: " + query;
    }

    public String delete(String delete) {
        // 模拟执行数据库删除
        System.out.println("Executing database delete: " + delete);

        // 返回删除结果
        return "Result for delete: " + delete;
    }
}

上述代码定义了一个数据库操作的具体实现类 DataBaseImpl,包含了 querydelete 两个方法。

public class DatabaseMethodInterceptor implements MethodInterceptor {
    private DataBaseImpl dataBase;

    public DatabaseMethodInterceptor() {
        this.dataBase = new DataBaseImpl();
    }

    public DatabaseMethodInterceptor(DataBaseImpl dataBase) {
        this.dataBase = dataBase;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 在方法调用前的逻辑处理
        // 如:判断方法名,查看是否需要做记录日志
        String methodName = method.getName();
        if ("delete".equals(methodName)) {
            //是删除操作才做增强,不然还是调用原方法
            System.out.println("Recording deletion operation log");
        }
        System.out.println("Before method: " + methodName);
        System.out.println("Arguments: " + objects[0]);

        // 调用被代理对象的方法
        Object result = method.invoke(dataBase, objects);

        // 在方法调用后的逻辑处理
        System.out.println("Result: " + result);
        System.out.println("After method: " + method);

        // 返回方法的返回结果
        return result;
    }
}

上述代码是基于CGLIB的方法拦截器 DatabaseMethodInterceptor,实现了 MethodInterceptor 接口。拦截器中的 intercept 方法用于在方法调用前后进行逻辑处理,包括记录日志和调用被代理对象的方法。

public class Main {
    public static void main(String[] args) {
        //cglib通过enhancer
        Enhancer enhancer = new Enhancer();
        //设置他的父类-要继承谁,给谁做代理
        enhancer.setSuperclass(DataBaseImpl.class);
        //设置方法拦截器,用于拦截方法,对方法做增强
        enhancer.setCallback(new DatabaseMethodInterceptor());
        // 创建代理对象
        DataBaseImpl proxy = (DataBaseImpl) enhancer.create();
        // 调用代理对象的方法
        String queryResult = proxy.query("SELECT * FROM table");
        System.out.println("--------cglib----------------");
        String deleteResult = proxy.delete("DELETE FROM table WHERE id = 1");
        System.out.println("--------cglib----------------");

        // 输出方法的返回结果
        System.out.println("Query Result: " + queryResult);
        System.out.println("--------cglib----------------");
        System.out.println("Delete Result: " + deleteResult);
    }
}

上述代码是 Main 类,包含了代理对象的创建和方法调用的示例。使用 Enhancer 创建代理对象,并设置父类和方法拦截器,最终通过 create 方法创建代理对象。然后,通过代理对象调用方法。

在运行该示例代码时,会输出方法调用的前后日志和结果。

请注意,为了使该示例代码正常运行,你需要在项目的依赖中添加 CGLIB 的相关依赖,或者将项目修改为spring boot项目。你可以将以下依赖添加到你的 pom.xml 文件中:

在运行代码时,你将看到方法调用的输出结果和日志记录,以及代理对象的增强效果。

Before method: query
Arguments: SELECT * FROM table
Executing database query: SELECT * FROM table
Result: Result for query: SELECT * FROM table
After method: public java.lang.String structuralDesignPattern.proxy.dynamicProxy.cglib.DataBaseImpl.query(java.lang.String)
--------cglib----------------
Recording deletion operation log
Before method: delete
Arguments: DELETE FROM table WHERE id = 1
Executing database delete: DELETE FROM table WHERE id = 1
Result: Result for delete: DELETE FROM table WHERE id = 1
After method: public java.lang.String structuralDesignPattern.proxy.dynamicProxy.cglib.DataBaseImpl.delete(java.lang.String)
--------cglib----------------
Query Result: Result for query: SELECT * FROM table
--------cglib----------------
Delete Result: Result for delete: DELETE FROM table WHERE id = 1

二、装饰器设计模式

1、简介

装饰器设计模式(Decorator Design Pattern)是一种结构型设计模式,它允许你在不改变已有对象的情况下,使用组合替代继承对某些原生对象的方法做增强,添加新的能力

  • 代理设计模式:主要倾向于控制对对象的访问
    • 在代理设计模式中,代理类附加的是跟原始类无关的功能
      • 代理会屏蔽原始对象的访问 (即增加缓存功能),而并不是
      • 如我们要给所有的service层添加日志/事务日志/事务和我们的业务不强相关,代理类附加的是跟原始类无关的功能
  • 装饰器设计模式:主要倾向于增强对象的功能
    • 在装饰器模式(增强对象的功能)中,装饰器类附加的是跟原始类相关的增强功能
      • 装饰器不会屏蔽原始对象的访问,既可以访问原始对象,还能访问包装后的对象
      • 如我们有一个缓存结构,这个结构功能不够强大,我们需要给这个结构包装一下增加一个淘汰策略,再包装一下添加一个刷新机制,对原始功能并不够强大的能力让他变得更加强大,装饰器类附加的是跟原始类相关的增强功能

案例:为什么不用继承实现呢 ?

IO库提供了一组用于处理输入和输出的类和接口,例如InputStreamOutputStreamReaderWriter等。这些类和接口通过组合关系,可以以各种方式组合和包装,实现各种输入和输出操作。

InputStream有很多子类,如ByteInputStream、FileInputStream等,如我们想要给FileInputStream添加一个新的能力时,如添加缓存的功能,我们可以通过继承的方式实现,创建一个类继承自FileInputStream再对其做增强,但是假如我们要给每一个InputStream的的子类做增强呢,InputStream那么多子类,那我们要为每一个子类创建类进行继承,会让类结构爆炸,所以它是通过装饰器设计模式来解决的,里面维护一个原生对象,通过组合关系和包装,对原生对象的某些方法做增强,这样我们只需要为新的功能创建装饰器类,而不需要修改现有的类继承结构,使得代码更加灵活、可扩展和可维护。

优点:

  1. 可以在不修改现有代码的情况下扩展对象的功能。
  2. 允许多个装饰器嵌套使用,以实现更复杂的功能组合。
  3. 遵循开闭原则,即对扩展开放,对修改关闭。

2、基本流程

​ 该模式通过创建一个包装器(装饰器),将对象放入该包装器中,并在保持对象接口不变的情况下,增加了额外的功能或责任。装饰器包装了原始对象,从而允许你在执行原始对象的操作之前或之后添加自定义的行为。

  1. 抽象组件(Component):定义了装饰器和具体组件的共同接口,可以是抽象类或接口。
  2. 具体组件(Concrete Component):实现了抽象组件接口,即被装饰的原始对象。
  3. 抽象装饰器(Decorator):继承了抽象组件接口,并持有一个抽象组件对象的引用,可以是抽象类或接口。
  4. 具体装饰器(Concrete Decorator):继承了抽象装饰器,实现了额外的功能,并调用被装饰对象的方法。

3、案例

下面是一个简单的示例代码,展示了装饰器模式的应用:

public interface Component {
    void operation();
}

这是一个接口Component,定义了一个operation方法。

public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("这是具体的对象");
    }
}

ConcreteComponent是一个实现了Component接口的具体类。它实现了operation方法,并打印出"这是具体的对象"。

public abstract class Decorator implements Component {
    private Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();
    }
}

Decorator是一个抽象类,实现了Component接口。它包含一个Component类型的私有成员变量component,并通过构造器接收一个Component对象进行初始化。它还实现了operation方法,调用了component对象的operation方法。

public class DecoratorA extends Decorator {
    public DecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        System.out.println("包装了一下,添加了背景颜色");
        super.operation();
    }
}

DecoratorA是一个继承自Decorator的具体装饰类。它接收一个Component对象,并通过构造器调用父类的构造器进行初始化。它重写了operation方法,在打印"包装了一下,添加了背景颜色"之后,再调用父类的operation方法。

public class DecoratorB extends Decorator {
    public DecoratorB(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        System.out.println("包装了一下,打了个合格标签");
        super.operation();
    }
}

DecoratorB是另一个继承自Decorator的具体装饰类。它也接收一个Component对象,并通过构造器调用父类的构造器进行初始化。它同样重写了operation方法,在打印"包装了一下,打了个合格标签"之后,再调用父类的operation方法。

public class Main {
    public static void main(String[] args) {
        // 创建一个原始对象
        Component component = new ConcreteComponent();
        // 没有包装,调用原始方法
        component.operation();
        System.out.println("----------分-----------界------------线--------------");
        // 同A包装一下
        DecoratorA decoratorA = new DecoratorA(component);
        decoratorA.operation();
        System.out.println("----------分-----------界------------线--------------");
        // 同B再包装一层
        DecoratorB decoratorB = new DecoratorB(decoratorA);
        decoratorB.operation();
        System.out.println("----------分-----------界------------线--------------");
        // 再次调用原有对象,并没有发生改变
        component.operation();
    }
    //更加直观
    public static void main(String[] args) {
        //创建一个原始对象
        Component component = new ConcreteComponent();
        //没有包装,调用原始方法
        component.operation();
        System.out.println("----------分-----------界------------线--------------");
        //同A包装一下
        Decorator decorator = new DecoratorA(component);
        decorator.operation();
        System.out.println("----------分-----------界------------线--------------");
        //同B再包装一层
        decorator = new DecoratorB(decorator);
        decorator.operation();
        System.out.println("----------分-----------界------------线--------------");
        //再次调用原有对象,并没有发生改变
        component.operation();
    }
}

Main类的main方法中,首先创建了一个ConcreteComponent对象作为原始对象。然后,通过component.operation()调用原始对象的operation方法,打印出"这是具体的对象"。

接下来,创建了一个DecoratorA对象decoratorA,并将原始对象component作为参数传递给它的构造器。通过decoratorA.operation()调用DecoratorA对象的operation方法。在打印"包装了一下,添加了背景颜色"之后,它调用父类Decoratoroperation方法,这会再次调用原始对象的operation方法。

然后,创建了一个DecoratorB对象decoratorB,并将decoratorA对象作为参数传递给它的构造器。通过decoratorB.operation()调用DecoratorB对象的operation方法。在打印"包装了一下,打了个合格标签"之后,它同样调用父类Decoratoroperation方法,这会再次调用原始对象的operation方法。

最后,再次调用原始对象componentoperation方法,输出结果仍然是"这是具体的对象",即原始对象并没有发生改变。

这是具体的对象
----------分-----------界------------线--------------
包装了一下,添加了背景颜色
这是具体的对象
----------分-----------界------------线--------------
包装了一下,打了个合格标签
包装了一下,添加了背景颜色
这是具体的对象
----------分-----------界------------线--------------
这是具体的对象

总结起来,装饰器模式允许通过包装对象来动态地为对象添加额外的行为。抽象类Decorator作为装饰器的基类,通过组合和继承的方式实现了装饰功能。在示例中,DecoratorADecoratorB是具体的装饰器类,它们分别在原始对象的行为前后添加了特定的功能。通过层层装饰,可以灵活地组合各种装饰器,实现对对象功能的动态扩展。

三、桥接设计模式

1、简介

桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象和实现分离,使它们可以独立地变化

  • 桥接模式的核心思想通过组合关系而不是继承关系来连接抽象和实现
  • 核心使用场景:当需要抽象和实现,一对多、多对多的场景。

在桥接模式中,抽象部分和实现部分分别由两个独立的类层次结构组成。抽象部分包含高层的抽象接口和方法,实现部分包含低层的具体实现。通过桥接模式,抽象部分和实现部分可以独立地扩展和变化,而不会相互影响。

以下是桥接模式的几个关键角色:

  1. 抽象部分(Abstraction):定义了抽象接口,并包含一个指向实现部分的引用。

  2. 扩展的抽象部分(Refined Abstraction):对抽象部分进行扩展,提供更多的功能或特性。

  3. 实现部分(Implementor):定义了实现部分的接口,供抽象部分调用。

  4. 具体实现部分(Concrete Implementor):实现实现部分的具体逻辑。

通过桥接模式,我们可以通过修改或添加新的抽象部分或实现部分,来实现更灵活的变化和扩展。它可以避免类爆炸问题,提高系统的可维护性和可扩展性。

2、基本流程

桥接模式是一种结构型设计模式,它的主要目的是将抽象部分与实现部分分离,使它们可以独立地变化。下面是桥接模式的基本流程:

  1. 定义抽象部分接口(Abstraction):抽象部分是桥接模式的核心,它定义了抽象部分的接口和功能。通常,抽象部分接口中会包含对实现部分对象的引用,以及一些抽象的操作方法。

  2. 定义实现部分接口(Implementor):实现部分接口定义了实现部分的接口和功能。实现部分接口通常是与抽象部分接口相对应的,但可以独立地变化。

  3. 创建具体抽象部分类(RefinedAbstraction):具体抽象部分类是抽象部分接口的具体实现,它扩展了抽象部分接口,并实现了相关的操作方法。具体抽象部分类通常会持有一个实现部分对象的引用。

  4. 创建具体实现部分类(ConcreteImplementor):具体实现部分类是实现部分接口的具体实现,它实现了实现部分接口定义的功能。

  5. 在具体抽象部分类中使用实现部分对象:具体抽象部分类通过调用实现部分对象的方法来实现自己的操作。抽象部分和实现部分通过组合关系连接在一起,而不是继承关系。

通过桥接模式,抽象部分和实现部分可以独立地变化,它们的变化不会相互影响。这样可以使系统更加灵活,方便扩展和维护。

3、案例

假如我们正在开发一个电子商务系统,该系统涉及不同类型的商品(如服装、电子产品、家居用品等),并且可以在不同的销售渠道上进行销售(如在线商店、移动应用、实体店等)。为了处理商品和销售渠道之间的关系,我们可以使用桥接设计模式。

首先,我们定义了一个名为 Product 的接口,其中包含一些通用的商品属性和方法。然后,我们创建了具体的商品类,例如 ClothingProduct(服装商品)、ElectronicsProduct(电子产品商品)和 HomeProduct(家居用品商品)。这些具体商品类都实现了 Product 接口,并实现了 sell() 方法。

接下来,我们定义了一个名为 SalesChannel 的接口,其中包含一些通用的销售渠道属性和方法,例如渠道名称、销售方式等。然后,我们创建了多个具体的销售渠道类,例如 OnlineStoreChannel(在线商店渠道)、MobileAppChannel(移动应用渠道)和 PhysicalStoreChannel(实体店渠道)。这些具体销售渠道类都继承自 SalesChannel 接口,并提供了各自特定的属性和方法。

现在,通过桥接模式,我们将商品和销售渠道分离开来,使它们可以独立地变化。我们创建了一个桥接类 ProductSales,该类包含一个指向商品和销售渠道的引用,并提供一个方法来在特定销售渠道上销售商品。这样,我们可以根据需要组合不同的商品和销售渠道,实现灵活的销售策略。

通过桥接设计模式,我们可以实现商品和销售渠道之间的解耦,并且可以方便地扩展和修改系统,以适应不同的需求和变化。

// 商品接口
interface Product {
    void sell(SalesChannel channel);
}

// 具体商品类
class ClothingProduct implements Product {
    private String name;
    private double price;

    public ClothingProduct(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public void sell(SalesChannel channel) {
        System.out.println("Selling " + name + " clothing product on " + channel.getName());
        // 实现具体的销售逻辑
    }
}
// 具体商品类
class DigitalProduct implements Product {
    private String name;
    private double price;

    public DigitalProduct(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public void sell(SalesChannel channel) {
        System.out.println("Selling " + name + " digital product on " + channel.getName());
        // 实现具体的销售逻辑
    }
}

// 销售渠道接口
interface SalesChannel {
    String getName();
}

// 具体销售渠道类
class OnlineStoreChannel implements SalesChannel {
    public String getName() {
        return "Online Store";
    }
}

class MobileAppChannel implements SalesChannel {
    public String getName() {
        return "Mobile App";
    }
}

class PhysicalStoreChannel implements SalesChannel {
    public String getName() {
        return "Physical Store";
    }
}

// 桥接类
class ProductSales {
    private Product product;
    private SalesChannel channel;

    public ProductSales(Product product, SalesChannel channel) {
        this.product = product;
        this.channel = channel;
    }

    public void sell() {
        product.sell(channel);
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        Product clothingProduct = new ClothingProduct("T-Shirt", 29.99);
        SalesChannel onlineStoreChannel = new OnlineStoreChannel();

        ProductSales productSales = new ProductSales(clothingProduct, onlineStoreChannel);
        productSales.sell();
    }
}

通过使用桥接模式,我们可以轻松地添加新的商品类型和销售渠道,而不需要修改现有的类。例如,我们可以创建新的 ElectronicsProduct 类和 MobileAppChannel 类,并根据需要进行组合。这样,我们可以实现不同商品在不同销售渠道上的灵活销售。同时,它也提供了更高的代码可读性和可维护性,使得系统更加灵活和易于扩展。

四、适配器设计模式

1、简介

适配器设计模式(Adapter Design Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户端所期望的另一个接口,主要用于解决不兼容接口之间的问题,使得原本由于接口不匹配而无法合作的类可以一起工作。

适配器模式涉及三个主要角色:

  1. 目标(Target):目标是客户端所期望的接口。它定义了客户端代码可以使用的方法。
  2. 适配器(Adapter):适配器是将原本不兼容的接口转换成目标接口的类。它实现了目标接口,并通过包装或转换来适应被适配者。
  3. 被适配者(Adaptee):被适配者是需要被适配的类或接口。它定义了适配器需要适配的方法。

2、基本流程

适配器模式的工作原理如下:

  1. 客户端通过调用目标接口中的方法来使用适配器。
  2. 适配器接收客户端的请求,并将其转发给被适配者。
  3. 被适配者执行相应的操作,并将结果返回给适配器。
  4. 适配器将结果转换为客户端所期望的格式,并将其返回给客户端。

3、使用场景和案例

适配器模式在软件开发中经常用于以下场景:

  • 将旧的接口适配成新的接口,以便与新的系统集成。
  • 在使用第三方库或组件时,适配器可以帮助将其接口转换为符合项目需求的接口。
  • 在系统演进中,适配器模式可以用于平滑迁移旧的接口到新的接口。

总结起来,适配器设计模式允许不兼容的接口之间进行协同工作,提供了一种解决接口不匹配问题的灵活方案。

将旧的接口迁移成新的接口:

当面对新老支付接口替换的场景时,适配器设计模式可以帮助我们平滑过渡并确保系统的兼容性。假设我们有一个电商平台,目前正在使用老的支付接口2022进行支付操作。由于接口2022已过时或者有其他限制,我们需要将支付接口替换为新的支付接口2023。下面是一个示例代码,演示如何使用适配器设计模式来实现支付接口的替换:

// 老的支付接口
public interface Payment2022Interface {
    void processPayment(double amount);
    // ...
}

// 老的支付接口实现
public class Payment2022Implementation implements Payment2022Interface {
    @Override
    public void processPayment(double amount) {
        // 使用老的支付接口进行支付
        // ...
        System.out.println("使用老的支付接口支付的: " + amount + "元");
    }
}

// 新的支付接口
public interface PaymentInterfaceB {
    void initiatePayment(double amount);
    // ...
}

// 新的支付接口实现
public class Payment2023Implementation implements Payment2023Interface {
    @Override
    public void initiatePayment(double amount) {
        // 使用新的支付接口进行支付
        // ...
        System.out.println("使用新的支付接口支付的: " + amount + "元");
    }
}

// 适配器类,将新的支付接口适配到老的支付接口上
public class PaymentAdapter implements Payment2022Interface {
    private Payment2023Interface payment2023;

    public PaymentAdapter(Payment2023Interface payment2023) {
        this.payment2023 = payment2023;
    }

    @Override
    public void processPayment(double amount) {
        // 将老的支付接口2022的调用转换为新的支付接口2023的调用
        payment2023.initiatePayment(amount);
    }
}

// 电商平台订单类
public class Order {
    private final double amount;
    private final Payment2022Interface payment2022;

    public Order(double amount, Payment2022Interface payment2022) {
        this.amount = amount;
        this.payment2022 = payment2022;
    }

    public void processOrderPayment() {
        // 调用支付接口进行支付
        payment2022.processPayment(amount);
    }
    // ...
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        // 创建新的支付接口实现
        Payment2023Interface payment2023 = new Payment2023Implementation();

        // 创建适配器,将新的支付接口适配到老的支付接口上
        Payment2022Interface payment2022 = new PaymentAdapter(payment2023);

        // 创建订单,并传入适配器
        Order order = new Order(100.0, payment2022);

        // 处理订单支付
        //表面调用的是老的支付接口,但是内部实现确实调用新的支付接口
        order.processOrderPayment();
    }
}

运行输出:

使用新的支付接口支付的: 100.0

以上代码展示了一个适配器模式的示例,用于将新的支付接口适配到旧的支付接口上。

在示例中,有以下关键部分:

  1. Payment2022Interface 是旧的支付接口,定义了 processPayment 方法用于处理支付操作。
  2. Payment2022Implementation 是旧的支付接口的实现类,实现了 Payment2022Interface 接口的方法。
  3. PaymentInterfaceB 是新的支付接口,定义了 initiatePayment 方法用于发起支付操作。
  4. Payment2023Implementation 是新的支付接口的实现类,实现了 PaymentInterfaceB 接口的方法。
  5. PaymentAdapter 是适配器类,实现了旧的支付接口 Payment2022Interface,并在内部使用新的支付接口 Payment2023Interface 进行支付操作的适配。
  6. Order 是电商平台的订单类,接收一个支付接口对象,在 processOrderPayment 方法中调用支付接口的支付方法。
  7. Main 是使用示例,创建了新的支付接口实现对象 Payment2023Implementation,然后使用适配器 PaymentAdapter 将新的支付接口适配到旧的支付接口上,并将适配后的接口对象传递给订单对象进行支付处理。

在示例中,通过适配器模式,可以在保留原有代码和接口的情况下,使用新的支付接口实现进行支付操作。即使订单类中使用的是旧的支付接口对象,实际上在内部调用的是新的支付接口实现的方法。这样就实现了旧接口和新接口的兼容性和平滑过渡。

4、实际案例

如在我的项目经历中有这样一条描述:

  • 整合银行不同接口发来的不同格式的报文,使用适配器设计模式进行适配,统一转换为我司可用的短信数据格式。

接收第三方发送的报文:

@WebService
public class SmsService {
    @WebMethod
    public void receiveSms(@WebParam(name = "message") String message, @WebParam(name = "format") String format) {
        //根据类型获取对应获取适配器
        SmsAdapter smsAdapter = SmsAdapterFactory.createAdapter(format);
        //解析
        SmsParamsDTO smsParamsDTO = smsAdapter.parse(message);
    }
}

适配器:

//适配器定义行为
public interface SmsAdapter {
    SmsParamsDTO parse(String msg);
}
//解析json的
public class JsonSmsAdapter implements SmsAdapter {
    @Override
    public SmsParamsDTO parse(String msg) {
        //解析json格式
        return null;
    }
}
//解析xml的
public class XmlSmsAdapter implements SmsAdapter {
    @Override
    public SmsParamsDTO parse(String msg) {
        //解析xml格式
        return null;
    }
}

根据类型获取适配器:

public class SmsAdapterFactory {
    public static SmsAdapter createAdapter(String format) {
        //判断报文格式获取不同的适配器对象
        if ("xml".equals(format)) {
            return new XmlSmsAdapter();
        } else if ("json".equals(format)) {
            return new JsonSmsAdapter();
        } else {
            //return default
            return null;
        }
    }
}

5、总结

适配器设计模式它允许不兼容的接口或类之间进行协同工作。

  • 适配器充当两个不兼容实体之间的桥梁,将一个接口转换为另一个接口,以便它们可以无缝地协同工作。
  • 在不修改现有代码的情况下实现接口之间的互操作性和交互。
  • 适配器通过包装或转换一个类,将其接口转换为客户端所期望的接口形式。

适配器设计模式的优点包括:

  • 解决接口不匹配问题:适配器允许不兼容的接口或类之间进行协同工作,提供了一种无缝的接口转换机制。

  • 可重用性和灵活性:适配器可以重复使用,用于适配多个不同的接口或类,提供了灵活性和可扩展性。

  • 保护现有代码:适配器可以包装现有的类或接口,而无需修改其源代码,从而保护现有代码的稳定性和完整性。

  • 提高代码复用:适配器将适配逻辑封装在一个单独的类中,可以在不同的系统和项目中重复使用。

适配器设计模式适用于以下情况:

  • 需要将一个已存在的类或接口适配到另一个接口,以满足客户端的需求。

  • 需要在不修改现有代码的情况下与已有类或接口进行协同工作。

  • 需要在系统中引入新的功能,但又不希望破坏现有的代码结构。

总而言之,适配器设计模式提供了一种解决接口不匹配问题的灵活而可扩展的方式,使得不兼容的类或接口可以协同工作。它帮助我们保护现有代码,提高代码复用性,并提供了系统的可扩展性和灵活性。

五、门面设计模式

1、介绍

门面设计模式(Facade Pattern)是一种结构性设计模式,它为子系统提供了一个统一的接口,以简化客户端与子系统之间的交互。通过使用门面模式,客户端可以通过与门面对象进行交互,而无需直接与子系统的组件进行通信。

2、核心思想

门面设计模式的核心思想是提供一个高层接口(门面),将复杂的子系统封装起来,以简化客户端与子系统之间的交互。门面对象充当了客户端与子系统之间的中介,它隐藏了子系统的复杂性,提供了一个简单且统一的接口供客户端使用。

3、组成和基本流程

门面设计模式由以下几个主要组成部分组成:

  • 门面(Facade):提供了一个统一的接口,封装了对子系统的访问。客户端只需与门面对象进行交互,无需直接与子系统的组件进行通信。
  • 子系统组件(Subsystems):表示实际执行工作的各个组件或类。子系统可以包含多个组件,每个组件负责一部分具体的功能。

门面设计模式的基本流程如下:

  1. 定义子系统组件:确定子系统中的各个组件或类,并实现它们的具体功能。
  2. 创建门面类:创建一个门面类,它提供了一个简单的接口来封装子系统的复杂性。门面类可以调用子系统组件来完成客户端请求的处理。
  3. 客户端与门面交互:客户端通过与门面对象进行交互来使用子系统。客户端只需调用门面提供的接口,无需直接与子系统组件进行交互。

4、使用场景

门面设计模式通常在以下情况下使用:

  • 当存在一个复杂的子系统,需要简化客户端与子系统之间的交互时。
  • 当需要将子系统的接口与实现解耦,以提高系统的可维护性和灵活性时。
  • 当希望向客户端隐藏子系统的复杂性,提供一个简单而统一的接口时。

门面设计模式在许多场景中都有应用,例如:

  • 在电商系统中,门面模式可以用于封装与库存管理、订单处理和支付系统等子系统的交互,提供一个统一的接口给客户端进行商品购买操作。
  • 在酒店预订系统中,门面模式可以将与客房管理、价格计算和订单生成等子系统的交互封装起来,为客户端提供一个简单的接口来预订酒店房间。

通过使用门面设计模式,我们可以简化复杂系统的使用,并提供一个统一的接口给客户端,使得客户端更加方便地与子系统进行交互。

5、具体案例

一个具体的业务场景,结合门面设计模式的案例是一个在线购物平台的订单处理系统。订单处理系统涉及多个子系统,包括库存管理、支付服务、物流管理等。在这种情况下,可以使用门面设计模式来提供一个简化的接口,将订单处理系统的复杂性隐藏起来。

下面是一个简化的示例代码,展示如何使用门面设计模式来简化订单处理系统:

// 子系统:库存管理
class InventorySystem {
    public void updateInventory(String productId, int quantity) {
        // 更新库存
        // ...
        System.out.println("更新库存,库存-1");
    }
}

// 子系统:支付服务
class PaymentService {
    public void processPayment(double amount, String paymentMethod) {
        // 处理支付
        // ...
        System.out.println("付款成功");
    }
}

// 子系统:物流管理
class LogisticsService {
    public void shipOrder(String orderId, String shippingAddress) {
        // 发货处理
        // ...
        System.out.println("开始发货,揽件");
    }
}

// 门面:订单处理门面
class OrderProcessingFacade {
    private InventorySystem inventorySystem;
    private PaymentService paymentService;
    private LogisticsService logisticsService;

    public OrderProcessingFacade() {
        this.inventorySystem = new InventorySystem();
        this.paymentService = new PaymentService();
        this.logisticsService = new LogisticsService();
    }

    public void processOrder(String productId, int quantity, double amount, String paymentMethod, String shippingAddress) {
        // 更新库存
        inventorySystem.updateInventory(productId, quantity);

        // 处理支付
        paymentService.processPayment(amount, paymentMethod);

        // 物流处理
        logisticsService.shipOrder(generateOrderId(), shippingAddress);

        // 其他订单处理逻辑
        // ...
    }

    private String generateOrderId() {
        // 生成订单号
        // ...
        String orderId = UUID.randomUUID().toString();
        return orderId;
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        OrderProcessingFacade orderProcessingFacade = new OrderProcessingFacade();

        // 处理订单
        orderProcessingFacade.processOrder("12345", 2, 100.0, "Credit Card", "Shipping Address");
    }
}

运行输出:

更新库存,库存-1
付款成功
开始发货,揽件

在这个示例中,我们构建了一个在线购物平台的订单处理系统。订单处理涉及多个子系统,包括库存管理、支付服务和物流管理。为了简化订单处理系统的复杂性,我们使用了门面设计模式。

在这个示例中,我们有以下几个类:

  • InventorySystem:库存管理子系统,负责更新库存。
  • PaymentService:支付服务子系统,负责处理支付。
  • LogisticsService:物流管理子系统,负责处理物流。
  • OrderProcessingFacade:订单处理门面,提供一个简化的接口来处理订单。它内部持有库存管理、支付服务和物流管理子系统的实例,并在processOrder方法中调用它们的相应方法来处理订单。

客户端代码中,我们创建了一个OrderProcessingFacade实例,并使用它来处理订单。通过调用processOrder方法,我们将订单的相关信息传递给门面,然后门面负责协调并调用库存管理、支付服务和物流管理子系统的方法来处理订单。客户端无需直接与子系统交互,而是通过门面来完成订单处理。

通过使用门面设计模式,我们将订单处理系统的复杂性封装起来,客户端只需要与门面进行交互,不需要了解和管理子系统的细节。这样可以提高代码的可维护性和可扩展性,同时降低了客户端与子系统的耦合度。

6、总结

门面设计模式是一种有用的设计模式,它提供了许多优点和一些潜在的缺点。

优点:

  • 简化客户端与复杂子系统之间的交互,客户端只需与门面对象进行通信,无需了解子系统的内部结构和组件。
  • 将子系统与客户端解耦,子系统的变更不会影响到客户端的代码。
  • 提供了一个清晰的界面和高层次的接口,提高了系统的可维护性和可读性。

缺点:

  • 可能会导致门面对象变得庞大,承担过多的责任,违反单一职责原则。
  • 如果需要对子系统进行更细粒度的控制或定制化操作,可能需要修改门面类,导致修改范围较大。

总体而言,门面设计模式是一种有助于简化复杂系统的交互和提高系统可维护性的设计模式。它在许多应用场景中都有着广泛的应用,尤其是在需要隐藏复杂性并提供统一接口的情况下。然而,在使用该模式时,需要权衡好门面对象的责任和灵活性之间的平衡,以确保设计的合理性和可扩展性。

7、与适配器设计模式的区别

  • 适配器设计模式:用于将一个类的接口转换为另一个类的接口,以便它们可以协同工作。
  • 门面设计模式:提供一个统一的接口,隐藏了底层子系统的复杂性,使客户端与子系统之间的交互更加简单和直观。

六、组合设计模式

1、介绍

组合设计模式是一种结构性设计模式,它允许将对象组织成树形结构,以表示“部分-整体”的层次结构。组合模式使得客户端能够以一致的方式处理单个对象以及对象的组合。

2、核心思想

组合设计模式的核心思想是将对象组织成树形结构,并对叶子节点和组合节点提供一致的操作接口。通过这种方式,客户端可以将单个对象和组合对象一视同仁,无需关心处理的是单个对象还是对象的组合。

3、组成和基本流程

组合设计模式由以下几个主要角色组成:

  1. Component(组件): 定义组合中对象的共有接口,可以是接口或抽象类。该接口声明了对于组合对象和叶子对象的通用操作。
  2. Leaf(叶子): 表示组合中的叶子对象,它没有子节点。实现了Component接口的操作。
  3. Composite(组合): 表示组合中的组合对象,它拥有子节点。实现了Component接口的操作,并且通常存储子节点。

组合设计模式的基本流程如下:

  1. 创建组件接口(Component),声明通用的操作方法。
  2. 创建叶子类(Leaf),实现组件接口。叶子类表示组合中的叶子节点。
  3. 创建组合类(Composite),实现组件接口。组合类表示组合中的组合节点,并且通常存储子节点。
  4. 在组合类中实现对子节点的管理操作,例如添加子节点、删除子节点等。
  5. 在客户端中使用组合类来构建组合对象的树形结构,并通过组件接口操作对象。

以下是一个示例代码片段,演示了组合设计模式的基本流程:

// 定义组件接口
interface Component {
    void operation();
}

// 叶子类
class Leaf implements Component {
    public void operation() {
        // 实现叶子对象的操作
    }
}

// 组合类
class Composite implements Component {
    private List<Component> components = new ArrayList<>();

    public void addComponent(Component component) {
        // 添加子节点
        components.add(component);
    }

    public void removeComponent(Component component) {
        // 删除子节点
        components.remove(component);
    }

    public void operation() {
        // 实现组合对象的操作
        // 可以通过循环调用子节点的操作方法来实现对子节点的操作
        for (Component component : components) {
            component.operation();
        }
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Component leaf1 = new Leaf();
        Component leaf2 = new Leaf();

        Composite composite1 = new Composite();
        composite1.addComponent(leaf1);
        composite1.addComponent(leaf2);

        Component leaf3 = new Leaf();

        Composite composite2 = new Composite();
        composite2.addComponent(leaf3);
        composite2.addComponent(composite1);

        composite2.operation();
    }
}

在这个示例中,我们有以下几个类:

  • Component:组件接口,定义了组合模式中的叶子节点和组合节点的共同操作方法。
  • Leaf:叶子节点,实现了Component接口,并定义了叶子节点的操作方法。
  • Composite:组合节点,实现了Component接口,并持有一个子组件列表。它提供了添加子组件和移除子组件的方法,并在operation方法中递归调用子组件的操作方法。

在客户端代码中,我们创建了一个叶子节点和一个组合节点,并将叶子节点添加到组合节点中。然后,我们调用组合节点的操作方法,它会递归调用叶子节点的操作方法。

通过使用组合模式,我们可以以统一的方式处理单个对象和组合对象,客户端无需关心当前处理的是叶子节点还是组合节点,从而简化了客户端的代码。组合模式适用于需要处理树状结构的情况,例如文件系统、UI组件、菜单栏等。

4、案例

例子1:业务场景 - 文件夹

好的,再举一个例子,以文件和文件夹为对象来说明组合设计模式的应用。

在这个例子中,我们使用组合模式来表示文件系统的层次结构。

  • 文件和文件夹都实现了FileSystemComponent接口,表示树状结构中的叶子节点和组合节点。
  • Folder类表示树状结构中的组合节点,它可以包含文件和其他文件夹。
  • 客户端代码中,我们创建了文件和文件夹对象,并将它们组织起来形成文件系统的层次结构。
  • 最后,我们调用根文件夹的display方法来显示整个文件系统。
interface FileSystemComponent {
    void display();
}

// 文件类,实现组件接口
class File implements FileSystemComponent {
    private String name;

    public File(String name) {
        this.name = name;
    }

    @Override
    public void display() {
        System.out.println("文件:" + name);
    }
}

// 文件夹类,实现组件接口
class Folder implements FileSystemComponent {
    private String name;
    private List<FileSystemComponent> components = new ArrayList<>();

    public Folder(String name) {
        this.name = name;
    }

    public void addComponent(FileSystemComponent component) {
        components.add(component);
    }

    public void removeComponent(FileSystemComponent component) {
        components.remove(component);
    }

    public List<FileSystemComponent> getComponents() {
        return components;
    }

    @Override
    public void display() {
        System.out.println("文件夹:" + name);
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 创建文件和文件夹对象
        FileSystemComponent song1 = new File("redflavor.mp3");
        FileSystemComponent song2 = new File("psycho.mp3");
        FileSystemComponent song3 = new File("birthday.mp3");
        Folder lyricsFolder = new Folder("歌词");
        FileSystemComponent lyric1 = new File("redflavor.txt");
        FileSystemComponent lyric2 = new File("psycho.txt");
        FileSystemComponent lyric3 = new File("birthday.txt");
        Folder musicFolder = new Folder("音乐");

        musicFolder.addComponent(song1);
        musicFolder.addComponent(song2);
        musicFolder.addComponent(song3);
        lyricsFolder.addComponent(lyric1);
        lyricsFolder.addComponent(lyric2);
        lyricsFolder.addComponent(lyric3);
        musicFolder.addComponent(lyricsFolder);

        FileSystemComponent movie1 = new File("疾速追杀.mp4");
        FileSystemComponent movie2 = new File("疾速备战.mp4");
        Folder movieFolder = new Folder("电影");
        movieFolder.addComponent(movie1);
        movieFolder.addComponent(movie2);

        Folder rootFolder = new Folder("D");
        rootFolder.addComponent(musicFolder);
        rootFolder.addComponent(movieFolder);

        // 遍历文件系统
        rootFolder.display();
        traverseFileSystem(rootFolder);
    }

    public static void traverseFileSystem(FileSystemComponent component) {
        if (component instanceof Folder) {
            for (FileSystemComponent subComponent : ((Folder) component).getComponents()) {
                subComponent.display();
                traverseFileSystem(subComponent);
            }
        }
    }
}

在上述示例中:

  • 我们创建了文件和文件夹对象,并按照要求构建了文件夹的层次结构。
  • 音乐文件夹包含三首歌和一个歌词文件夹
    • 歌词文件夹中包含三个歌词文件。
  • 电影文件夹中包含两部电影。
  • 所有这些文件和文件夹都被组织在根文件夹 D 中。

以下是使用Markdown格式绘制上述文件夹和文件的树形结构:

- D (文件夹)
    - 音乐 (文件夹)
        - reflavor.mp3 (文件)
        - psycho.mp3 (文件)
        - birthday.mp3 (文件)
        - 歌词 (文件夹)
            - reflavor.txt (文件)
            - psycho.txt (文件)
            - birthday.txt (文件)
    - 电影 (文件夹)
        - 疾速追杀.mp4 (文件)
        - 疾速备战.mp4 (文件)

这个树形结构表示了根文件夹 "D" 下的音乐文件夹和电影文件夹,以及它们的子文件和子文件夹。其中,音乐文件夹下包含了三个音乐文件和一个歌词文件夹,歌词文件夹中包含了三个歌词文件。电影文件夹中包含了两部电影文件。

请注意,上述树形结构只是一种可视化的表示方式,并非真正的文件系统结构。它提供了一个直观的展示方式,以更清晰地理解文件夹和文件之间的层次关系。

traverseFileSystem 方法中,我们使用递归方式遍历文件系统的层次结构,并调用每个文件和文件夹的 display 方法进行展示。

通过运行该代码,您将会看到文件夹结构被递归地打印出来,包括每个文件和文件夹的名称。

文件夹:D
文件夹:音乐
文件:redflavor.mp3
文件:psycho.mp3
文件:birthday.mp3
文件夹:歌词
文件:redflavor.txt
文件:psycho.txt
文件:birthday.txt
文件夹:电影
文件:疾速追杀.mp4
文件:疾速备战.mp4

通过使用组合模式,我们可以方便地管理和操作复杂的文件系统结构。组合模式统一了叶子节点和组合节点的处理方式,客户端无需关心当前处理的是文件还是文件夹,可以以相同的方式对待它们。这种设计模式简化了代码,并提供了一种灵活的方式来处理树状结构。

例子2:业务场景 - 组织架构管理

假设我们有一个组织架构管理系统,用于管理公司的组织结构。组织结构包括部门和员工,每个部门可以包含其他部门或员工,形成一个树状结构。在这种情况下,可以使用组合设计模式来表示组织架构。

// 组件接口
interface Component {
    void display();
}

// 部门类,实现组件接口
class Department implements Component {
    private String name;

    public Department(String name) {
        this.name = name;
    }

    public void display() {
        System.out.println("部门:" + name);
    }
}

// 员工类,实现组件接口
class Employee implements Component {
    private String name;

    public Employee(String name) {
        this.name = name;
    }

    public void display() {
        System.out.println("员工:" + name);
    }
}

// 组合类,包含子组件
class Composite implements Component {
    private List<Component> components = new ArrayList<>();

    public void add(Component component) {
        components.add(component);
    }

    public void remove(Component component) {
        components.remove(component);
    }

    public void display() {
        for (Component component : components) {
            component.display();
        }
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 创建部门和员工对象
        Component hrDepartment = new Department("人力资源部");
        Component financeDepartment = new Department("财务部");
        Component employee1 = new Employee("张三");
        Component employee2 = new Employee("李四");

        // 创建组合对象
        Composite organization = new Composite();
        organization.add(hrDepartment);
        organization.add(financeDepartment);
        organization.add(employee1);
        organization.add(employee2);

        // 显示组织架构
        organization.display();
    }
}

在这个例子中,我们使用组合模式来管理组织架构。部门和员工都实现了Component接口,表示树状结构中的叶子节点。Composite类表示树状结构中的组合节点,它可以包含部门和员工。客户端代码中,我们创建了部门、员工和组合对象,并将它们组织起来形成组织架构。最后,我们调用组合对象的display方法来显示组织架构。

5、总结

组合模式是一种结构型设计模式,旨在以树状结构组织对象,使得单个对象和组合对象能够以一致的方式进行处理。该模式允许我们将对象组合成树状结构,从而形成层次关系,并且可以递归地处理整个层次结构。

通过使用组合模式,我们可以将单个对象和组合对象都视为相同的抽象组件,从而使得客户端代码无需区分处理单个对象还是组合对象,而是统一使用组件接口进行操作。

以下是组合模式的一些关键点和总结:

  1. 角色:

    • 组件(Component):定义组合对象和叶子对象的共同接口。它可以是接口或抽象类,提供了默认的行为和管理子组件的方法。
    • 叶子对象(Leaf):表示组合中的叶子节点,它没有子组件。
    • 组合对象(Composite):表示组合中的容器节点,它可以包含子组件。它实现了组件接口,并提供了添加、删除和获取子组件的方法。
  2. 优点:

    • 简化客户端代码:客户端无需区分处理单个对象和组合对象,可以统一通过组件接口进行操作,从而简化了客户端代码。
    • 易于扩展:可以通过添加新的叶子对象或组合对象来扩展层次结构,而无需修改现有代码。
    • 递归处理:可以递归地处理整个层次结构,无论是遍历、搜索还是执行操作,都能够方便地应用于整个组合对象。
  3. 注意事项:

    • 组件接口的设计:组件接口应该定义一组合适的方法,以满足组合和叶子对象的需要,并提供默认实现(如果有的话)。
    • 叶子对象的限制:叶子对象不能有子组件,它们应该是组合结构的最小单位。
    • 递归遍历:递归遍历组合结构时,需要适当处理组件和叶子对象的差异,以避免出现不必要的操作或错误。

组合模式在许多场景中都有应用,特别是当需要处理树状结构的对象时,它能够提供一种灵活且统一的方式来处理对象的层次关系。

总的来说,组合模式通过统一处理单个对象和组合对象,提供了一种方便的方式来管理和操作树状结构的对象。它帮助我们简化了客户端代码,同时也提供了一种灵活的方式来扩展和处理组合对象的层次结构。

希望这个总结对您有帮助!如果您还有其他问题,请随时提问。

七、享元设计模式

1、介绍

享元设计模式(Flyweight Pattern)是一种结构性设计模式,它通过共享对象来有效地支持大量细粒度的对象。享元模式旨在减少内存使用和提高性能,特别适用于需要创建大量相似对象的情况。

2、核心思想

享元设计模式的核心思想是共享对象。它通过将对象的共享部分提取出来,并将不同对象之间的变化部分外部化,以实现对相似对象的共享和复用。这样可以减少对象的创建和内存占用,并提高系统的性能。

3、组成和基本流程

享元设计模式由以下组成部分构成:

  • 享元工厂(Flyweight Factory):负责创建和管理享元对象,维护一个享元池用于存储和管理共享对象。
  • 享元对象(Flyweight):表示需要被共享的对象,包含内部状态和外部状态两部分。内部状态可被共享,而外部状态则由客户端提供。

享元设计模式的基本流程如下:

  1. 创建享元工厂,用于创建和管理享元对象。
  2. 定义享元对象接口,包含对内部状态和外部状态的操作方法。
  3. 客户端通过享元工厂获取或创建享元对象。
  4. 客户端设置享元对象的外部状态。
  5. 客户端使用享元对象进行操作,享元对象根据内部状态和外部状态的组合来执行具体操作。

4、使用场景

享元设计模式适用于以下场景:

  • 当系统需要大量相似对象,并且创建和管理这些对象会消耗大量资源时。
  • 当对象的大部分状态可以外部化,并且可以在不同对象之间共享时。
  • 当需要缓存和复用对象以提高系统性能时。

6、具体案例(仅标题)

  • 游戏中的棋子对象
  • 文字编辑器中的字符对象(字体、样式)
  • 考试系统里面的试卷、学生

7、总结

享元设计模式通过共享对象来减少内存使用和提高性能。它适用于需要大量相似对象的情况,并将对象的共享部分提取出来以实现复用。使用享元设计模式可以有效地减少系统资源的消耗,提高系统的性能和可扩展性。

优点:

  • 减少内存使用:通过共享对象的方式减少相似对象的创建,降低了内存占用。
  • 提高性能:共享对象减少了对象的创建和销毁过程,提高了系统的性能。
  • 支持大量对象:可以有效地支持大量细粒度的对象,提供了更好的扩展性。

缺点:

  • 共享外部状态:外部状态的共享需要注意线程安全性和正确性。
  • 复杂性增加:享元模式会引入共享对象的管理逻辑,增加了代码的复杂性。

总体而言,享元设计模式在需要创建大量相似对象并且关注内存和性能优化的情况下非常有用。它提供了一种有效地共享和复用对象的机制,以提高系统的效率和可扩展性。然而,在使用该模式时需要注意外部状态的处理和线程安全性,以确保系统的正确性和可靠性。