【面试】Java中的多种设计模式(十种主要设计模式)

发布时间:2023年12月26日

Java中的多种设计模式(十种主要设计模式)

在这里插入图片描述

文章概述

设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。它是软件工程中常见问题的解决方案的一种描述或模板。设计模式可以提供一种通用的、可重用的解决方案,帮助开发人员解决某类问题时采用一种结构良好且经过验证的方法。

设计模式并不是可以直接转化为代码的东西,而是一种解决问题的思路、一种经验的总结。它在面向对象软件设计中起到指导作用,通过设计模式,开发人员能够更容易地理解代码结构,更快地定位和解决问题。

设计模式的主要目标包括:

  1. 提高代码的可重用性: 设计模式可以使开发人员将经过验证的设计思想和方法应用于新的问题,从而提高代码的可重用性。

  2. 提高代码的可维护性: 设计模式能够提供一种清晰的结构,使得代码更易于理解和维护。

  3. 降低代码的耦合性: 设计模式通过定义良好的接口和抽象类,有助于降低代码组件之间的依赖关系,减少耦合性。

  4. 提高代码的可扩展性: 设计模式通过强调松散耦合,使得系统更容易扩展和修改。

一些常见的设计模式包括单例模式、工厂模式、观察者模式、策略模式等。这些模式都有明确定义的结构和角色,开发人员可以根据问题的性质选择适当的设计模式来解决。设计模式是一种在软件开发中非常有用的实践,但也需要根据具体情况慎重选择和应用。

设计模式列举

Java 中常用的设计模式有很多,其中一些主要的设计模式包括:

  1. 单例模式(Singleton Pattern):

    • 确保一个类只有一个实例,并提供一个全局访问点。
    • 示例:java.lang.Runtimejava.awt.Desktop
  2. 工厂模式(Factory Pattern):

    • 定义一个创建对象的接口,但由子类决定实例化哪个类。
    • 示例:java.util.Calendarjava.text.NumberFormat
  3. 抽象工厂模式(Abstract Factory Pattern):

    • 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
    • 示例:javax.xml.parsers.DocumentBuilderFactory
  4. 建造者模式(Builder Pattern):

    • 将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
    • 示例:java.lang.StringBuilderjava.nio.ByteBuffer
  5. 原型模式(Prototype Pattern):

    • 通过复制现有对象来创建新对象,而不是通过实例化。
    • 示例:java.lang.Object#clone()
  6. 适配器模式(Adapter Pattern):

    • 将一个类的接口转换成客户希望的另一个接口。
    • 示例:java.util.Arrays#asList()java.io.InputStreamReader
  7. 装饰器模式(Decorator Pattern):

    • 动态地给一个对象添加一些额外的职责,就扩展功能而言,装饰模式比生成子类更灵活。
    • 示例:java.io中的诸多类,如BufferedReader
  8. 观察者模式(Observer Pattern):

    • 定义对象间的一对多依赖,当一个对象状态改变时,所有依赖它的对象都得到通知并被自动更新。
    • 示例:java.util.Observerjava.util.Observable
  9. 策略模式(Strategy Pattern):

    • 定义一系列算法,将每个算法封装起来,并使它们可以互换。
    • 示例:java.util.Comparatorjava.util.Collections#sort()
  10. 责任链模式(Chain of Responsibility Pattern):

    • 使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。
    • 示例:java.util.logging.Logger#log()

这些设计模式提供了在不同场景下解决问题的通用解决方案,有助于提高代码的可维护性、可扩展性和重用性。在实际开发中,根据具体的问题和需求选择合适的设计模式是很重要的。

单例模式详解

单例模式(Singleton Pattern) 是一种创建型设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式适用于需要全局访问点且只能有一个实例的场景,例如线程池、缓存、日志对象等。

单例模式的特点:

  1. 单一实例: 单例模式确保类只有一个实例,提供一个全局访问点。
  2. 延迟实例化(Lazy Initialization): 实例只有在第一次被请求时才被创建,避免了不必要的资源开销。
  3. 全局访问点: 提供一个全局的访问点,使得其他类可以轻松地访问该实例。

