Java常用设计模式(三)

组合模式(Composite Pattern)

  组合模式是一种结构型设计模式,它允许将对象组合成树形结构,以表示“部分-整体”的层次关系。组合模式可以让客户端使用统一的方式处理单个对象和组合对象,从而简化了客户端的代码。

使用场景

  1. 当需要表示一个对象的部分-整体层次结构时,可以使用组合模式。例如,可以使用组合模式来表示一个文件夹,它包含多个文件和其他文件夹。

  1. 当客户端代码需要统一地处理单个对象和组合对象时,可以使用组合模式。这样客户端代码可以使用相同的代码来处理单个对象和组合对象,而不需要进行特判。

  1. 当需要对树形结构进行递归遍历时,可以使用组合模式。组合模式可以让代码更加简洁,而且可以轻松地遍历整个树形结构

  1. 当需要动态地添加或删除树形结构中的子节点时,可以使用组合模式。组合模式可以让你方便地添加或删除子节点,而且不会影响其他节点的行

代码实现

以下是另一个使用Java组合模式的例子,它表示一个文件系统:

import java.util.ArrayList;
import java.util.List;

publicclassFileSystem {
    private String name;
    privateboolean isFile;
    private List<FileSystem> children = newArrayList<>();

    publicFileSystem(String name, boolean isFile) {
        this.name = name;
        this.isFile = isFile;
    }

    publicvoidadd(FileSystem fileSystem) {
        children.add(fileSystem);
    }

    publicvoidremove(FileSystem fileSystem) {
        children.remove(fileSystem);
    }

    publicvoiddisplay(int depth) {
        System.out.println("-".repeat(depth) + name);

        for (FileSystem fileSystem : children) {
            fileSystem.display(depth + 2);
        }
    }

    publicstaticvoidmain(String[] args) {
        FileSystemroot=newFileSystem("C:", false);
        FileSystemwindowsFolder=newFileSystem("Windows", false);
        FileSystemprogramFilesFolder=newFileSystem("Program Files", false);
        FileSystemnotepadFile=newFileSystem("notepad.exe", true);
        FileSystemjavaFolder=newFileSystem("Java", false);
        FileSystemeclipseFolder=newFileSystem("Eclipse", false);
        FileSystemeclipseExeFile=newFileSystem("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)

  外观模式是一种结构型设计模式,它提供了一个简单的接口,隐藏了一组复杂的子系统的复杂性,使得客户端可以更容易地使用这个子系统。

  外观模式的核心思想是通过提供一个简化的接口,将系统的复杂性封装起来,从而降低系统的耦合性,并使得系统更易于维护和扩展。

使用场景

  1. 系统中包含多个模块,每个模块都有自己的接口和实现,需要对外提供一个统一的接口

  1. 系统中存在多个复杂的对象或类,需要对外提供简单的接口

  1. 需要解耦系统的各个组件,使得它们可以独立变化

  1. 需要隔离系统的变化,使得系统的不同模块可以独立变化

代码实现

  一个常见的外观模式的例子是电脑开机过程。在电脑开机的过程中,有许多子系统需要被初始化,例如 CPU、内存、硬盘等等。如果每个子系统都要直接与客户端进行交互,那么客户端的代码会非常复杂。

  使用外观模式可以将所有子系统的初始化过程封装到一个简单的接口中,从而使得客户端只需要与外观对象交互,就可以完成所有的初始化过程。

// CPU 子系统publicclassCPU {
    publicvoidstart() {
        System.out.println("CPU is starting...");
    }
}

// 内存子系统publicclassMemory {
    publicvoidstart() {
        System.out.println("Memory is starting...");
    }
}

// 硬盘子系统publicclassHardDrive {
    publicvoidstart() {
        System.out.println("HardDrive is starting...");
    }
}

// 外观类publicclassComputer {
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;

    publicComputer() {
        cpu = new CPU();
        memory = new Memory();
        hardDrive = new HardDrive();
    }

    // 提供一个简单的接口,封装了所有子系统的初始化过程publicvoidstart() {
        System.out.println("Computer is starting...");
        cpu.start();
        memory.start();
        hardDrive.start();
        System.out.println("Computer has started...");
    }
}

// 客户端代码publicclassClient {
    publicstaticvoidmain(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)

  享元模式是一种结构型设计模式,它通过共享对象来减少内存占用和提高系统性能。享元模式适用于大量细粒度的对象,这些对象具有相似的属性,而且这些属性可以被共享。

  在享元模式中,我们将对象分为两种:

  • 内部状态指对象共享的部分,可以被多个对象共享;

  • 外部状态则指对象独有的部分,不能被共享。

  通过将内部状态抽取出来,我们可以减少系统中需要创建的对象数量,从而降低内存消耗。

