JAVA中23种设计模式(详解)

创建型模式

请解释什么是单例模式,并给出一个使用场景

单例模式(Singleton Pattern) 就是一个类只能有一个实例,它主要用于资源管理(避免资源冲突)保证全局唯一的场景。

具体使用场景

  1. 配置管理

基本上应用都会有一个全局配置,这个配置从理论上来说需要保证唯一性,确保读取到的配置是同一份,是一致的,所以天然适合单例实现。

  1. 连接池、线程池

池化资源需要保证唯一性,不然就没有池化的意义了,总不能每次访问池化资源都新建一个吧? 需要保持单例,控制具体池化资源的数量,便于管理和监控。

还有日志、缓存等需要全局唯一避免资源冲突的场景。

单例模式有哪几种实现?如何保证线程安全?

单例模式有很多种,首先最简单的是懒汉式单例

懒汉式
  1. (线程不安全) 单例:【不可用】

为了避免内存空间浪费,采用懒汉式单例,即用到该单例对象的时候再创建。但是,存在很大问题,单线程下这段代码没有问题,但是在多线程下有很大问题。

复制代码
public class LazyMan {
    private LazyMan(){};
    public static LazyMan lazyMan;   
    public static LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan = new LazyMan();   //用到该单例对象的时候再创建,这里没加锁,如果多个线程进入,会并发产生多个实例
        }
        return lazyMan;
    }
}

这时可以改进为下面的线程安全版懒汉式

  1. (线程安全)单例:【不推荐使用】

同步方法

复制代码
public class LazyMan {
    private LazyMan(){}; //私有化构造函数,防止外部实例化
    public static LazyMan lazyMan;   
    public static synchroized LazyMan getInstance(){  //加锁
        if (lazyMan==null){
            lazyMan = new LazyMan();   //用到该单例对象的时候再创建
        }
        return lazyMan;
    }
}

缺点:效率低,每次getInstance时都要同步处理,存在性能问题,实际开发不推荐

双重检查单例(线程安全):【推荐使用】

  • 实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),不为空则直接return实例化对象。

  • 利用volatile关键字保证了可见性,利用双重检查机制减少了同步带来的性能损耗。

复制代码
public class Singleton {
    private static volatile Singleton instance;
    // 私有构造函数,防止外部实例化
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
饿汉式

(静态常量)【可用】

类一旦加载就创建一个单例 ,保证在调用getInstance方法之前单例已经存在,即没有延迟加载,这种饿汉式单例会造成空间浪费。

复制代码
public class Hungry {
    private Hungry(){}
    private final static Hungry HUNGRY = new Hungry();    //在类内部就创建了一个静态对象,并且get方法返回该对象
    public static Hungry getInstance(){
        return HUNGRY;
    }
}
//或者 静态代码块形式
public class Hungry {
  static{
    private final static Hungry HUNGRY = new Hungry();    
  }     
    private Hungry(){}
    public static Hungry getInstance(){
        return HUNGRY;
    }
}
​
Hungry in1=Hungry.getInstance()
Hungry in2=Hungry.getInstance()        //in1==in2

改进方法,使用下面的静态内部类形式

静态内部类
  • (线程安全)单例:【推荐使用】

  • 不仅线程安全,还实现了延迟加载

复制代码
public class Inner {
    private Inner(){}
    //直到调用 getInstance() 方法之前,Inner 实例都不会被创建,实现了延迟初始化
    public static Inner getInstance(){
        return InnerClass.INNER;
    }
    private static class InnerClass{          
        private static final Inner INNER = new Inner();  //静态字段只会在类加载时被初始化一次,线程安全
    }
}
枚举
  • (线程安全)【推荐使用】

  • 不仅线程安全,还能防止反序列化导致重新创建新的对象

复制代码
class SingletonEnum{
    // 1、创建一个枚举
    public enum CreateInstance{
        // 枚举实例,底层变量定义是public static final,因此它在 JVM 中只会被初始化一次,并且是线程安全的
        INSTANCE;
        private SingletonEnum instance;
        
        // 保证不能在类外部通过new构造器来构造对象
        private CreateInstance() {
            instance = new SingletonEnum();
            System.out.println(Thread.currentThread().getName());
        }
        // 创建一个公共的方法,由实例调用返回单例类
        public SingletonEnum getInstance() {
            return instance;
        }
    }
    public static void main(string[] args){
        Singleton instance = CreateInstance.INSTANCE.getInstance();
        singleton instance2= CreateInstance.INSTANCE.getInstance();
        System.out.println(instance == instance2);  //输出 true
    }
}

工厂模式和抽象工厂模式有什么区别?

工厂模式

工厂模式定义了一个创建对象的接口,一个具体的工厂类负责生产一种产品,如果需要添加新的产品,仅需新增对应的具体工厂类而不需要修改原有的代码实现。

复制代码
// 抽象产品
interface Product {
    void use();
}
// 具体产品A
class ConcreteProductA implements Product {
    public void use() {
        System.out.println("Using ConcreteProductA");
    }
}
// 具体产品B
class ConcreteProductB implements Product {
    public void use() {
        System.out.println("Using ConcreteProductB");
    }
}
// 抽象工厂
interface Factory {
    Product createProduct();
}
// 具体工厂A
class ConcreteFactoryA implements Factory {
    public Product createProduct() {
        return new ConcreteProductA();
    }
}
// 具体工厂B
class ConcreteFactoryB implements Factory {
    public Product createProduct() {
        return new ConcreteProductB();
    }
}
​
// 使用工厂方法创建产品
public class Client {
    public static void main(String[] args) {
        new ConcreteFactoryA().createProduct();
        new ConcreteFactoryB().createProduct() ;
    }
}
抽象工厂模式(Abstract Factory)
  • 是工厂模式的一种变体,创建一系列相关或相互依赖对象的接口,即 生产一系列产品