单例模式的实现方式:

1. 饿汉式(Eager Initialization):
public class Singleton {
    private static final Singleton instance = new Singleton();

    // 私有构造方法,防止外部实例化
    private Singleton() {}

    // 全局访问点
    public static Singleton getInstance() {
        return instance;
    }
}

在饿汉式中,实例在类加载时就被创建,因此保证了线程安全。但是可能会导致不必要的资源浪费,因为实例在整个生命周期中都会存在,即使没有被用到。

2. 懒汉式(Lazy Initialization):
public class Singleton {
    private static Singleton instance;

    // 私有构造方法,防止外部实例化
    private Singleton() {}

    // 全局访问点,使用双重检查锁定来保证线程安全
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

懒汉式在第一次被请求时才创建实例,避免了不必要的资源浪费。但是需要注意,需要使用双重检查锁定(Double-Checked Locking)来保证在多线程环境中的线程安全性。

单例模式的应用场景:

  1. 资源共享: 单例模式可以确保多个模块使用同一个实例,共享资源。
  2. 控制实例数量: 有些情况下,系统只需要一个实例,通过单例模式可以控制实例的数量。
  3. 全局访问点: 提供一个全局的访问点,方便其他类调用。

两个经典例子分别是:

  1. Java中的Runtime类: Runtime类的实例是单例的,可以通过Runtime.getRuntime()方法获取,用于与运行时环境交互。

    Runtime runtime = Runtime.getRuntime();
    
  2. 日志系统中的Logger类: 许多日志框架使用单例模式来管理日志实例,以确保所有的日志输出都通过同一个日志对象进行管理。

    Logger logger = Logger.getLogger("MyLogger");
    

这两个例子都体现了单例模式在实际应用中的常见场景和优势。

工厂模式详解

工厂模式(Factory Pattern) 是一种创建型设计模式,其主要目的是提供一个接口来创建对象,但允许子类在运行时更改创建的类。工厂模式解决了直接在代码中使用构造函数创建对象的问题,使得代码更具灵活性和可维护性。

工厂模式的主要组成部分:

  1. 抽象产品(Abstract Product): 定义了产品的接口,声明了产品的方法。
  2. 具体产品(Concrete Product): 实现了抽象产品接口的具体类,是工厂模式创建的对象。
  3. 抽象工厂(Abstract Factory): 声明了创建抽象产品的方法,是工厂模式的核心接口。
  4. 具体工厂(Concrete Factory): 实现了抽象工厂接口,负责创建具体产品的对象。

工厂模式的实现方式:

1. 简单工厂模式(Simple Factory Pattern):
public interface Product {
    void produce();
}

public class ConcreteProductA implements Product {
    @Override
    public void produce() {
        System.out.println("Product A is produced.");
    }
}

public class ConcreteProductB implements Product {
    @Override
    public void produce() {
        System.out.println("Product B is produced.");
    }
}

public class SimpleFactory {
    public static Product createProduct(String type) {
        if ("A".equals(type)) {
            return new ConcreteProductA();
        } else if ("B".equals(type)) {
            return new ConcreteProductB();
        }
        throw new IllegalArgumentException("Invalid product type");
    }
}

在简单工厂模式中,通过一个工厂类的静态方法来创建产品对象。这种方式简单易懂,但不符合开闭原则,如果需要新增产品类型,需要修改工厂类的代码。

2. 工厂方法模式(Factory Method Pattern):
public interface Product {
    void produce();
}

public class ConcreteProductA implements Product {
    @Override
    public void produce() {
        System.out.println("Product A is produced.");
    }
}

public class ConcreteProductB implements Product {
    @Override
    public void produce() {
        System.out.println("Product B is produced.");
    }
}

public interface Factory {
    Product createProduct();
}

public class ConcreteFactoryA implements Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductA();
    }
}

public class ConcreteFactoryB implements Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductB();
    }
}

在工厂方法模式中,定义一个抽象的工厂接口,由具体的工厂类实现,每个工厂类负责创建特定类型的产品。这样可以满足开闭原则,添加新的产品类型时只需新增相应的工厂类。