使用场景

  1. 当一个应用程序需要创建大量的相似对象时,使用享元模式可以减少内存占用和提高系统性能

  1. 当一个对象的状态可以被拆分为内部状态和外部状态时,使用享元模式可以将内部状态共享,从而减少对象数量和内存消耗。

  1. 当一个应用程序需要使用缓存来提高性能时,可以使用享元模式来实现缓存功能,从而提高系统性能。

代码实现

  使用享元模式实现一个简单的字符串存储库,它可以存储大量的字符串,同时尽可能节省内存。

  首先定义一个 Flyweight 接口,它表示共享的字符串对象。具体的字符串对象将实现这个接口。

public interface Flyweight {
    voidprint(String str);
}
复制代码

  接下来,定义一个具体的 Flyweight 实现类,它包含一个字符串值和一个计数器,用于记录该字符串的使用次数

publicclassConcreteFlyweightimplementsFlyweight {
    privateString value;
    private int count;

    publicConcreteFlyweight(String value) {
        this.value = value;
        this.count = 0;
    }

    @Overridepublicvoidprint(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;

publicclassFlyweightFactory {
    privatestaticfinal Map<String, Flyweight> flyweights = newHashMap<>();

    publicstatic Flyweight getFlyweight(String value) {
        Flyweightflyweight= flyweights.get(value);
        if (flyweight == null) {
            flyweight = newConcreteFlyweight(value);
            flyweights.put(value, flyweight);
        }
        return flyweight;
    }
}
复制代码

  最后,定义一个 Client 类,它负责使用 Flyweight 对象来存储字符串

publicclassClient {
    publicstaticvoidmain(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 静态代理例子,它模拟了一个银行账户的操作:

// 定义一个账户接口publicinterfaceAccount {
    voiddeposit(double amount);
    voidwithdraw(double amount);
}

// 实现账户接口的类publicclassBankAccountimplementsAccount {
    privatedouble balance;

    publicBankAccount(double balance) {
        this.balance = balance;
    }

    publicvoiddeposit(double amount) {
        balance += amount;
        System.out.println("Deposited " + amount + ", balance is now " + balance);
    }

    publicvoidwithdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
            System.out.println("Withdrew " + amount + ", balance is now " + balance);
        } else {
            System.out.println("Sorry, insufficient balance");
        }
    }
}

// 定义一个账户代理类publicclassAccountProxyimplementsAccount {
    private BankAccount bankAccount;

    publicAccountProxy(BankAccount bankAccount) {
        this.bankAccount = bankAccount;
    }

    publicvoiddeposit(double amount) {
        bankAccount.deposit(amount);
    }

    publicvoidwithdraw(double amount) {
        bankAccount.withdraw(amount);
    }
}

// 使用示例publicclassMain {
    publicstaticvoidmain(String[] args) {
        // 创建一个银行账户实例BankAccountaccount=newBankAccount(1000.0);

        // 创建一个账户代理实例AccountaccountProxy=newAccountProxy(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;

// 定义一个账户接口publicinterfaceAccount {
    voiddeposit(double amount);
    voidwithdraw(double amount);
}

// 实现账户接口的类publicclassBankAccountimplementsAccount {
    privatedouble balance;

    publicBankAccount(double balance) {
        this.balance = balance;
    }

    publicvoiddeposit(double amount) {
        balance += amount;
        System.out.println("Deposited " + amount + ", balance is now " + balance);
    }

    publicvoidwithdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
            System.out.println("Withdrew " + amount + ", balance is now " + balance);
        } else {
            System.out.println("Sorry, insufficient balance");
        }
    }
}

// 定义一个账户代理类publicclassAccountProxyimplementsInvocationHandler {
    private BankAccount bankAccount;

    publicAccountProxy(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");
        } elseif (method.getName().equals("withdraw")) {
            System.out.println("Before withdraw");
            method.invoke(bankAccount, args);
            System.out.println("After withdraw");
        }
        returnnull;
    }
}