  • 在工厂模式基础上,给具体工厂类 添加一个工厂接口,使得工厂类可以多实现

复制代码
// 抽象产品A
public interface ProductA {
    void use();
}
// 具体产品A1
public class ConcreteProductA1 implements ProductA {
    @Override
    public void use() {
        System.out.println("Using ConcreteProductA1");
    }
}
// 具体产品A2
public class ConcreteProductA2 implements ProductA {
    @Override
    public void use() {
        System.out.println("Using ConcreteProductA2");
    }
}
​
// 抽象产品B
public interface ProductB {
    void eat();
}
// 具体产品B1
public class ConcreteProductB1 implements ProductB {
    @Override
    public void eat() {
        System.out.println("Eating ConcreteProductB1");
    }
}
// 具体产品B2
public class ConcreteProductB2 implements ProductB {
    @Override
    public void eat() {
        System.out.println("Eating ConcreteProductB2");
    }
}
​
// 抽象工厂
public interface AbstractFactory {
    ProductA createProductA();
    ProductB createProductB();
}
​
// 具体工厂1
public class ConcreteFactory1 implements AbstractFactory {
    @Override
    public ProductA createProductA() {
        return new ConcreteProductA1();
    }
    @Override
    public ProductB createProductB() {
        return new ConcreteProductB1();
    }
}
​
// 具体工厂2
public class ConcreteFactory2 implements AbstractFactory {
    @Override
    public ProductA createProductA() {
        return new ConcreteProductA2();
    }
    @Override
    public ProductB createProductB() {
        return new ConcreteProductB2();
    }
}
​
// 使用抽象工厂创建产品
public class Client {
    public static void main(String[] args) {
        AbstractFactory factory1 = new ConcreteFactory1();
        ProductA productA1 = factory1.createProductA();
        ProductB productB1 = factory1.createProductB();
        productA1.use();
        productB1.eat();
​
        AbstractFactory factory2 = new ConcreteFactory2();
        ProductA productA2 = factory2.createProductA();
        ProductB productB2 = factory2.createProductB();
        productA2.use();
        productB2.eat();
    }
}

简单工厂模式的工作原理。

  • 前面提到的工厂模式是 通过创建对应工厂来创建产品,允许子类决定实例化哪一个类,符合开放封闭原则

  • 而简单工厂模式(Simple Factory Pattern)不属于 GoF 23 种经典设计模式之一,但是在实际开发中非常常见

  • 通过传入的不同的参数来控制创建哪个具体产品。它的实现较为简单,但不够灵活,违反了开放封闭原则。

复制代码
// 产品接口
public interface Product {
    void use();
}
// 具体产品A
public class ConcreteProductA implements Product {
    @Override
    public void use() {
        System.out.println("Using ConcreteProductA");
    }
}
// 具体产品B
public class ConcreteProductB implements Product {
    @Override
    public void use() {
        System.out.println("Using ConcreteProductB");
    }
}
​
// 简单工厂类
public class SimpleFactory {
    public static Product createProduct(String type) {
        switch (type) {
            case "A":
                return new ConcreteProductA();
            case "B":
                return new ConcreteProductB();
            default:
                throw new IllegalArgumentException("Unknown product type");
        }
    }
}
// 客户端代码
public class Client {
    public static void main(String[] args) {
        Product productA = SimpleFactory.createProduct("A");
        productA.use(); // Output: Using ConcreteProductA
​
        Product productB = SimpleFactory.createProduct("B");
        productB.use(); // Output: Using ConcreteProductB
    }
}

什么是建造者模式?一般用在什么场景?

建造者模式(Builder)使用相同的代码基础构建不同类型的对象,通过将对象构建过程分解为多个较小的步骤来实现此目的,如StringBuilder

两个主要组成部分:建造者和产品 建造者 是负责构建产品的类,产品则是最终构建的对象

复制代码
public class Product {       //最终构建的对象,即产品
    private String partA;
    private String partB;
    private String partC;
    public void setPartA(String partA) {
        this.partA = partA;
    }
    public void setPartB(String partB) {
        this.partB = partB;
    } 
    public void setPartC(String partC) {
        this.partC = partC;
    }
    // other product-related methods
​
}
​
public interface Builder {   //Builder接口或抽象类,定义了构建过程的关键步骤
    void buildPartA();
    void buildPartB();
    void buildPartC();
    Product getResult();
}
  
public class ConcreteBuilder implements Builder {  //实现了Builder接口中定义的方法,以构建具体的Product对象
    private Product product = new Product();
    public void buildPartA() {
        product.setPartA("Part A");
    }
    public void buildPartB() {
        product.setPartB("Part B");
    }
    public void buildPartC() {
        product.setPartC("Part C");
    }    
    public Product getResult() {
        return product;
    } 
}
​
public class Director {      //指导类,它负责使用Builder对象来构建最终的Product对象
    private Builder builder;
​
    public Director(Builder builder) {  //实现依赖倒置原则,依赖于抽象
        this.builder = builder; 
    }
    public void construct() {   //确保Product对象按照指定的顺序创建
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
    }
}
​
Builder builder = new ConcreteBuilder();   //创建建造者
Director director = new Director(builder);  //创建指导
director.construct();     
Product product = builder.getResult();
//这将构建一个Product对象,并将其存储在product变量中。

适用场景

一般情况下,只有复杂对象的创建才需要使用建造者模式,比如一个对象有十几个参数,如果我们用构造器填参,可读性很差,还可能会搞混参数的赋值

例如 hutool 内的 ExecutorBuilder 就提供了建造者模式创建线程池的方法

复制代码
public ExecutorService buildTaskPool() {
return ExecutorBuilder.create()
        .setCorePoolSize(10)
        .setMaxPoolSize(20)
        .setWorkQueue(new LinkedBlockingQueue<>(100))
        .setKeepAliveTime(3L, TimeUnit.SECONDS)
        .setThreadFactory(new ThreadFactoryBuilder().setNamePrefix("task-pool-").build())
        .build();
}
  • 建造者模式可以很容易地增加新的类型,只需要创建新的子类即可,不需要修改现有的代码。

  • 通过建造者模式,可以对 对象的各个部分进行细致的控制,从而更好地管理对象的创建过程。

什么是原型模式 ? 一般用在什么场景?

原型模式也是一种创建型设计模式,主要通过复制(克隆)现有的实例来创建新的对象,避免复杂创建过程,提升创建对象的效率。

在 java 中 可以利用 clone 实现对象的拷贝,

  • 注意:Java 的 clone 仅是浅拷贝,默写场景需要 使用深拷贝避免共享数据的导致错乱