工厂模式的应用场景:

  1. 对象的创建复杂: 当一个对象的创建过程比较复杂,包括多个步骤或者涉及到一些条件判断时,可以使用工厂模式封装这些复杂的创建过程。
  2. 类不希望直接暴露在外部: 当类的实现细节发生变化时,不希望直接修改客户端的代码,可以通过工厂模式来隐藏具体的实现。

两个经典的例子是:

  1. Java中的Calendar类: Calendar类是一个抽象工厂类,提供了静态方法getInstance(),根据不同的实现子类(如GregorianCalendar)来创建不同的日历对象。

    Calendar calendar = Calendar.getInstance();
    
  2. Java中的Executor框架: Executor框架提供了一系列工厂方法,用于创建不同类型的线程池,如Executors.newFixedThreadPool()Executors.newCachedThreadPool()等。

    ExecutorService executorService = Executors.newFixedThreadPool(5);
    

这两个例子展示了工厂模式在实际应用中的灵活性和可扩展性,通过工厂模式可以更好地组织和管理对象的创建过程。

抽象工厂模式详解

抽象工厂模式(Abstract Factory Pattern) 是一种创建型设计模式,与工厂方法模式类似,它也是用于创建一系列相关或相互依赖的对象,但抽象工厂模式更强调一系列相关的产品对象的创建,形成一个产品族,而不仅仅是一个单一的产品。

抽象工厂模式的主要组成部分:

  1. 抽象产品(Abstract Product): 定义了产品的接口,声明了产品的方法。
  2. 具体产品(Concrete Product): 实现了抽象产品接口的具体类,是工厂模式创建的对象。
  3. 抽象工厂(Abstract Factory): 声明了一系列创建抽象产品的方法,形成一个产品族的接口。
  4. 具体工厂(Concrete Factory): 实现了抽象工厂接口,负责创建一系列相关的具体产品。

抽象工厂模式的实现方式:

// 抽象产品A
public interface ProductA {
    void useA();
}

// 具体产品A1
public class ConcreteProductA1 implements ProductA {
    @Override
    public void useA() {
        System.out.println("Product A1 is used.");
    }
}

// 具体产品A2
public class ConcreteProductA2 implements ProductA {
    @Override
    public void useA() {
        System.out.println("Product A2 is used.");
    }
}

// 抽象产品B
public interface ProductB {
    void useB();
}

// 具体产品B1
public class ConcreteProductB1 implements ProductB {
    @Override
    public void useB() {
        System.out.println("Product B1 is used.");
    }
}

// 具体产品B2
public class ConcreteProductB2 implements ProductB {
    @Override
    public void useB() {
        System.out.println("Product B2 is used.");
    }
}

// 抽象工厂
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();
    }

在抽象工厂模式中,抽象工厂接口定义了一系列创建相关产品的方法,每个具体工厂类实现了这些方法,分别创建一系列具体的产品对象。这样,客户端可以通过选择具体的工厂来创建一整套相关的产品。

抽象工厂模式的应用场景:

  1. 一系列相关产品的创建: 当需要创建一系列相关或相互依赖的产品对象时,使用抽象工厂模式可以确保这些产品对象之间的兼容性。
  2. 系统独立于其产品的创建、组合和表示: 客户端通过抽象接口与产品的系列进行交互,使得客户端与具体实现解耦。

两个经典的例子是:

  1. 图形界面库中的抽象工厂: 在图形界面库中,抽象工厂可以定义创建按钮、文本框等界面元素的方法,而具体工厂可以分别实现这些方法以创建特定风格(如Windows风格、Mac风格)的界面元素。

  2. 数据库访问库中的抽象工厂: 在数据库访问库中,抽象工厂可以定义创建连接、命令等数据库访问对象的方法,而具体工厂可以实现这些方法以创建特定类型(如MySQL、Oracle)的数据库访问对象。

这两个例子展示了抽象工厂模式在实际应用中的灵活性和可维护性,通过抽象工厂模式可以方便地扩展产品系列,而不影响客户端代码。

建造者模式详解