// 使用示例publicclassMain {
    publicstaticvoidmain(String[] args) {
        // 创建一个银行账户实例BankAccountaccount=newBankAccount(1000.0);

        // 创建一个账户代理实例AccountProxyaccountProxy=newAccountProxy(account);

        // 使用动态代理创建一个代理对象Accountproxy= (Account) Proxy.newProxyInstance(
                Account.class.getClassLoader(),
                newClass<?>[] { 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)

  责任链模式是一种行为型设计模式,它允许多个对象来处理请求,并且将这些对象连成一条链。当请求到来时,它会依次经过链上的对象,直到有一个对象能够处理请求为止。

  责任链模式和过滤器模式都是行为型设计模式,它们的主要目的是将处理请求的对象组织成一个链,并让请求沿着这个链传递,直到找到能够处理该请求的对象为止。

  那他们有什么区别吗?
  1. 用途不同:责任链模式的主要目的是让多个对象都有机会处理同一个请求,而过滤器模式的主要目的是在一个对象中实现多个过滤器,并依次对请求进行处理。

  1. 处理方式不同:责任链模式中,每个处理器都会判断自己是否能够处理请求,如果可以则处理请求,并将请求传递给下一个处理器;如果不能处理,则将请求传递给下一个处理器。而过滤器模式中,每个过滤器都是独立的,它们按照一定的顺序依次处理请求,如果某个过滤器不能处理请求,则直接返回。

  1. 对象关系不同:责任链模式中,处理器之间通常是单向关系,即每个处理器只知道下一个处理器是谁,而不知道上一个处理器是谁。而过滤器模式中,过滤器之间通常是相互独立的,它们不需要知道彼此的存在。

  1. 实现方式不同:责任链模式通常是通过继承或组合的方式实现的,而过滤器模式通常是通过组合的方式实现的。

使用场景

  责任链模式通常用于以下场景:

  • 请求的处理涉及多个对象,并且处理流程需要按顺序进行。

  • 请求的处理方式需要动态决定,或者需要在不同时间处理请求。

  • 在请求处理过程中,需要将请求拆分成多个部分分别处理,最后将它们合并。

  • 需要在不影响客户端的情况下增强处理流程。

  • 需要在系统中动态添加或删除处理器。

  • 处理器之间需要解耦,避免它们之间的直接依赖关系。

代码实现

  假设我们有一个处理HTTP请求的框架,其中有多个过滤器(Filter),每个过滤器都可以对请求进行处理,如果当前的过滤器不能处理请求,就将请求交给下一个过滤器。代码如下:

publicinterfaceFilter {
    voiddoFilter(Request request, Response response, FilterChain chain);
}

publicclassFilterAimplementsFilter {
    @OverridepublicvoiddoFilter(Request request, Response response, FilterChain chain) {
        System.out.println("过滤器A处理请求");
        // 处理请求
        chain.doFilter(request, response);
        // 处理响应
    }
}

publicclassFilterBimplementsFilter {
    @OverridepublicvoiddoFilter(Request request, Response response, FilterChain chain) {
        System.out.println("过滤器B处理请求");
        // 处理请求
        chain.doFilter(request, response);
        // 处理响应
    }
}

publicclassFilterChain {
    private List<Filter> filters = newArrayList<>();
    privateintindex=0;

    publicvoidaddFilter(Filter filter) {
        filters.add(filter);
    }

    publicvoiddoFilter(Request request, Response response) {
        if (index == filters.size()) {
            return;
        }
        Filterfilter= filters.get(index);
        index++;
        filter.doFilter(request, response, this);
    }
}

publicclassRequest {}
publicclassResponse {}

publicclassMain {
    publicstaticvoidmain(String[] args) {
        Requestrequest=newRequest();
        Responseresponse=newResponse();

        FilterChainchain=newFilterChain();
        chain.addFilter(newFilterA());
        chain.addFilter(newFilterB());

        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)

  解释器模式是一种行为型设计模式,它定义了一种语言语法,以及解释器,可以解释这种语法。解释器模式通常用于编译器、解析器、表达式计算器等领域。例如,正则表达式引擎就是一个常见的使用解释器模式的例子。

使用场景

  1. 当有一个语言需要解释执行,并且可以将该语言表示为一个抽象语法树时,解释器模式是一个很好的选择。

  1. 当需要处理一些规则或配置时,解释器模式也可以作为一种替代方案。例如,编写一个计算器程序时,可以使用解释器模式来处理表达式。

  1. 当需要实现一些复杂的规则或策略时,解释器模式也是一种不错的选择。例如,编写一个自然语言处理程序时,可以使用解释器模式来解析和理解语言的语法结构。

代码实现

  下面是一个简单的Java解释器模式的例子,用于计算加法表达式的值:

interfaceExpression {
    intinterpret();
}

classNumberExpressionimplementsExpression {
    privatefinalint number;

    publicNumberExpression(int number) {
        this.number = number;
    }

    @Overridepublicintinterpret() {
        return number;
    }
}

classPlusExpressionimplementsExpression {
    privatefinal Expression leftExpression;
    privatefinal Expression rightExpression;

    publicPlusExpression(Expression leftExpression, Expression rightExpression) {
        this.leftExpression = leftExpression;
        this.rightExpression = rightExpression;
    }

    @Overridepublicintinterpret() {
        return leftExpression.interpret() + rightExpression.interpret();
    }
}

classInterpreterDemo {
    publicstaticvoidmain(String[] args) {
        Expressionexpression=newPlusExpression(newNumberExpression(1), newNumberExpression(2));
        System.out.println(expression.interpret()); // Output: 3
    }
}

复制代码

  这里定义了两种表达式:NumberExpression表示一个数字,PlusExpression表示加法表达式。PlusExpression包含左表达式和右表达式,它们可以是数字或其他表达式。interpret方法用于计算表达式的值。

  在实际应用中,解释器模式可能会更加复杂,但这个例子可以帮助我们了解解释器模式的基本思想和实现方式。

使用小结

  在Java中,解释器模式常常应用于编译器、正则表达式、模板引擎、SQL解析器等方面。常用的Java模板引擎,如FreeMarker和Thymeleaf等,都是基于解释器模式实现的。

  解释器模式的缺点是:它可能会导致类的数量增加,并且在解析复杂的语法时,可能会导致性能问题。因此,在选择解释器模式时,需要考虑这些因素。

迭代器模式(Iterator Pattern)

  迭代器模式是一种行为设计模式,它提供一种统一的方法来遍历一个容器中的所有元素,而不用暴露容器的内部结构。

  使用迭代器模式可以将遍历容器和容器本身的实现分离开来,从而可以在不影响容器的情况下更改遍历算法。此外,迭代器模式可以简化遍历代码,并提供更加通用的遍历方法。

使用场景

  1. 需要遍历一个容器中的所有元素,但是不想暴露容器的内部结构。

  1. 需要提供一个通用的遍历方法,而不需要知道容器的具体实现细节。

  1. 需要支持多种不同的遍历方式,例如正序、倒序、随机等。

  1. 需要对容器的遍历算法进行更改,但是不想影响容器本身的实现。

  1. 需要在遍历过程中同时进行修改操作

代码实现

  下面是一个使用Java迭代器模式的简单例子,以遍历一个名字列表为例:

  首先定义一个名字列表接口,包含两个方法:添加名字和获取迭代器。

public interface NameList {
    voidaddName(String name);
    Iterator<String> iterator();
}
复制代码

  然后定义一个名字列表实现类,实现添加名字和获取迭代器的方法。

  在这个实现类中,定义了一个内部类 NameIterator,实现了 Iterator 接口中的 hasNext、next 和 remove 方法。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

publicclassNameListImplimplementsNameList {
    privateList<String> names = newArrayList<>();

    publicvoidaddName(String name) {
        names.add(name);
    }

    publicIterator<String> iterator() {
        returnnewNameIterator();
    }

    privateclassNameIteratorimplementsIterator<String> {
        private int index = 0;

        publicbooleanhasNext() {
            return index < names.size();
        }

        publicStringnext() {
            return names.get(index++);
        }

        publicvoidremove() {
            names.remove(--index);
        }
    }
}
复制代码

  下面是一个使用迭代器遍历名字列表的示例代码:

publicclass IteratorDemo {
    publicstatic 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)

  中介者模式用于降低多个对象之间的耦合,通过将多个对象之间的通信封装在一个中介者对象中来实现。中介者对象可以独立地管理对象之间的交互,并且可以让这些对象互相通信而不必显示地引用彼此。

使用场景

  1. 多个对象之间存在复杂的相互关系,但是它们的交互逻辑比较分散和混乱。

  1. 当一个对象需要和其他多个对象进行交互时,如果每个对象都直接和其他对象进行交互,那么会产生复杂的调用关系,导致代码难以维护和扩展。

  1. 当一个对象的改变会影响到其他多个对象的状态时,如果每个对象都需要手动处理状态变化,那么也会产生复杂的交互逻辑和调用关系。

代码实现

  假设正在开发一个聊天室应用程序,我们可以使用中介者模式来协调多个聊天用户之间的交互。在这个应用程序中,每个聊天用户都可以发送消息给其他用户,而中介者将负责将这些消息传递给其他用户。

  具体实现中,我们可以定义一个中介者接口,其中包含一个发送消息的方法。然后我们可以创建一个具体的中介者实现,该实现将负责将消息从一个聊天用户传递到另一个聊天用户。我们还需要创建一个聊天用户接口和具体的聊天用户实现,以便我们可以让多个聊天用户加入到中介者中,并相互交互。

  以下是一个简单的 Java 中介者模式示例,其中包含一个中介者接口(ChatMediator),聊天用户接口(ChatUser)和具体实现(UserImpl),以及一个具体的中介者实现(ChatMediatorImpl):

// 中介者接口publicinterfaceChatMediator {
    voidsendMessage(String message, ChatUser user);
}

// 聊天用户接口publicinterfaceChatUser {
    voidreceiveMessage(String message);
    voidsendMessage(String message);
}