  • 在 Spring 中,将 Bean 的作用范围设置为 prototype,这样每次从容器中获取 Bean 时,都会返回一个新的实例。

浅拷贝 :将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型(指的是该对象内部的引用类型属性),就会拷贝指向原有对象的内存地址

深拷贝:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的

简单来说,就是深复制进行了完全彻底的复制,而浅拷贝不彻底。

一个简单的原型模式实现例子如下

先创建一个原型类:

复制代码
public class Prototype implements Cloneable {     //一个原型类,只需要实现Cloneable接口,覆写clone方法
    @Override
    public Object clone() throws CloneNotSupportedException { 
        Prototype proto = (Prototype) super.clone();           //因为此处的重点是super.clone()这句话
        return proto;
    }
}
Prototype pro=new Prototype();
Prototype pro1=(Prototype)pro.clone(); //克隆 二者地址不同
//不管深浅,拷贝出来的对象的地址是不一样的,只是若对象里面有引用,那么深浅拷贝会不一样
复制代码
@Data
public class Prototype implements Cloneable, Serializable {
​
    private static final long serialVersionUID = 1L;
    private String string;
    private SerializableObject obj;
     
    /* 浅拷贝 */
    public Object clone() throws CloneNotSupportedException {
        Prototype proto = (Prototype) super.clone();
        return proto;
    }
    /* 深拷贝 */
    public Object deepClone() throws IOException, ClassNotFoundException {
        /* 写入当前对象的二进制流 */
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        /* 读出二进制流产生的新对象 */
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    }
     
}
class SerializableObject implements Serializable {
    private static final long serialVersionUID = 1L;
}

结构型模式

什么是适配器模式?一般用在什么场景?

相当于转接头,比如整合老系统的时候,由于老系统的接口已经跟不上新接口的设计,此时就可以利用适配器模式作为中间层,兼容老系统的接口调用。

适配器模式可以分为两种实现方式:类适配器和对象适配器。

类适配器 : 通过多重继承,让适配器类同时继承目标接口和现有类,从而实现接口的适配。但在Java等语言中,由于不支持多重继承,类适配器的实现较为复杂,通常不常用。

复制代码
// 目标接口
public interface Target {
    void request();
}
// 适配者类
public class Adaptee {
    public void specificRequest() {
        System.out.println("Adaptee's specificRequest");
    }
}
// 类适配器
public class Adapter extends Adaptee implements Target {
    @Override
    public void request() {
        specificRequest();
    }
}
​
// 客户端代码
public class Client {
    public static void main(String[] args) {
        Target target = new Adapter();
        target.request();  // Output: Adaptee's specificRequest
    }
}

对象适配器 : 通过组合,让适配器类持有现有类的实例,并实现目标接口**。对象适配器使用组合关系来实现接口的适配**,较为常用。

复制代码
// 目标接口
public interface Target {
    void request();
}
// 适配者类
public class Adaptee {
    public void specificRequest() {
        System.out.println("Adaptee's specificRequest");
    }
}
// 对象适配器
public class Adapter implements Target {  //这里不再是和类适配器一样继承适配器,而是在类中装配适配器对象
    private Adaptee adaptee;
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    @Override
    public void request() {
        adaptee.specificRequest();
    }
}
​
// 客户端代码
public class Client {
    public static void main(String[] args) {
        Adaptee adaptee = new Adaptee();
        Target target = new Adapter(adaptee);
        target.request();  // Output: Adaptee's specificRequest
    }
}

Java 日志中的 slf4j 其实就是使用了适配器模式来统一不同日志框架接口,使得我们不需要修改代码就可以替换不同的底层日志实现。

什么是桥接模式?一般用在什么场景?

桥接模式使用场景比较少,它主要的作用是将抽象和实现解耦,使它们可以独立地变化。

我们熟知的JDBC 就使用了桥接模式,借着它我们来理解下这个设计模式。

JDBC定义了抽象的规范,不同的数据库厂商遵循这些规范,但是它们各自又会有不同的实现。 在使用中,如果我们数据库是 mysql,则传入 com.mysql.jdbc.Driver 驱动实现类,如果要换成 oracle替换实现类为oracle.jdbc.driver.OracleDriver 即可,这就是典型的抽象与实现解耦。

实际执行数据库操作,例如获取连接,实际上就是委托给具体的实现类,JDBC源码如下:

这样设计后,如果更换数据库,除了替换实现类,关于JDBC的使用代码我们不需要做任何修改,这就是桥接模式带来的好处,提升了系统的可扩展性,也符合开闭原则。

代码实例

实现形状颜色解耦

  1. 实现化(Implementor):定义颜色接口

    复制代码
    public interface Color {
        void applyColor();
    }
  2. 具体实现化(Concrete Implementor):实现具体颜色

    复制代码
    public class RedColor implements Color {
        @Override
        public void applyColor() { 
            System.out.println("Applying red color");  //红色
        }
    }
    ​
    public class BlueColor implements Color {   //蓝色
        @Override
        public void applyColor() {
            System.out.println("Applying blue color");
        }
    }
  3. 抽象化(Abstraction):定义形状接口

    复制代码
    public abstract class Shape {
        protected Color color;
        public Shape(Color color) {
            this.color = color;
        }
        public abstract void draw();
    }
  4. 细化抽象化(Refined Abstraction):实现具体形状

    复制代码
    public class Circle extends Shape {
        public Circle(Color color) {
            super(color);
        }
    ​
        @Override
        public void draw() {
            System.out.print("Drawing a circle with ");
            color.applyColor();
        }
    }
    ​
    public class Rectangle extends Shape {
        public Rectangle(Color color) {
            super(color);
        }
    ​
        @Override
        public void draw() {
            System.out.print("Drawing a rectangle with ");
            color.applyColor();
        }
    }
  5. 客户端代码