建造者模式(Builder Pattern) 是一种创建型设计模式,其主要目的是将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。建造者模式适用于需要一步一步构建复杂对象的场景,以及在构建过程中需要更改对象的属性。

建造者模式的主要角色:

  1. 产品(Product): 表示被构建的复杂对象,包含多个组成部分。
  2. 抽象建造者(Builder): 声明了构建产品各个部分的抽象方法。
  3. 具体建造者(Concrete Builder): 实现了抽象建造者接口,负责构建产品的各个部分,同时提供一个获取构建结果的方法。
  4. 指挥者(Director): 调用建造者的构建方法,按照一定的顺序和逻辑来构建产品。

建造者模式的实现方式:

// 产品
public class Product {
    private String partA;
    private String partB;
    private String partC;

    // 省略构造函数、getter和setter方法

    @Override
    public String toString() {
        return "Product{partA='" + partA + "', partB='" + partB + "', partC='" + partC + "'}";
    }
}

// 抽象建造者
public interface Builder {
    void buildPartA();
    void buildPartB();
    void buildPartC();
    Product getResult();
}

// 具体建造者A
public class ConcreteBuilderA implements Builder {
    private Product product = new Product();

    @Override
    public void buildPartA() {
        product.setPartA("PartA for BuilderA");
    }

    @Override
    public void buildPartB() {
        product.setPartB("PartB for BuilderA");
    }

    @Override
    public void buildPartC() {
        product.setPartC("PartC for BuilderA");
    }

    @Override
    public Product getResult() {
        return product;
    }
}

// 具体建造者B
public class ConcreteBuilderB implements Builder {
    private Product product = new Product();

    @Override
    public void buildPartA() {
        product.setPartA("PartA for BuilderB");
    }

    @Override
    public void buildPartB() {
        product.setPartB("PartB for BuilderB");
    }

    @Override
    public void buildPartC() {
        product.setPartC("PartC for BuilderB");
    }

    @Override
    public Product getResult() {
        return product;
    }
}

// 指挥者
public class Director {
    public void construct(Builder builder) {
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
    }
}

建造者模式的应用场景:

  1. 构建一个复杂对象: 当一个对象的构建过程比较复杂,有很多步骤和参数需要设置时,使用建造者模式可以将构建过程封装起来,使得代码更清晰。
  2. 创建的对象需要多个表示: 当一个对象有多个表示形式(不同的属性),而且需要在构建过程中灵活改变时,可以使用建造者模式。

两个经典的例子就是:

  1. StringBuilder类: StringBuilder类允许动态地构建字符串对象,通过链式调用append方法可以方便地添加字符、字符串等内容。最后通过toString方法获取最终的字符串对象。

    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("Hello");
    stringBuilder.append(" ");
    stringBuilder.append("Builder");
    String result = stringBuilder.toString();
    
  2. Java中的Locale.Builder类: Locale.Builder类用于构建Locale对象,可以通过设置语言、国家、地区等属性来构建一个Locale对象。

    Locale.Builder localeBuilder = new Locale.Builder();
    Locale locale = localeBuilder.setLanguage("en").setRegion("US").build();
    

这两个例子展示了建造者模式在实际应用中的灵活性,通过建造者模式,可以更好地组织和管理对象的构建过程,使得代码更加清晰和可维护。

原型模式详解

原型模式(Prototype Pattern) 是一种创建型设计模式,其主要目的是通过复制现有对象来创建新对象,而不是通过构造函数进行创建。原型模式对于创建成本较高的对象,或者对象的创建过程比较复杂而又需要频繁创建时,能够提高性能和简化代码。

原型模式的主要角色:

  1. 原型接口(Prototype): 声明了克隆方法的接口。
  2. 具体原型类(Concrete Prototype): 实现了克隆方法,通过复制自身创建新对象。

原型模式的实现方式:

// 原型接口
public interface Prototype extends Cloneable {
    Prototype clone();
}

// 具体原型类
public class ConcretePrototype implements Prototype {
    private String attribute;

    public ConcretePrototype(String attribute) {
        this.attribute = attribute;
    }