// 具体的聊天用户实现publicclassUserImplimplementsChatUser {
    privateChatMediator mediator;
    privateString name;

    publicUserImpl(ChatMediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }

    @OverridepublicvoidreceiveMessage(String message) {
        System.out.println(name + " received message: " + message);
    }

    @OverridepublicvoidsendMessage(String message) {
        mediator.sendMessage(message, this);
    }
}

// 具体的中介者实现publicclassChatMediatorImplimplementsChatMediator {
    privateList<ChatUser> users;

    publicChatMediatorImpl() {
        this.users = newArrayList<>();
    }

    @OverridepublicvoidsendMessage(String message, ChatUser user) {
        for (ChatUser u : users) {
            if (u != user) {
                u.receiveMessage(message);
            }
        }
    }

    publicvoidaddUser(ChatUser user) {
        users.add(user);
    }
}

// 测试代码publicclassMediatorPatternDemo {
    publicstaticvoidmain(String[] args) {
        ChatMediator mediator = newChatMediatorImpl();

        ChatUser user1 = newUserImpl(mediator, "Alice");
        ChatUser user2 = newUserImpl(mediator, "Bob");
        ChatUser user3 = newUserImpl(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'sup,guys?Bob received message:What'sup,guys?复制代码

使用小结

  在MVC框架中,中介者模式被广泛使用,模型和视图之间的通信和控制都可以通过中介者来实现,降低模块之间的耦合度,提高整个系统的可维护性和可扩展性。

  中介者模式将复杂的交互转化为中介者对象和其他对象之间的简单交互,从而使系统易于维护和扩展。

备忘录模式(Memento Pattern)

  备忘录模式是一种设计模式,它允许在不破坏封装性的情况下保存和恢复对象的状态。这种模式通常用于需要提供撤销或恢复操作的应用程序中。

使用场景

  • 需要提供撤销操作的应用程序,例如文本编辑器等。在这种情况下,备忘录模式可以用于保存对象的历史状态,并在需要时恢复对象的状态。

  • 需要保存对象状态以进行后续恢复的应用程序。例如,游戏保存和恢复状态。

  • 需要在不破坏对象封装性的情况下保存和恢复对象状态的应用程序。

  • 需要跟踪和记录对象状态变化的应用程序。例如,监控和记录系统状态变化。

代码实现

  假设我们有一个文本编辑器类,需要提供撤销和恢复操作:

import java.util.Stack;

publicclassTextEditor {
    privateString text;
    private Stack<TextEditorMemento> undoStack;
    private Stack<TextEditorMemento> redoStack;

    publicTextEditor(String text){
        this.text = text;
        this.undoStack = new Stack<>();
        this.redoStack = new Stack<>();
    }

    publicvoidsetText(String text){
        undoStack.push(newTextEditorMemento(this.text));
        this.text = text;
        redoStack.clear();
    }

    publicStringgetText(){
        return text;
    }

    publicvoidundo(){
        if (!undoStack.isEmpty()) {
            redoStack.push(newTextEditorMemento(this.text));
            this.text = undoStack.pop().getText();
        }
    }

    publicvoidredo(){
        if (!redoStack.isEmpty()) {
            undoStack.push(newTextEditorMemento(this.text));
            this.text = redoStack.pop().getText();
        }
    }

    privatestaticclassTextEditorMemento {
        privatefinalString text;

        publicTextEditorMemento(String text){
            this.text = text;
        }

        publicStringgetText(){
            return text;
        }
    }
}

publicclassMain {
    publicstaticvoidmain(String[] args){
        TextEditor editor = newTextEditor("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中

使用小结

  通过使用备忘录模式,我们可以轻松地提供撤销和恢复操作,同时保持其封装性和简洁性。如果需要实现多次撤销和恢复操作,我们只需要保存多个备忘录即可。

作者:也许明天y

链接:https://juejin.cn/post/7202820042688528445

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值