    复制代码
    public class Client {
        public static void main(String[] args) {
            // 创建红色圆形
            Shape redCircle = new Circle(new RedColor());
            redCircle.draw();  // 输出: Drawing a circle with Applying red color
    ​
            // 创建蓝色矩形
            Shape blueRectangle = new Rectangle(new BlueColor());
            blueRectangle.draw();  // 输出: Drawing a rectangle with Applying blue color
        }
    }

什么是组合模式?一般用在什么场景?

又叫部分整体模式,将对象组合成树状结构以表示"整体------部分"的层次关系

注意,数据必须是树形结构,也因此这个设计模式的应用场景有限,但在业务上还是比较常见,因为很多业务都有后台管理系统,含菜单管理、部门管理等。 例如菜单、子菜单、权限等关系就可以组合成树形结构,部门、子部分以及人员也可以组合成树形结构,这种场景就很契合组合模式的实现。

以部门为示例,展示组织内的部门和人员信息,我们简单看下实现代码:

复制代码
// 抽象组织
interface OrganizationComponent {  
    void showDetails();
}
​
// 组织人员
class Employee implements OrganizationComponent {
    private String name;
    private String position;
​
    public Employee(String name, String position) {
        this.name = name;
        this.position = position;
    }
​
    @Override
    public void showDetails() {
        System.out.println("Employee: " + name + ", Position: " + position);
    }
}
​
// 组织部门
class Department implements OrganizationComponent {
    private String name;
    private List<OrganizationComponent> components = new ArrayList<>();
​
    public Department(String name) {
        this.name = name;
    }
​
    @Override
    public void showDetails() {
        System.out.println("Department: " + name);
        for (OrganizationComponent component : components) {
            component.showDetails();
        }
    }
    public void add(OrganizationComponent component) { //往部门添加人员 或 部门
        components.add(component);
    }
​
    public void remove(OrganizationComponent component) {
        components.remove(component);
    }
}
​
// 使用代码
public class Client {
    public static void main(String[] args) {
        // 创建人员
        OrganizationComponent employee1 = new Employee("John Doe", "Developer");
        OrganizationComponent employee2 = new Employee("Jane Smith", "Designer");
        OrganizationComponent employee3 = new Employee("Emily Davis", "Manager");
​
        // 创建部门
        Department engineeringDepartment = new Department("Engineering Department");
        Department designDepartment = new Department("Design Department");
        Department headDepartment = new Department("Head Department");
​
        // 添加人员到部门
        engineeringDepartment.add(employee1);
        designDepartment.add(employee2);
        headDepartment.add(employee3);
​
        // 添加子部门到上级部门
        headDepartment.add(engineeringDepartment);
        headDepartment.add(designDepartment);
​
        // 显示整个组织结构的详情
        headDepartment.showDetails();
        // 输出
        // Department: Head Department
        // Employee: Emily Davis, Position: Manager
        // Department: Engineering Department
        // Employee: John Doe, Position: Developer
        // Department: Design Department
        // Employee: Jane Smith, Position: Designer
    }
}

通过组合模式,提供统一的接口,简化对层次结构的处理,使得使用方代码更加简洁与灵活。

什么是装饰器模式?一般用在什么场景?

  • 装饰器模式(Decorator Pattern) 主要作用是通过创建包装类 来实现功能的增强,而不是修改原始类

  • 通过创建一个装饰器类,该类实现了与原始对象相同的接口,并持有一个原始对象的引用。通过将原始对象传递给装饰器

最典型的装饰器实现就是 Java 中的I/O类库,示例代码如下:

复制代码
import java.io.*;
public class IOExample {
    public static void main(String[] args) throws IOException {
        File file = new File("test.txt");
        FileInputStream fis = new FileInputStream(file);  //读取文件流
        BufferedInputStream bis = new BufferedInputStream(fis);  //fis被BufferedlnputStream 装饰,提供了缓存的功能
        DataInputStream dis = new DataInputStream(bis); //被 DatalnputStream 装饰,提供了按数据类型读取的功能
​
        while (dis.available() > 0) {
            System.out.println(dis.readLine());
        }
​
        dis.close();
    }
}

可以看到,多个装饰器叠加在一起,实现了多个功能的增强,且不会增加类的实现复杂度,每个装饰器仅需关注自己的加强功能即可,提高代码的灵活性和可维护性。

文件流的代码比较复杂,这里就不展示了,我们简单看个多装饰器叠加的代码实现:

复制代码
// 组件
interface Component {
    void operation();
}
​
// 具体构件
class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("ConcreteComponent operation");
    }
}
​
// 装饰器
abstract class Decorator implements Component {
    protected Component component;   
​
    public Decorator(Component component) {
        this.component = component;
    }
​
    @Override
    public void operation() {
        component.operation();
    }
}
​
// 具体装饰器 A
class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }
​
    @Override
    public void operation() {
        super.operation();
        addedBehavior();
    }
​
    private void addedBehavior() {
        System.out.println("ConcreteDecoratorA added behavior");
    }
}
​
// 具体装饰器 B
class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }
    @Override
    public void operation() {
        super.operation();
        addedState();
    }
    private void addedState() {
        System.out.println("ConcreteDecoratorB added state");
    }
}
​
// 客户端代码
public class Client {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        Component decoratorA = new ConcreteDecoratorA(component);
        Component decoratorB = new ConcreteDecoratorB(decoratorA);
        decoratorB.operation();
        // Output:
        // ConcreteComponent operation
        // ConcreteDecoratorA added behavior
        // ConcreteDecoratorB added state
    }
}

装饰器、适配器、代理、桥接这四种设计模式有什么区别?

这四种设计模式看起来非常相像,有些甚至从代码上来看差不了多少,那它们之间有什么区别呢?

其实设计模式最大的区别就是 设计思路,它们的出发点都是不一样的。

  • 装饰器模式: 目的是在不改变原始类接口、不对原始类复杂化的情况下,对原始类进行本身功能增强,并且支持嵌套多装饰器多功能增强。

  • 适配器模式: 目的是面对不兼容的接口进行适配,主要是兼容的问题,不得已的做法。好比我们出国旅游,每国的插头标准不一样,我们只能做转接头适配。

  • 代理模式: 目的是在不改变原始类接口的情况下,为原始类提供代理类,对其进行访问控制,实现额外功能,它和装饰器的区别在于不是增强原始类本身对应的功能。

  • 桥接模式: 它的目的是将抽象与实现分离,使它们可以独立地变化,例如JDBC场景,不同数据库厂商的不同实现。

什么是外观模式 ? 一般用在什么场景?

外观模式(Facade Pattern)也叫门面模式,是一种结构型设计模式,它提供了一个简化的接口,用于访问复杂系统中的一组接口。将复杂系统的功能封装起来,让客户端可以更方便地使用系统功能而不需要了解其内部复杂结构。

举个例子就清晰了。例如 A系统有 a、b、c三个接口,B需要分别调用 a、b、c三个接口,这样比较麻烦,所以A将 a、b、c封装成一个接口给B调用,对B来说使用起来就方便了,这就是外观模式

不过在使用外观模式的同时,我们也需要考虑接口的设计粒度,如果接口的粒度太大,可复用性就低了,接口的粒度太小,易用性就低了。所以设计是一个权衡的过程,我们需要综合考虑,理论上可复用性为主,然后特殊情况利用外观模式封装一个大接口提供出去。