    @Override
    public Prototype clone() {
        try {
            // 利用Java的clone()方法实现对象的浅复制
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public String getAttribute() {
        return attribute;
    }

    public void setAttribute(String attribute) {
        this.attribute = attribute;
    }
}

原型模式的应用场景:

  1. 创建成本较高的对象: 当创建对象的成本较高,但又需要频繁创建新对象时,可以使用原型模式,通过复制现有对象来创建新对象,避免重复的初始化操作。
  2. 对象的构建过程复杂: 当对象的构建过程比较复杂,而且需要创建多个相似对象时,可以使用原型模式来简化对象的构建过程。

两个经典的例子是:

  1. Java中的Object.clone()方法: Object类提供了一个clone()方法,可以实现对象的浅复制。任何类只要实现Cloneable接口并覆写clone()方法,就可以通过clone()方法实现对象的复制。

    MyClass original = new MyClass();
    MyClass copy = (MyClass) original.clone();
    
  2. 图形编辑器中的复制粘贴功能: 在图形编辑器中,用户可以通过复制一个图形对象来创建一个新的相似对象,然后粘贴到画布上。这个过程可以使用原型模式来实现,复制操作就是通过原型对象创建新对象的过程。

这两个例子展示了原型模式在实际应用中的灵活性和可用性,通过原型模式可以方便地实现对象的复制,避免了重复的初始化过程。需要注意的是,对于复杂对象,可能需要考虑深复制的问题,以确保克隆对象的属性也是独立的。

适配器模式详解

适配器模式(Adapter Pattern) 是一种结构型设计模式,它允许接口不兼容的类能够一起工作。适配器模式充当两个不兼容接口之间的桥梁,使得它们能够协同工作。

适配器模式的主要角色:

  1. 目标接口(Target): 定义客户端使用的与特定领域相关的接口。
  2. 适配器(Adapter): 实现目标接口,并持有被适配者的实例,负责将客户端的请求转化为被适配者的接口。
  3. 被适配者(Adaptee): 包含需要被适配的接口,即客户端希望使用的接口。
  4. 客户端(Client): 使用目标接口与适配器进行交互,调用适配器的方法。

适配器模式的实现方式:

1. 类适配器模式:
// 目标接口
public interface Target {
    void request();
}

// 被适配者
public class Adaptee {
    public void specificRequest() {
        System.out.println("Specific request");
    }
}

// 适配器
public class ClassAdapter extends Adaptee implements Target {
    @Override
    public void request() {
        specificRequest();
    }
}
2. 对象适配器模式:
// 目标接口
public interface Target {
    void request();
}

// 被适配者
public class Adaptee {
    public void specificRequest() {
        System.out.println("Specific request");
    }
}

// 适配器
public class ObjectAdapter implements Target {
    private Adaptee adaptee;

    public ObjectAdapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        adaptee.specificRequest();
    }
}

适配器模式的应用场景:

  1. 系统需要与已有的类库、框架或模块进行集成: 当系统需要使用一些已经存在的类,但这些类的接口与系统要求的接口不一致时,可以使用适配器模式进行适配。
  2. 不同接口间的协同工作: 当系统中的一个组件需要与另一个组件协同工作,但它们的接口不兼容时,可以使用适配器模式。

两个经典的例子就是:

  1. Java中的Arrays.asList()方法: 该方法返回一个List对象,但底层实际上是基于数组的,通过适配器模式将数组适配为List

    String[] array = {"A", "B", "C"};
    List<String> list = Arrays.asList(array);
    
  2. Java中的InputStreamReader类: InputStreamReader是字节流到字符流的适配器,它将字节流适配为字符流,使得可以按字符而不是字节来读取数据。

    InputStream inputStream = new FileInputStream("file.txt");
    Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
    

这两个例子都展示了适配器模式在实际应用中的灵活性和可用性,通过适配器模式可以使得原本不兼容的接口能够协同工作。

装配器模式详解

装饰器模式(Decorator Pattern) 是一种结构型设计模式,它允许动态地将责任附加到对象上。装饰器模式提供了一种灵活的方式,通过扩展类的功能来创建新的对象,而不是通过创建子类。

装饰器模式的主要角色:

  1. 组件接口(Component): 定义了具体组件和装饰器的共同接口,客户端通过该接口与具体组件进行交互。
  2. 具体组件(Concrete Component): 实现了组件接口的具体类,是被装饰的原始对象。
  3. 装饰器抽象类(Decorator): 实现了组件接口,并包含一个指向具体组件的引用,通过该引用可以调用被装饰对象的方法。
  4. 具体装饰器(Concrete Decorator): 扩展了装饰器抽象类,添加了额外的责任或行为。

装饰器模式的实现方式:

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

// 具体组件
public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("ConcreteComponent operation");
    }
}

// 装饰器抽象类
public abstract class Decorator implements Component {
    private Component component;

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

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

// 具体装饰器A
public class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        System.out.println("ConcreteDecoratorA operation");
    }
}

// 具体装饰器B
public class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        System.out.println("ConcreteDecoratorB operation");
    }
}

装饰器模式的应用场景:

  1. 动态地给对象添加功能: 当需要动态地给对象添加额外的功能或责任时,装饰器模式可以提供一种灵活的解决方案,避免使用静态继承。
  2. 避免使用子类进行扩展: 当类的数目庞大,且每个类都需要以多种方式进行扩展时,使用装饰器模式可以避免创建大量的子类。

两个经典的例子如下:

  1. Java中的BufferedReader类: BufferedReaderReader的装饰器,它通过持有一个Reader对象,并在其基础上添加了缓冲区的功能,提供了更高效的读取方法。

    Reader reader = new FileReader("file.txt");
    BufferedReader bufferedReader = new BufferedReader(reader);
    
  2. Java中的InputStreamReader类: InputStreamReader是字节流到字符流的适配器,同时也是Reader的装饰器,它可以通过包装其他InputStream对象来提供字符流的读取能力。

    InputStream inputStream = new FileInputStream("file.txt");
    Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
    

这两个例子都展示了装饰器模式在实际应用中的灵活性和可用性,通过装饰器模式可以在运行时动态地给对象添加额外的功能,而无需修改其结构。

观察者模式详解

观察者模式(Observer Pattern) 是一种行为型设计模式,其主要目的是定义对象之间的一对多依赖关系,使得当一个对象状态改变时,其所有依赖对象都会得到通知并自动更新。观察者模式提供了一种简单而灵活的方法来实现发布-订阅机制。

观察者模式的主要角色:

  1. 主题(Subject): 负责维护一组观察者对象,提供方法来添加、删除和通知观察者。
  2. 具体主题(Concrete Subject): 实现了主题接口,维护自身状态,并在状态改变时通知观察者。
  3. 观察者(Observer): 定义了更新接口,以便在主题状态发生改变时得到通知。
  4. 具体观察者(Concrete Observer): 实现了观察者接口,具体的观察者对象,在接收到通知时执行特定的操作。

观察者模式的实现方式:

// 主题接口
public interface Subject {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

// 具体主题
public class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private int state;

    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(state);
        }
    }

    public void setState(int state) {
        this.state = state;
        notifyObservers();
    }
}

// 观察者接口
public interface Observer {
    void update(int state);
}

// 具体观察者A
public class ConcreteObserverA implements Observer {
    @Override
    public void update(int state) {
        System.out.println("ConcreteObserverA is notified with state: " + state);
    }
}

// 具体观察者B
public class ConcreteObserverB implements Observer {
    @Override
    public void update(int state) {
        System.out.println("ConcreteObserverB is notified with state: " + state);
    }
}

观察者模式的应用场景:

  1. 对象之间存在一对多的依赖关系: 当一个对象的改变需要通知其他多个对象时,可以使用观察者模式。
  2. 抽象和实现之间存在分离: 当一个对象的改变需要通知其他多个对象时,但又希望尽量减少它们之间的耦合度,可以使用观察者模式。