复制代码
class CPU {         //CPU
    public void start() {
        System.out.println("CPU 启动");
    }
    public void shutdown() {
        System.out.println("CPU 关闭");
    }
}
class Memory {            //内存
    public void start() {
        System.out.println("内存启动");
    }
    public void shutdown() {
        System.out.println("内存关闭");
    }
}
​
class HardDrive {     //硬盘
    public void start() {
        System.out.println("硬盘启动");
    }
    public void shutdown() {
        System.out.println("硬盘关闭");
    }
​
}
class ComputerFacade {         //外观类,封装了计算机系统的一组复杂接口,包括 CPU、内存和硬盘的启动和关闭
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;
​
    public ComputerFacade() {
        cpu = new CPU();
        memory = new Memory();
        hardDrive = new HardDrive();
    }
    
    public void start() {       //启动计算机
        System.out.println("计算机启动开始");
        cpu.start();
        memory.start();
        hardDrive.start();
        System.out.println("计算机启动完成");
    }
    
    public void shutdown() {    //关闭计算机
        System.out.println("计算机关闭开始");
        cpu.shutdown();
        memory.shutdown();
        hardDrive.shutdown();
        System.out.println("计算机关闭完成");
    }
}
复制代码
ComputerFacade computerFacade = new ComputerFacade();
computerFacade.start();
// 输出:
// 计算机启动开始
// CPU 启动
// 内存启动
// 硬盘启动
// 计算机启动完成
​
computerFacade.shutdown();
// 输出:
// 计算机关闭开始
// CPU 关闭
// 内存关闭
// 硬盘关闭
// 计算机关闭完成

什么是享元模式?一般用在什么场景?

享元模式(Flyweight Pattern)是一种结构型设计模式,主要用于通过共享来减少内存占用,提高内存效率。

例如一个系统中可能会存在大量的重复对象,并且这些对象实际是不可变的,在设计上我们就可以仅在内存中保留一份实例,然后多处引用即可,这样就能节省内存的开销。

在 Integer 中就采用了享元模式,即 integer 中缓冲池。即 Integer -128 到 127 之内的相等,而超过这个范围用 == 就不对了,因为这个范围内采用了享元模式,本质就是同一个对象,所以用 == 判断相等。

具体原理可看 Java基础 29.什么是Java的Integer缓冲池?

示例:

假设我们要绘制棋盘上的棋子,棋子的种类有很多,但是每种棋子的形状和颜色是固定的。我们可以使用享元模式来共享相同种类的棋子对象,从而减少对象的创建数量。

复制代码
// 棋子接口
interface ChessPiece {
    void setColor(String color);
    void display(int x, int y);
}
// 具体棋子类
class ConcreteChessPiece implements ChessPiece {
    private String color;
    public ConcreteChessPiece(String color) {
        this.color = color;
    }
    @Override
    public void setColor(String color) {
        this.color = color;
    }
    @Override
    public void display(int x, int y) {
        System.out.println("Chess Piece color: " + color + ", position: (" + x + "," + y + ")");
    }
}
复制代码
// 享元工厂类
class ChessPieceFactory {
    private Map<String, ChessPiece> chessPieces;
    public ChessPieceFactory() {
        this.chessPieces = new HashMap<>();    //用集合存chessPieces
    }
    
    public ChessPiece getChessPiece(String color) {  //返回单例
        ChessPiece chessPiece = chessPieces.get(color);
        if (chessPiece == null) {
            chessPiece = new ConcreteChessPiece(color);
            chessPieces.put(color, chessPiece);
        }
        return chessPiece;
    }
}
复制代码
// 客户端代码
public class Client {
    public static void main(String[] args) {
        ChessPieceFactory chessPieceFactory = new ChessPieceFactory();
​
        ChessPiece blackPiece1 = chessPieceFactory.getChessPiece("black");  
        ChessPiece blackPiece2 = chessPieceFactory.getChessPiece("black");
        ChessPiece whitePiece1 = chessPieceFactory.getChessPiece("white");
        ChessPiece whitePiece2 = chessPieceFactory.getChessPiece("white");
    
        blackPiece1.display(1, 2);  // 输出:Chess Piece color: black, position: (1,2)
        blackPiece2.display(3, 4);  // 输出:Chess Piece color: black, position: (3,4)
        whitePiece1.display(5, 6);  // 输出:Chess Piece color: white, position: (5,6)
        whitePiece2.display(7, 8);  // 输出:Chess Piece color: white, position: (7,8)
    }
}

什么是代理模式?一般用在什么场景?

代理模式就是 通过一个代理对象来控制对另一个对象的访问,在不改变原始对象的情况下,提供了对原始对象的间接访问,实现额外的功能。

这个模式在我们业务中太常见了,例如动态代理,Spring AOP ,RPC 框架也是使用了动态代理才使得调用远程方法和本地方法一样。

所以,统一报错、监控、限流、鉴权等等,需要跟业务解耦的功能,我们基本上都是使用代理类进行统一处理的。 简单举个计数代理的例子,比如统计文档展示的次数,通过 DocumentProxy 代理类就附加了这个功能:

核心是创建一个代理类,该类实现了与原始对象相同的接口,并持有一个原始对象的引用。在代理类的方法中,我们可以添加额外的功能,然后将请求转发给原始对象进行处理。

复制代码
// 图片接口
interface Image {
    void display();
}
// 具体图片类
class RealImage implements Image {
    private String filename;
    public RealImage(String filename) {
        this.filename = filename;
        loadImageFromDisk();
    }
    private void loadImageFromDisk() {
        System.out.println("Loading " + filename + " from disk.");
    }
    @Override
    public void display() {
        System.out.println("Displaying " + filename);
    }
}
复制代码
// 代理图片类
class ProxyImage implements Image {
    private RealImage realImage;   //引入具体图片类
    private String filename;        //实现了对原始图片对象的控制,而不需要修改原始图片类的代码
​
    public ProxyImage(String filename) {
        this.filename = filename;
    }
    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        beforeDisplay();
        realImage.display();
    }
    private void beforeDisplay() {
        System.out.println("Before displaying " + filename + ", do some pre-processing.");
    }
}
复制代码
// 客户端代码
public class Client {
    public static void main(String[] args) {
        Image image1 = new ProxyImage("image1.jpg");
        Image image2 = new ProxyImage("image2.jpg");
​
        image1.display();  // 输出:Before displaying image1.jpg, do some pre-processing.
                           //        Loading image1.jpg from disk.
                           //        Displaying image1.jpg
        image2.display();  // 输出:Before displaying image2.jpg, do some pre-processing.
                           //        Loading image2.jpg from disk.
                           //        Displaying image2.jpg
    }
}