两个经典的例子如下:

  1. Java中的java.util.Observablejava.util.Observer Observable是抽象主题类,Observer是抽象观察者接口。具体主题通过继承Observable,具体观察者通过实现Observer接口。

    // 具体主题
    public class ConcreteSubject extends Observable {
        private int state;
    
        public void setState(int state) {
            this.state = state;
            setChanged();
            notifyObservers(state);
        }
    }
    
    // 具体观察者
    public class ConcreteObserver implements Observer {
        @Override
        public void update(Observable o, Object arg) {
            int state = (int) arg;
            System.out.println("ConcreteObserver is notified with state: " + state);
        }
    }
    
  2. Android中的事件监听机制: Android中的事件监听机制也是观察者模式的一种应用,例如,通过setOnClickListener方法设置按钮的点击事件监听器,当按钮被点击时,注册的监听器就会收到通知并执行相应的操作。

    Button button = findViewById(R.id.button);
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 按钮被点击时执行的操作
        }
    });
    

这两个例子展示了观察者模式在实际应用中的灵活性和可用性,通过观察者模式可以实现对象之间的松耦合,使得它们之间的关系更加灵活和可维护。

策略模式详解

策略模式(Strategy Pattern) 是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来,并使它们可以互相替换。策略模式使得算法的变化独立于使用算法的客户端,从而实现了算法的灵活性和可维护性。

策略模式的主要角色:

  1. 策略接口(Strategy): 定义了算法的接口,具体策略类要实现这个接口。
  2. 具体策略类(Concrete Strategy): 实现了策略接口,包含具体的算法实现。
  3. 上下文(Context): 维持一个对策略对象的引用,可以在运行时切换不同的策略。

策略模式的实现方式:

// 策略接口
public interface Strategy {
    void performAlgorithm();
}

// 具体策略类A
public class ConcreteStrategyA implements Strategy {
    @Override
    public void performAlgorithm() {
        System.out.println("ConcreteStrategyA is used.");
    }
}

// 具体策略类B
public class ConcreteStrategyB implements Strategy {
    @Override
    public void performAlgorithm() {
        System.out.println("ConcreteStrategyB is used.");
    }
}

// 上下文
public class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeAlgorithm() {
        strategy.performAlgorithm();
    }
}

策略模式的应用场景:

  1. 存在多个相似的算法,需要在运行时动态选择其中一个: 当一个问题可以有多种解决方式,并且需要根据具体情况选择其中一种方式时,可以使用策略模式。
  2. 避免使用条件语句决定算法的选择: 当需要根据某个条件选择不同的算法时,使用策略模式可以避免使用繁琐的条件语句,提高代码的可读性和可维护性。

两个经典的例子如下:

  1. 排序算法的策略模式: 假设有一个排序类,可以根据不同的排序算法进行排序。通过策略模式,可以将各种排序算法封装成具体的策略类,然后在运行时选择不同的策略。

    // 策略接口
    public interface SortingStrategy {
        void sort(int[] array);
    }
    
    // 具体策略类A
    public class BubbleSort implements SortingStrategy {
        @Override
        public void sort(int[] array) {
            // 冒泡排序算法的具体实现
        }
    }
    
    // 具体策略类B
    public class QuickSort implements SortingStrategy {
        @Override
        public void sort(int[] array) {
            // 快速排序算法的具体实现
        }
    }
    
    // 上下文
    public class Sorter {
        private SortingStrategy strategy;
    
        public Sorter(SortingStrategy strategy) {
            this.strategy = strategy;
        }
    
        public void setStrategy(SortingStrategy strategy) {
            this.strategy = strategy;
        }
    
        public void performSort(int[] array) {
            strategy.sort(array);
        }
    }
    
  2. 支付方式的策略模式: 假设有一个支付类,可以根据不同的支付方式进行支付。通过策略模式,可以将各种支付方式封装成具体的策略类,然后在运行时选择不同的策略。

    // 策略接口
    public interface PaymentStrategy {
        void pay(double amount);
    }
    
    // 具体策略类A
    public class CreditCardPayment implements PaymentStrategy {
        @Override
        public void pay(double amount) {
            // 信用卡支付的具体实现
        }
    }
    
    // 具体策略类B
    public class PayPalPayment implements PaymentStrategy {
        @Override
        public void pay(double amount) {
            // PayPal支付的具体实现
        }
    }
    
    // 上下文
    public class PaymentProcessor {
        private PaymentStrategy strategy;
    
        public PaymentProcessor(PaymentStrategy strategy) {
            this.strategy = strategy;
        }
    
        public void setStrategy(PaymentStrategy strategy) {
            this.strategy = strategy;
        }
    
        public void processPayment(double amount) {
            strategy.pay(amount);
        }
    }
    