行为型模式

什么是观察者模式?一般用在什么场景?

观察者模式其实也称为发布订阅模式,它定义了对象之间的一种一对多 的依赖关系,让多个观察者对象 同时监听某一个主题对象。当主题对象状态发生变化时,它会通知所有观察者对象。

目的:将观察者和被观察者代码解耦,使得一个对象或者事件的变更,让不同观察者可以有不同的处理,非常灵活,扩展性很强,是事件驱动编程的基础。

观察者模式的组成部分

  • Subject(主题/被观察者): 状态发生变化时,通知所有注册的观察者

  • Observer(观察者): 接收来自主题的更新通知,并进行相应的操作。

  • ConcreteSubject(具体主题): 实现具体的主题对象,保存需要被观察的状态。

  • ConcreteObserver(具体观察者): 实现具体的观察者对象,更新自己以与主题的状态同步

我们以一个新闻发布系统来理解下观察者模式。

1)定义接口

新闻发布者(Subject)可以发布新闻,观察者(Observer)是订阅者,希望获取新闻更新

2)实现具体类

  • 具体新闻发布者(Concrete Subject)维护一个订阅者列表,并在发布新闻时通知他们

  • 具体观察者(Concrete Observer)如报纸、新闻网站等,实现更新方法来展示新闻

3)订阅和退订

订阅者可以订阅(注册)或退订(注销)新闻发布者的新闻。

4)发布新闻

当新闻发布者有新新闻时,"观察者更新自己的新闻内容。它通知所有订阅的观察者

5)客户端代码

客户端代码创建新闻发布者和观察者对象,订阅者选择订新闻,并在接收到新闻时更新显示。

复制代码
// 定义观察者接口
interface Observer {
    void update(String news);
}
// 定义主题接口
interface Subject {
    void attach(Observer o);
    void detach(Observer o);
    void notifyObservers();
}
// 具体新闻发布者
class NewsPublisher implements Subject {
    private List<Observer> observers = new ArrayList<>();
​
    public void attach(Observer o) {
        observers.add(o);
    }
    public void detach(Observer o) {
        observers.remove(o);
    }
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update("新闻更新");
        }
    }
    public void publishNews() {
        // 假设这里是新闻发布逻辑,会通知发布者里面集合的观察者
        notifyObservers();
    }
}
​
// 具体观察者
class NewsPaper implements Observer {
    public void update(String news) {
        System.out.println("新报 " + news);
    }
}
​
// 客户端代码
public class Client {
    public static void main(String[] args) {
        NewsPublisher publisher = new NewsPublisher();  //发布者
        Observer newsPaper = new NewsPaper();   //观察者
​
        publisher.attach(newsPaper);     //导入观察者
        publisher.publishNews();        //发布新闻
​
        publisher.detach(newsPaper);    //移除观察者
        publisher.publishNews(); // 此时报纸订阅者不会接收到新闻
    }
}

例如消息队列、 Spring 内的监听器机制等都是其应用场景

什么是迭代器模式?一般用在什么场景?

迭代器模式,它提供一种方法顺序访问一个集合对象中的各个元素,而又不暴露该对象(可能是数组、链表、树等等)的内部实现。

将遍历逻辑与集合对象的实现分离,提供一致的遍历方式,使得代码统一化,在不改变遍历代码的情况下就能替换底层集合实现。

像 Java 的java.util.Iterator接口和Iterable接口是迭代器模式的直接应用。所有集合类(如ArrayList、HashSet、LinkedList 等)都实现了 Iterable 接口,并提供了 iterator() 方法来获取迭代器,例如,遍历 ArrayList 中的元素:

复制代码
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
​
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

再比如 JDBC 的ResultSet接口遍历数据库査询结果,也是迭代器模式的实现。

迭代器模式的组成部分

  • lterator(迭代器接口): 定义访问和历元素的接口

  • Aggregate(聚合接口): 定义创建迭代器的接口

  • Concretelterator(具体迭代器): 实现迭代器接口,负责遍历聚合对象中的元素

  • ConcreteAggregate(具体聚合类): 实现聚合接口,返回一个具体的迭代器实例。

我们来简单实现一个迭代器

复制代码
//定义迭代器接口
interface Iterator<T> {
    boolean hasNext();
    T next();
}
//定义聚合接口
interface Aggregate<T> {
    Iterator<T> createIterator();
}
​
//定义具体迭代器
class ConcreteIterator<T> implements Iterator<T> {
    private List<T> items;
    private int position = 0;  //当前遍历位置
    public ConcreteIterator(List<T> items) {
        this.items = items;
    }
    @Override
    public boolean hasNext() {    //如果进行集合遍历时位置<集合大小,则返回true
        return position < items.size();
    }
    @Override
    public T next() {  //获取当前遍历位置的下一个元素
        return items.get(position++);
    }
}
//定义具体聚合类
class ConcreteAggregate<T> implements Aggregate<T> {
    private List<T> items = new ArrayList<>();
    public void addItem(T item) {   //添加元素方法
        items.add(item);
    }
    @Override
    public Iterator<T> createIterator() {
        return new ConcreteIterator<>(items);
    }
}
//简单使用
public class Client {
    public static void main(String[] args) {
        ConcreteAggregate<String> aggregate = new ConcreteAggregate<>();
        aggregate.addItem("Item 1");    //添加集合元素
        aggregate.addItem("Item 2");
        aggregate.addItem("Item 3");
​
        Iterator<String> iterator = aggregate.createIterator(); //返回对应该集合的迭代器
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

什么是命令模式?一般用在什么场景?

命令模式(Command Pattern)是一种行为设计模式,它将请求封装成对象,使得请求参数化,便于对请求排队或记录请求日志,以及支持可撤销的操作。

大部分编程语言都不支持以函数作为参数传递,利用命令模式就可以实现这点。听起来稍微有点抽象,其实命令模式非常少见,被问的频率极低,稍作了解即可。

简单看下以开关灯为例实现命令模式的代码,更容易理解上面那段含义:

复制代码
// 命令接口
interface Command {
    void execute();
}
// 接收者
class Light {   
    public void on() {    //用于控制灯光的开关
        System.out.println("The light is on.");
    }
    public void off() {
        System.out.println("The light is off.");
    }
}
​
// 具体命令   开灯
class LightOnCommand implements Command {
    private Light light;
    public LightOnCommand(Light light) {
        this.light = light;
    }
    @Override
    public void execute() {
        light.on();
    }
}
// 具体命令   关灯
class LightOffCommand implements Command {
    private Light light;
    public LightOffCommand(Light light) {
        this.light = light;
    }
    @Override
    public void execute() {
        light.off();
    }
}
// 调用者,传入了开关命令,因此可以开关灯
class RemoteControl {
    private Command command;
    public void setCommand(Command command) {
        this.command = command;
    }
    public void pressButton() {
        command.execute();
    }
}
​
​
// 客户端代码
public class Client {
    public static void main(String[] args) {
        Light light = new Light();   
        Command lightOn = new LightOnCommand(light);  //开灯
        Command lightOff = new LightOffCommand(light);   //关灯
​
        RemoteControl remote = new RemoteControl();
        remote.setCommand(lightOn);     
        remote.pressButton();   //开灯
​
        remote.setCommand(lightOff);
        remote.pressButton();    //关灯
    }
}

什么是状态模式?一般用在什么场景?

状态模式 允许对象在其内部状态发生改变时改变其行为,将状态的行为封装在独立的类中,并将这些状态对象组合在拥有状态的对象中,这样就可以在状态改变时切换状态对象,从而改变对象的行为

它主要是状态机的一种实现方式,状态机可分为: 状态、事件、动作三个部分。事件的触发就会导致状态的改变,并且可作出一定的动作(也可以没有动作,只有状态的改变)

复制代码
// 定义状态接口
interface State {
    void handle(Context context);
}
// 上下文环境,状态模式的核心,它持有一个 State 对象,并提供了 setState 方法来改变当前状态
class Context {
    private State state;
    public Context(State state) {
        this.state = state;
    }
    public void setState(State state) {
        this.state = state;
    }
    public void request() {
        state.handle(this);
    }
}
// 具体实现类
//AB每个类在处理请求时都会打印一条消息,并且会改变上下文 Context 的状态为另一种状态
class ConcreteStateA implements State {
    @Override
    public void handle(Context context) {
        System.out.println("State A is handling the request.");
        context.setState(new ConcreteStateB());
    }
}
class ConcreteStateB implements State {
    @Override
    public void handle(Context context) {
        System.out.println("State B is handling the request.");
        context.setState(new ConcreteStateA());
    }
}
​
​
public class Client {
    public static void main(String[] args) {
        Context context = new Context(new ConcreteStateA());   //新建A状态环境
        context.request(); // State A is handling the request.   //请求A状态,然后设置当前状态为B
        context.request(); // State B is handling the request.
        context.request(); // State A is handling the request.
        context.request(); // State B is handling the request.
    }
}

什么是责任链模式?一般用在什么场景?

责任链模式允许将多个对象连接成一条链,并且沿着这条链传递请求,让多个对象都有机会处理这个请求,请求会顺着链传递,直到某个对象处理它为止。

它主要避免了请求发送者和接受者之间的耦合,增强了系统的灵活性和可扩展性。

在很多场景都能看到责任链模式,比如日志的处理,不同级别不同输出。再比如 Spring 过滤器的 Chain 也是责任链模式。

下面是一个简单的日志处理示例代码。

  • 日志处理器有三种类型: 控制台日志处理器(ConsoleLogger)、文件日志处理器(FileLogger)、错误日志处理器(ErrorLogger)。

  • 日志处理器按照优先级进行处理,如果当前处理器不能处理,则将请求传递给下一个处理器。

复制代码
// 责任链模式的抽象日志类
abstract class Logger {
    public static int INFO = 1;
    public static int DEBUG = 2;
    public static int ERROR = 3;
    protected int level;
    protected Logger nextLogger;   //日志中可以存下一个日志
    public void setNextLogger(Logger nextLogger) {
        this.nextLogger = nextLogger;
    }
    public void logMessage(int level, String message) {
        if (this.level <= level) {
            write(message);
        }
        if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }
    protected abstract void write(String message);
}
​
// 具体处理器类:控制台日志处理器
class ConsoleLogger extends Logger {
    public ConsoleLogger(int level) {
        this.level = level;
    }
    @Override
    protected void write(String message) {
        System.out.println("Standard Console::Logger: " + message);
    }
}
// 具体处理器类:文件日志处理器
class FileLogger extends Logger {
    public FileLogger(int level) {
        this.level = level;
    }
    @Override
    protected void write(String message) {
        System.out.println("File::Logger: " + message);
    }
}
// 具体处理器类:错误日志处理器
class ErrorLogger extends Logger {
    public ErrorLogger(int level) {
        this.level = level;
    }
    @Override
    protected void write(String message) {
        System.out.println("Error Console::Logger: " + message);
    }
}
​
// 客户端代码
public class ChainPatternDemo {
    private static Logger getChainOfLoggers() {  //获取日志责任链
        Logger errorLogger = new ErrorLogger(Logger.ERROR);
        Logger fileLogger = new FileLogger(Logger.DEBUG);
        Logger consoleLogger = new ConsoleLogger(Logger.INFO);
        errorLogger.setNextLogger(fileLogger);  //存下一个日志
        fileLogger.setNextLogger(consoleLogger);   //存再下一个日志
        return errorLogger;  //最后返回
    }
    public static void main(String[] args) {
        Logger loggerChain = getChainOfLoggers();
        loggerChain.logMessage(Logger.INFO, "mianshiya.com");
        loggerChain.logMessage(Logger.DEBUG, "小程序:面试鸭");
        loggerChain.logMessage(Logger.ERROR, "网页端:mianshiya.com");
    }
}

什么是中介者模式?一般用在什么场景?

中介模式通过引入了一个中介对象,来封装一组对象之间的交互,来避免对象之间的直接交互。通过引入一个中介者对象,使对象之间的关系变得简单且易于维护。

听起来不就是和现实生活中的中介一样嘛。

引入中介模式的原因:多对象交互可能会使得关系图很混乱,代码也不清晰,让多对象都和中介交互就能避免这点

聊天室实现其实就是运用了中介模式。信息的传递都由服务器这个中介来做,所有用户都把消息发给服务器,不然如果点对点传输大家可以想象下有多复杂。我们可以简单的看下聊天室的实现:

复制代码
interface ChatMediator {       //中介者接口
    void sendMessage(String message, User user);
    void addUser(User user);
}
class ChatMediatorImpl implements ChatMediator { //具体中介者
    private List<User> users;   //存储加入的用户
    public ChatMediatorImpl() {
        this.users = new ArrayList<>();
    }
    @Override
    public void addUser(User user) {
        this.users.add(user);
    }
    @Override
    public void sendMessage(String message, User user) {
        for (User u : this.users) {
            if (u != user) {
                u.receive(message);
            }
        }
    }
}
​
abstract class User {       //抽象用户
    protected ChatMediator mediator;
    protected String name;
    public User(ChatMediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }
    public abstract void send(String message);
    public abstract void receive(String message);
}
​
class UserImpl extends User {    //用户
    public UserImpl(ChatMediator mediator, String name) {
        super(mediator, name);
    }
    @Override
    public void send(String message) {
        System.out.println(this.name + " sends: " + message);
        mediator.sendMessage(message, this);
    }
    @Override
    public void receive(String message) {
        System.out.println(this.name + " receives: " + message);
    }
}
public class ChatClient {    //客户端
    public static void main(String[] args) {
        ChatMediator mediator = new ChatMediatorImpl();
        User user1 = new UserImpl(mediator, "Alice");
        User user2 = new UserImpl(mediator, "Bob");
        User user3 = new UserImpl(mediator, "Charlie");
​
        mediator.addUser(user1);   //用户加入中介
        mediator.addUser(user2);
        mediator.addUser(user3);
​
        user1.send("Hello, everyone!");
    }
}

什么是访问者模式?一般用在什么场景?

访问者模式它将数据结构与操作分离,使得你可以在不改变数据结构的前提下定义新的操作。访问者模式通过将操作封装到独立的访问者对象中,使得新的操作可以很容易地添加到系统中。

例如对象序列化场景,比如需要将对象转成 JSON 或者 XML 等格式,利用访问者模式将对象的结构和序列化操作分离,这样就能很方便的扩展新的序列化格式。先看下示例代码:

复制代码
//访问者接口(类比序列化接口)
interface Visitor {
    void visit(ElementA element);
    void visit(ElementB element);
}
//具体访问者类(类比具体序列化实现)
class ConcreteVisitor implements Visitor {
    @Override
    public void visit(ElementA element) {
        System.out.println("Processing ElementA: " + element.getName());
    }
    @Override
    public void visit(ElementB element) {
        System.out.println("Processing ElementB: " + element.getName());
    }
}
​
//需要对序列化的元素接口
interface Element {
    void accept(Visitor visitor);
}
​
// 具体被序列化的元素A
class ElementA implements Element {
    private String name;
    public ElementA(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
// 具体被序列化的元素B
class ElementB implements Element {
    private String name;
    public ElementB(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
​
//对象结构,包含很多元素
class ObjectStructure {
    private List<Element> elements = new ArrayList<>();
​
    public void addElement(Element element) {
        elements.add(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.addElement(new ElementA("Element A1"));
        objectStructure.addElement(new ElementB("Element B1"));
        objectStructure.addElement(new ElementA("Element A2"));
        
        //访问者 (如果是序列化场景,ConcreteVisitor 可以当做 JSON 序列化)
        Visitor visitor = new ConcreteVisitor();
        objectStructure.accept(visitor);
​
        // 假设后面要替换 xml 序列化,仅需新建 xml 的访问者,然后传入到对象内部即可
        Visitor visitorXML = new XMLVisitor();
        objectStructure.accept(visitorXML);
    }
}

可以看到,将数据结构和操作分离开之后,如果要替换具体的操作,仅需新增一个操作即可,不需要修改任何数据结构,这就符合开闭原则,也保证了类的职责单一。

什么是备忘录模式,一般用在什么场景?

备忘录模式指的是在不违背封装原则的前提下,捕获对象内部的状态,将其保存在外部,便于后面对象恢复之前的状态,使得系统更具灵活性和可维护性。

听来像不像保留个快照作为备份,后面基于这个快照进行恢复? 所以备忘录模式其实也叫快照模式。主要用于撤销、恢复等场景。

来看下示例代码,我先大致解释一下几个类的含义:

  • Memento,存储状态的备忘录

  • Originator,需要备份状态的对象类

  • Caretaker,管理者,仅保存备忘录

复制代码
// 备忘录类
class Memento {
    private String state;
    public Memento(String state) {
        this.state = state;
    }
    public String getState() {
        return state;
    }
}
​
// 需要备份状态的对象类
class Originator {
    private String state;
    public void setState(String state) {
        this.state = state;
        System.out.println("State set to: " + state);
    }
    public String getState() {
        return state;
    }
    public Memento createMemento() {
        return new Memento(state);
    }
    public void restoreMemento(Memento memento) {
        this.state = memento.getState();
        System.out.println("State restored to: " + state);
    }
}
//管理存储类
class Caretaker {
    private Memento memento;
    public Memento getMemento() {
        return memento;
    }
    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}
​
//客户端类
public class Client {
    public static void main(String[] args) {
        Originator originator = new Originator();  //对象类
        Caretaker caretaker = new Caretaker();       //存储类
​
        originator.setState("State1");
        caretaker.setMemento(originator.createMemento()); //保存快照
​
        originator.setState("State2");
        System.out.println("Current State: " + originator.getState());
​
        originator.restoreMemento(caretaker.getMemento());    //恢复快照
        System.out.println("Restored State: " + originator.getState());
    }
}

如果直接利用 set 方法修改内部状态,就违反了封装的原则,因为如果你暴露了 set 方法,则对象内部的状态很可能被别的业务调用修改了。而 restoreMemento 是一个很清晰的方法定义,即恢复之前的状态,不会被乱用。这也是备忘录模式的前提,不违反封装原则。

相关推荐
CrissChan9 分钟前
Pycharm 函数注释
java·前端·pycharm
C++ 老炮儿的技术栈41 分钟前
UDP 与 TCP 的区别是什么?
开发语言·c++·windows·算法·visual studio
启航挨踢1 小时前
java学习电子书推荐
java
wgslucky1 小时前
Dubbo报错:module java.base does not “opens java.lang“ to unnamed module
java·开发语言·dubbo
whyeekkk1 小时前
python打卡第48天
开发语言·python
DougLiang2 小时前
关于easyexcel动态下拉选问题处理
java·开发语言
mochensage2 小时前
C++信息学竞赛中常用函数的一般用法
java·c++·算法
计蒙不吃鱼3 小时前
一篇文章实现Android图片拼接并保存至相册
android·java·前端
小海编码日记3 小时前
Java八股-JVM & GC
java
全职计算机毕业设计3 小时前
基于Java Web的校园失物招领平台设计与实现
java·开发语言·前端