这两个例子展示了策略模式在实际应用中的灵活性和可用性,通过策略模式可以方便地扩展和替换算法或业务规则。

责任链模式详解

责任链模式(Chain of Responsibility Pattern) 是一种行为型设计模式,它允许多个对象都有机会处理请求,从而避免将请求的发送者与接收者直接耦合在一起。请求沿着链传递,直到有一个对象处理它为止。

责任链模式的主要角色:

  1. 处理者接口(Handler): 定义了处理请求的接口,具体处理者要实现这个接口。
  2. 具体处理者(Concrete Handler): 实现了处理者接口,处理它所负责的请求,可以访问其后继者。
  3. 客户端(Client): 创建一个请求并将它发送给处理者。

责任链模式的实现方式:

// 处理者接口
public interface Handler {
    void handleRequest(Request request);
}

// 具体处理者A
public class ConcreteHandlerA implements Handler {
    private Handler successor;

    @Override
    public void handleRequest(Request request) {
        if (request.getType() == RequestType.TYPE_A) {
            // 处理请求的逻辑
        } else if (successor != null) {
            successor.handleRequest(request);
        }
    }

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
}

// 具体处理者B
public class ConcreteHandlerB implements Handler {
    private Handler successor;

    @Override
    public void handleRequest(Request request) {
        if (request.getType() == RequestType.TYPE_B) {
            // 处理请求的逻辑
        } else if (successor != null) {
            successor.handleRequest(request);
        }
    }

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
}

// 请求类
public class Request {
    private RequestType type;

    public Request(RequestType type) {
        this.type = type;
    }

    public RequestType getType() {
        return type;
    }
}

// 请求类型枚举
public enum RequestType {
    TYPE_A, TYPE_B
}

责任链模式的应用场景:

  1. 有多个对象可以处理同一请求,但具体哪个对象处理由运行时决定: 当系统中的处理者可能根据请求的不同而变化,并且客户端不知道具体哪个处理者能够处理请求时,可以使用责任链模式。
  2. 需要动态指定处理流程时: 当系统中的处理流程可能根据不同的条件而变化,需要在运行时动态地指定处理流程时,可以使用责任链模式。

两个经典的例子:

  1. Java中的异常处理: Java中的异常处理机制就是一个责任链模式的实现。当一个方法抛出异常时,系统会从当前方法开始,按照调用层次逐级向上查找能够处理该异常的catch块,直到找到合适的处理者或者到达顶层方法。

    try {
        // 可能抛出异常的代码
    } catch (ExceptionType1 e) {
        // 处理ExceptionType1的逻辑
    } catch (ExceptionType2 e) {
        // 处理ExceptionType2的逻辑
    } catch (Exception e) {
        // 处理其他异常的逻辑
    }
    
  2. Android中的事件分发机制: Android中的事件分发机制也是一个责任链模式的实现。事件从顶层的View开始传递,依次经过父容器和子View,最终到达目标View。每个View都有机会处理事件,如果处理不了就交给下一个处理者。

    public boolean dispatchTouchEvent(MotionEvent event) {
        // 一系列的处理逻辑
        if (onInterceptTouchEvent(event)) {
            // 如果拦截了事件,直接返回,不再往下传递
            return true;
        } else {
            // 没有拦截,继续往下传递
            return super.dispatchTouchEvent(event);
        }
    }
    

这两个例子都展示了责任链模式在实际应用中的灵活性和可用性,通过责任链模式可以实现一种松散耦合的处理机制,使得系统更易于扩展和维护。

十种主要模式大概如此,但是日常项目中并非经常涉及,但是,可以不用,我们却不能不了解!说不定哪一天的面试就会用到呢!

文章来源:https://blog.csdn.net/weixin_53742691/article/details/135232148
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。