【设计模式】Java设计模式详细讲解

一、概述

Java设计模式是Java程序设计中一种重要的最佳实践,它提供了一种框架和结构,可以帮助开发者更好地理解和设计复杂的系统。设计模式不仅仅是一种语法规则,更是一种思想和方法论,它能够帮助开发者更好地分析、设计和实现软件系统。

设计模式的概念最早由GOF( Gang of Four)在1994年出版的《设计模式:可复用的面向对象软件设计》一书中提出。这本书中介绍了23种经典的设计模式,这些设计模式是根据面向对象编程的原则和最佳实践总结出来的,旨在帮助开发者更好地设计和实现可扩展、可维护和可重用的软件系统。

本文将介绍Java设计模式的基本概念、特点和分类,并通过具体的示例来解释不同类型的设计模式。

二、设计模式的定义和分类

设计模式是一种经过验证的最佳实践,用于解决软件开发中常见的重复出现的问题。它提供了一种框架和结构,可以帮助开发者更好地设计和实现软件系统。

设计模式通常被分为三种类型:创建型模式结构型模式行为型模式

1.1、创建型模式

创建型模式关注对象创建的方式,主要解决对象创建的复杂性问题。以下是一些常见的创建型模式:

1.1.1、单例模式:确保只有一个特定类型的对象实例。

以下是一个简单的 Java 单例模式的实现:

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

在这个实现中,我们使用一个静态变量 instance 来保存单例对象。构造函数是私有的,因此无法从外部创建实例。getInstance() 方法是公共的,并且返回单例对象。如果单例对象尚未创建,则会创建一个新的实例并将其存储在 instance 变量中。由于 getInstance() 方法是同步的,因此它确保了在多线程环境中只有一个线程可以创建或获取单例对象。

以下是另一种更优雅的 Java 单例模式的实现,使用了双重检查锁定(double-checked locking):

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

在这个实现中,我们使用了双重检查锁定来确保只有在第一次调用 getInstance() 方法时才会创建单例对象。在第一次检查 instance 是否为 null 时,没有同步,因此只有单个线程可以进入同步块并创建单例对象。这样可以避免在每次调用 getInstance() 方法时都进行同步,从而提高性能。但是,需要注意的是,如果单例对象的创建非常耗时或需要执行一些耗时的初始化操作,那么这个实现可能会导致性能问题。

1.1.2、原型模式:通过复制对象来创建新对象。

在Java中,可以通过实现 Cloneable 接口并重写 clone() 方法来使用原型模式。以下是一个简单的示例:

public class Circle implements Cloneable {
    private int x;
    private int y;
    private int radius;

    public Circle(int x, int y, int radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getRadius() {
        return radius;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        Circle circle = (Circle) super.clone();
        return circle;
    }
}

在上面的示例中,Circle 类实现了 Cloneable 接口并重写了 clone() 方法。这个方法返回一个与原始对象具有相同值的新对象。需要注意的是,如果对象中有引用类型的成员变量,这些成员变量也需要使用同样的方式进行克隆。

1.1.4、工厂模式:通过工厂方法来创建对象,而不是直接实例化对象。

在工厂模式中,我们创建对象时不会直接使用 new 操作符,而是通过调用一个工厂方法来创建对象。这个工厂方法通常是一个静态方法,它返回一个实现了特定接口或继承了特定类的对象。

以下是一个简单的示例:

public interface Shape {
    void draw();
}

public class Circle implements Shape {
    public void draw() {
        System.out.println("Circle draw method");
    }
}

public class Rectangle implements Shape {
    public void draw() {
        System.out.println("Rectangle draw method");
    }
}

public class ShapeFactory {
    public static Shape getShape(String shapeType) {
        if ("circle".equals(shapeType)) {
            return new Circle();
        } else if ("rectangle".equals(shapeType)) {
            return new Rectangle();
        }
        return null;
    }
}

在上面的示例中,我们定义了一个 Shape 接口和两个实现了 Shape 接口的类:Circle 和 Rectangle。我们还定义了一个 ShapeFactory 类,它提供了一个静态方法 getShape,该方法接受一个字符串参数 shapeType,并根据该参数创建相应的 Shape 对象。

使用工厂模式的好处是,我们可以在运行时动态创建不同类型的对象,而不需要在代码中硬编码它们。此外,工厂模式还可以帮助我们实现依赖注入和单元测试等其他功能。

1.1.5、建造者模式:通过逐步构建对象来创建复杂对象。

在建造者模式中,我们创建对象时不会直接使用 new 操作符来创建对象,而是通过一系列的建造方法来逐步构建对象。建造者模式通常用于创建复杂的对象,尤其是那些具有很多属性并且这些属性的初始化依赖于其他属性的情况。

以下是一个简单的示例:

public class Car {
    private String color;
    private String model;
    private int year;

    public Car(Builder builder) {
        this.color = builder.color;
        this.model = builder.model;
        this.year = builder.year;
    }

    public void drive() {
        System.out.println("Driving " + this.color + " " + this.model + " car");
    }
}

public class CarBuilder {
    private String color;
    private String model;
    private int year;

    public CarBuilder setColor(String color) {
        this.color = color;
        return this;
    }

    public CarBuilder setModel(String model) {
        this.model = model;
        return this;
    }

    public CarBuilder setYear(int year) {
        this.year = year;
        return this;
    }

    public Car build() {
        return new Car(this);
    }
}

在上面的示例中,我们定义了一个 Car 类和一个 CarBuilder 类。Car 类具有三个属性:color、model 和 year。CarBuilder 类提供了设置这些属性的方法,并且还有一个 build 方法,该方法返回一个 Car 对象。通过使用 CarBuilder,我们可以逐步构建 Car 对象,而不需要在代码中硬编码它们。

1.1.6、抽象工厂模式:创建一组相关对象,并为其提供接口。

在抽象工厂模式中,我们定义了一个抽象的工厂接口,该接口声明了一组创建对象的方法。然后,我们定义了多个实现该接口的具体工厂类,每个工厂类都提供了特定于该主题的对象。

以下是一个简单的示例:

public interface Shape {
    void draw();
}

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Circle");
    }
}

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Rectangle");
    }
}

public interface ShapeFactory {
    Shape getShape();
}

public class CircleFactory implements ShapeFactory {
    @Override
    public Circle getShape() {
        return new Circle();
    }
}

public class RectangleFactory implements ShapeFactory {
    @Override
    public Rectangle getShape() {
        return new Rectangle();
    }
}

在上面的示例中,我们定义了一个抽象的 Shape 接口和两个实现该接口的具体类:Circle 和 Rectangle。我们还定义了一个抽象的 ShapeFactory 接口,它声明了一个 getShape 方法。然后,我们定义了两个具体工厂类:CircleFactory 和 RectangleFactory,它们实现了 ShapeFactory 接口,并提供了特定于该主题的 Shape 对象。

在使用抽象工厂模式时,我们可以创建不同的工厂对象来获取不同类型的对象。例如,我们可以创建一个 CircleFactory 对象来获取 Circle 对象,或者创建一个 RectangleFactory 对象来获取 Rectangle 对象。这样,我们就可以在不指定具体类的情况下创建不同类型的对象。

1.2、结构型模式

结构型模式关注对象之间的组合关系,主要解决如何组合对象以形成更大的结构。以下是一些常见的结构型模式:

1.2.1、适配器模式:将一个类的接口转换成另一个客户端所期望的接口形式。

在适配器模式中,我们定义了一个适配器类,该类实现了目标接口,并将不兼容的接口转换为兼容的接口。通常,适配器类会接收一个实现了不兼容接口的对象,并将其转换为另一个实现了目标接口的对象。

以下是一个简单的示例:

public interface Target {
    void doSomething();
}

public class Adaptee implements Target {
    @Override
    public void doSomething() {
        System.out.println("Adaptee's doSomething method");
    }
}

public class Adapter implements Target {
    private Adaptee adaptee;
    
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    
    @Override
    public void doSomething() {
        adaptee.doSomething();
    }
}

在上面的示例中,我们定义了一个 Target 接口,以及一个实现了该接口的 Adaptee 类。我们还定义了一个适配器类 Adapter,该类实现了 Target 接口,并将 Adaptee 类的 doSomething 方法转换为 Target 接口的 doSomething 方法。

在使用适配器模式时,我们可以创建一个 Adaptee 对象,并将其传递给 Adapter 构造函数。然后,我们可以使用 Adapter 对象来调用 Target 接口的 doSomething 方法,而实际执行的是 Adaptee 类的 doSomething 方法。

适配器模式提供了一种灵活的方式来处理不兼容的接口,它可以将不同的接口集成到同一个系统中,并使它们能够相互协作。

1.2.2、桥接模式:将一个复杂的类层次结构分解为多个简单的接口,以便于独立升级和扩展。

Java 桥接模式提供了一种方式来将一个类的接口与另一个不相关的接口关联起来。

在桥接模式中,我们定义了一个桥接类,该类将一个类的接口与另一个不相关的接口关联起来。通常,桥接类会包含一个实现了不相关接口的对象,并将该对象的操作转换为原始接口的操作。

以下是一个简单的示例:

public interface Shape {
    void draw();
}

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Circle");
    }
}

public interface Color {
    void fill();
}

public class Red implements Color {
    @Override
    public void fill() {
        System.out.println("filling with red color");
    }
}

public class Bridge implements Shape, Color {
    private Circle circle;
    private Red color;
    
    public Bridge(Circle circle, Red color) {
        this.circle = circle;
        this.color = color;
    }
    
    @Override
    public void draw() {
        circle.draw();
    }
    
    @Override
    public void fill() {
        color.fill();
    }
}

在上面的示例中,我们定义了两个接口,Shape 和 Color,分别表示图形和颜色。我们还定义了 Circle 和 Red 类,分别实现了 Shape 和 Color 接口。最后,我们定义了一个 Bridge 类,该类实现了 Shape 和 Color 接口,并将 Circle 和 Red 类的操作转换为这两个接口的操作。

在使用桥接模式时,我们可以创建一个 Circle 对象和一个 Red 对象,并将它们传递给 Bridge 构造函数。然后,我们可以使用 Bridge 对象来调用 Shape 和 Color 接口的方法,而实际执行的是 Circle 和 Red 类的操作。

桥接模式提供了一种灵活的方式来将不同的接口关联起来,它可以将不相关的接口集成到同一个对象中,并使它们能够相互协作。

1.2.3、组合模式:将一组相关对象组合成一个树形结构,以便于客户端以统一的方式处理对象。

Java 组合模式提供了一种方式来组合多个对象,从而形成一个树形结构,并可以递归地调用这些对象的操作。

在组合模式中,我们定义了一个抽象的节点类,该节点类包含了子节点的引用。然后,我们定义了两个具体的节点类,一个是叶节点,另一个是组合节点。叶节点表示树的末端,它没有子节点。组合节点表示树的分支,它可以包含多个子节点。

以下是一个简单的示例:

public abstract class Component {
    protected Component left;
    protected Component right;
    
    public Component(Component left, Component right) {
        this.left = left;
        this.right = right;
    }
    
    public abstract void operation();
}

public class Leaf extends Component {
    private String name;
    
    public Leaf(String name) {
        this.name = name;
    }
    
    @Override
    public void operation() {
        System.out.println("Leaf " + name + ": operation()");
    }
}

public class Composite extends Component {
    private List<Component> children;
    
    public Composite() {
        children = new ArrayList<>();
    }
    
    public void add(Component component) {
        children.add(component);
    }
    
    @Override
    public void operation() {
        for (Component child : children) {
            child.operation();
        }
    }
}

在上面的示例中,我们定义了一个抽象的节点类 Component,它包含了左右子节点的引用。然后,我们定义了两个具体的节点类,一个是叶节点 Leaf,另一个是组合节点 Composite。叶节点表示树的末端,它没有子节点。组合节点表示树的分支,它可以包含多个子节点。

在使用组合模式时,我们可以创建一个树形结构,每个节点可以是叶节点或组合节点。组合节点可以递归地调用其子节点的操作。例如,我们可以创建一个包含多个叶节点和组合节点的树,并调用根节点的 operation() 方法,该方法将递归地调用所有子节点的 operation() 方法。

1.2.4、装饰器模式:动态地给一个对象添加额外的职责,同时保持对象的接口不变。

Java 装饰器模式提供了一种方式来动态地给一个对象添加一些额外的职责。这通过创建一个包装对象,它包装了原始对象并提供一个新的接口,经常用于改变一个或多个类的行为或为其添加新的行为。

装饰器模式包含以下四个角色:

  • Component:定义一个接口,这个接口表示可以被装饰的行为。
  • ConcreteComponent:实现 Component 接口,表示需要被装饰的具体对象。
  • Decorator:继承 Component 接口,并且包含一个对 ConcreteComponent 的引用。Decorator 提供了一种方法来将自身添加到被装饰的对象的引用中。
  • ConcreteDecorator:实现 Component 接口,并添加额外的功能。在需要的时候,可以调用 ConcreteComponent 的方法。
    下面是一个简单的 Java 示例代码:
// Component
interface Coffee {
    double getCost();
    String getIngredients();
}

// ConcreteComponent
class SimpleCoffee implements Coffee {
    public double getCost() {
        return 1;
    }
    
    public String getIngredients() {
        return "Coffee";
    }
}

// Decorator
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;
    
    public CoffeeDecorator(Coffee c) {
        this.decoratedCoffee = c;
    }
    
    public double getCost() {
        return decoratedCoffee.getCost();
    }
    
    public String getIngredients() {
        return decoratedCoffee.getIngredients();
    }
}

// ConcreteDecorator A
class WhippedCoffee extends CoffeeDecorator {
    public WhippedCoffee(Coffee c) {
        super(c);
    }
    
    public double getCost() {
        return decoratedCoffee.getCost() + 0.5;
    }
    
    public String getIngredients() {
        return decoratedCoffee.getIngredients() + ", Whipped Cream";
    }
}

// ConcreteDecorator B
class MochaCoffee extends CoffeeDecorator {
    public MochaCoffee(Coffee c) {
        super(c);
    }
    
    public double getCost() {
        return decoratedCoffee.getCost() + 1;
    }
    
    public String getIngredients() {
        return decoratedCoffee.getIngredients() + ", Mocha";
    }
}

在这个示例中,我们首先定义了一个 Coffee 接口和它的实现类 SimpleCoffee。然后,我们创建了一个抽象的 Decorator 类,它实现了 Coffee 接口并包含一个对被装饰对象的引用。接下来,我们创建了两个具体的装饰器类 WhippedCoffee 和 MochaCoffee,它们分别添加了 “Whipped Cream” 和 “Mocha” 的成分和价格。最后,我们可以使用这些装饰器来动态地给咖啡添加额外的职责。

1.2.5、外观模式:为子系统提供单一的入口点,简化子系统的使用。

外观模式(Facade Pattern)提供了一个简化的接口,将一些复杂的子系统或类的使用方式变得简单。外观模式的主要目的是简化接口,以便客户端代码能够更方便地使用子系统或类。

在 Java 中,外观模式通常使用类来实现。外观类提供了一些简单的方法,这些方法将客户端代码与子系统或类的复杂实现隔离开来。外观类通常只暴露子系统或类的一小部分功能,并且隐藏了内部实现的细节。

下面是一个简单的 Java 代码示例,演示了外观模式的使用:

public class Facade {
    private SubSystem1 subSystem1;
    private SubSystem2 subSystem2;
    
    public Facade() {
        subSystem1 = new SubSystem1();
        subSystem2 = new SubSystem2();
    }
    
    public void simplifiedMethod() {
        subSystem1.complexMethod1();
        subSystem2.complexMethod2();
    }
}

public class SubSystem1 {
    public void complexMethod1() {
        // complex implementation of method 1
    }
}

public class SubSystem2 {
    public void complexMethod2() {
        // complex implementation of method 2
    }
}

在上面的代码中,Facade 类是外观类,它简化了 SubSystem1 和 SubSystem2 的使用方式。Facade 类只提供了一个 simplifiedMethod() 方法,该方法调用了 SubSystem1 和 SubSystem2 的复杂方法。这样,客户端代码只需要调用 Facade 类的 simplifiedMethod() 方法,而不需要直接调用 SubSystem1 和 SubSystem2 的复杂方法。

使用外观模式可以降低客户端代码与子系统或类的耦合度,提高了代码的可维护性和可读性。同时,外观模式还可以隐藏子系统或类的内部实现细节,提高了系统的安全性。

1.2.6、享元模式:通过共享对象来减少系统中的对象数量,以降低系统的内存占用。

享元模式用于减少创建对象的数量,以便降低内存消耗和提高性能。享元模式通过共享对象来实现这一目标,使得多个客户端可以共享同一个对象,而不是为每个客户端创建新的对象。

享元模式主要适用于以下情况:

  • 对象创建成本较高:如果对象的创建成本较高,例如需要消耗大量的内存或网络带宽,那么使用享元模式可以降低对象的创建和销毁开销。
  • 对象数量可能非常大:如果需要创建的对象数量可能非常大,例如在图形渲染或数据处理中,使用享元模式可以避免大量的对象创建,从而减少内存消耗。
    在 Java 中,享元模式通常使用一个享元工厂类来管理共享对象。享元工厂类负责创建和管理共享对象,并确保多个客户端共享同一个对象。

下面是一个简单的 Java 代码示例,演示了享元模式的使用:

import java.util.HashMap;
import java.util.Map;

public class FlyweightFactory {
    private Map<String, Flyweight> flyweights = new HashMap<>();
    
    public synchronized Flyweight getFlyweight(String key) {
        if (!flyweights.containsKey(key)) {
            Flyweight flyweight = new Flyweight(key);
            flyweights.put(key, flyweight);
        }
        return flyweights.get(key);
    }
}

public abstract class Flyweight {
    protected String key;
    
    public Flyweight(String key) {
        this.key = key;
    }
    
    public abstract void operation(int x, int y);
}

public class ConcreteFlyweight1 extends Flyweight {
    public ConcreteFlyweight1(String key) {
        super(key);
    }
    
    @Override
    public void operation(int x, int y) {
        // implementation of operation for ConcreteFlyweight1
    }
}

public class ConcreteFlyweight2 extends Flyweight {
    public ConcreteFlyweight2(String key) {
        super(key);
    }
    
    @Override
    public void operation(int x, int y) {
        // implementation of operation for ConcreteFlyweight2
    }
}

在上面的代码中,FlyweightFactory 类是享元工厂类,它负责创建和管理共享对象。Flyweight 是一个抽象类,表示享元对象。ConcreteFlyweight1 和 ConcreteFlyweight2 是具体的享元实现类。每个享元实现类对应不同的操作。通过调用 FlyweightFactory 的 getFlyweight() 方法,可以获取共享的享元对象。在获取对象时,如果该对象不存在,则创建一个新的对象并将其添加到共享对象池中。如果该对象已经存在,则直接返回已经存在的对象。通过这种方式,多个客户端可以共享同一个享元对象,从而减少了对象的创建和销毁开销。

1.2.7、代理模式:通过代理来控制对另一个对象的访问。

代理模式是提供了一种将实际操作隐藏在代理对象之后的机制。代理模式通常用于控制对对象的访问,提供额外的功能或者修改对象的行为。

代理模式包括以下两种类型:

  • 静态代理:静态代理是指在编译期间代理类和被代理类之间的关系就已经确定。代理类需要实现与被代理类相同的接口,并在代理类中调用被代理类的方法。在调用过程中,可以执行额外的操作。
  • 动态代理:动态代理是指在运行时创建代理类。Java 中的动态代理主要通过 java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口实现。动态代理允许在运行时动态地创建代理类,并且可以实现对被代理对象的增强和修改。
    下面是一个简单的静态代理的示例:
public interface Shape {
    void draw();
}

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle...");
    }
}

public class RectangleProxy implements Shape {
    private Rectangle rectangle;

    public RectangleProxy(Rectangle rectangle) {
        this.rectangle = rectangle;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a rectangle...");
        rectangle.draw();
        System.out.println("Drawing a rectangle...done");
    }
}

public class Main {
    public static void main(String[] args) {
        Shape rectangle = new Rectangle();
        Shape rectangleProxy = new RectangleProxy(rectangle);
        rectangleProxy.draw();
    }
}

在上面的示例中,Rectangle 是被代理类,RectangleProxy 是代理类。RectangleProxy 实现了与 Rectangle 相同的接口 Shape,并且在 draw() 方法中调用了 Rectangle 的 draw() 方法。这样,在调用 rectangleProxy.draw() 时,额外的操作也会被执行。

需要注意的是,静态代理需要在编译期间创建代理类和被代理类的关系,而动态代理可以在运行时动态地创建代理类和被代理类的关系。动态代理的灵活性和灵活性使其成为实际应用程序中的常用技术。

1.3、行为型模式

行为型模式关注对象之间的交互关系,主要解决如何让对象之间更好地协同工作。以下是一些常见的行为型模式:

1.3.1、策略模式:定义一系列可互换的算法,并将每个算法封装起来,使它们可以相互替换。

策略模式是定义了一系列可以互相替换的算法,使得在运行时可以根据情况选择合适的算法。

策略模式包括以下组件:

  • 策略(Strategy):定义了一系列的策略接口,每个策略接口表示一个具体的算法。策略类通常包含一些共同的行为和属性。
  • 具体策略(ConcreteStrategy):实现了策略接口,实现了具体的算法。每个具体策略类表示一个具体的算法。
  • 环境(Context):持有策略对象的引用,并且使用策略对象的方法来执行具体的操作。
    下面是一个简单的示例代码:
// 策略接口
public interface Strategy {
    public void execute();
}

// 具体策略类1
public class ConcreteStrategy1 implements Strategy {
    @Override
    public void execute() {
        System.out.println("ConcreteStrategy1 is executed.");
    }
}

// 具体策略类2
public class ConcreteStrategy2 implements Strategy {
    @Override
    public void execute() {
        System.out.println("ConcreteStrategy2 is executed.");
    }
}

// 环境类
public class Context {
    private Strategy strategy;

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

    public void executeStrategy() {
        strategy.execute();
    }
}

// 测试代码
public class Main {
    public static void main(String[] args) {
        Strategy strategy1 = new ConcreteStrategy1();
        Context context1 = new Context(strategy1);
        context1.executeStrategy(); // 输出:ConcreteStrategy1 is executed.

        Strategy strategy2 = new ConcreteStrategy2();
        Context context2 = new Context(strategy2);
        context2.executeStrategy(); // 输出:ConcreteStrategy2 is executed.
    }
}

在上面的示例中,Strategy 是一个策略接口,定义了一个 execute() 方法。ConcreteStrategy1 和 ConcreteStrategy2 是具体的策略类,分别实现了 Strategy 接口,并重写了 execute() 方法。Context 类持有一个策略对象的引用,并在 executeStrategy() 方法中调用了策略对象的 execute() 方法。在测试代码中,我们可以根据需要选择具体的策略对象来执行操作。

1.3.2、观察者模式:定义对象之间的依赖关系,当一个对象发生改变时,所有依赖于它的对象都会收到通知并自动更新。

观察者模式定义了一种一对多的依赖关系,使得当一个对象(主题)的状态发生改变时,所有依赖于它的对象(观察者)都能够得到通知并自动更新。

观察者模式包括以下组件:

  • 主题(Subject):维护一个观察者列表,并定义了添加和删除观察者的方法。当主题的状态发生改变时,它会自动通知所有的观察者。
  • 观察者(Observer):定义了一个更新方法,当主题的状态发生改变时,该方法将被调用。
    下面是一个简单的示例代码:
// 主题接口
public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

// 具体主题类
public class ConcreteSubject implements Subject {
    private ObserverList observers;
    private int state;

    public ConcreteSubject() {
        observers = new ObserverList();
    }

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

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

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

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

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

// 具体观察者类1
public class ConcreteObserver1 implements Observer {
    private int state;

    @Override
    public void update() {
        state = ((ConcreteSubject) ((ObserverObject) subject).getSubject()).getState();
        System.out.println("ConcreteObserver1 received state: " + state);
    }
}

// 具体观察者类2
public class ConcreteObserver2 implements Observer {
    private int state;

    @Override
    public void update() {
        state = ((ConcreteSubject) ((ObserverObject) subject).getSubject()).getState();
        System.out.println("ConcreteObserver2 received state: " + state);
    }
}

// 观察者列表类
public class ObserverList {
    private ArrayList<Observer> observers;

    public ObserverList() {
        observers = new ArrayList<Observer>();
    }

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

    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
}
1.3.3、模板方法模式:定义一个操作算法的骨架,将某些步骤的执行延迟到子类中实现。

模板方法模式定义了一个操作的框架,将算法的步骤封装到不同的抽象方法中,从而允许子类在不改变算法结构的情况下重定义某些步骤的具体实现。

在 Java 中,模板方法模式通常使用继承来实现。一个抽象类定义了算法的框架,包括一些抽象方法和一些具体方法。子类可以继承这个抽象类,重定义抽象方法以提供自己的实现,而具体方法则保留了与框架的接口一致的实现。

以下是一个简单的示例代码,演示了模板方法模式的使用:

public abstract class Template {
    public void templateMethod() {
        step1();
        System.out.println("Algorithm framework");
        step2();
        System.out.println("Algorithm framework");
        step3();
    }

    public abstract void step1();
    public abstract void step2();
    public abstract void step3();
}

public class ConcreteTemplate extends Template {
    @Override
    public void step1() {
        System.out.println("Concrete step 1");
    }

    @Override
    public void step2() {
        System.out.println("Concrete step 2");
    }

    @Override
    public void step3() {
        System.out.println("Concrete step 3");
    }
}

在上面的代码中,Template 是一个抽象类,定义了一个模板方法 templateMethod(),它包含了算法的框架。step1()、step2() 和 step3() 是抽象方法,子类需要提供具体的实现。ConcreteTemplate 是 Template 的一个子类,重定义了所有的抽象方法,提供了自己的实现。在 templateMethod() 中,算法的框架保持不变,但是每个步骤的具体实现都由子类提供。

使用模板方法模式的好处是它可以让子类在不改变算法结构的情况下重定义某些步骤的具体实现。这使得代码更加灵活和可维护。同时,模板方法模式还可以提高代码的可重用性,因为算法的框架可以在不同的子类中重复使用。

1.3.4、状态模式:将一个对象的状态转换细化为各个状态,每个状态对应一个类,实现状态的转换和特定状态的逻辑。

状态模式允许一个对象在其内部状态改变时改变其行为。状态模式通过把状态封装到单独的状态类中来实现的,每个状态类负责处理对应的状态,并且可以有一个机会改变内部状态或者发出命令。

以下是一个简单的示例代码,演示了状态模式的使用:

public interface State {
    void handle(Context context);
}

public class StateA implements State {
    @Override
    public void handle(Context context) {
        System.out.println("StateA handled, context state: " + context.getState());
        context.setState(new StateB());
    }
}

public class StateB implements State {
    @Override
    public void handle(Context context) {
        System.out.println("StateB handled, context state: " + context.getState());
        context.setState(new StateA());
    }
}

public class Context {
    private State state;
    private String stateData;

    public Context(State initialState) {
        this.state = initialState;
    }

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

    public String getStateData() {
        return stateData;
    }

    public void setStateData(String stateData) {
        this.stateData = stateData;
    }

    public void request() {
        state.handle(this);
    }
}

在上面的代码中,State 是一个接口,定义了一个 handle() 方法。StateA 和 StateB 是实现了 State 接口的两个具体状态类,每个状态类都有自己的 handle() 方法。Context 类保存了一个当前状态的引用,并有一个 request() 方法来处理请求。当 Context 的 request() 方法被调用时,当前状态会处理请求,并在处理结束后改变状态。

使用状态模式的好处是它可以让一个对象根据其内部状态来改变其行为,并且可以将状态封装到单独的状态类中,使得代码更加清晰和易于维护。同时,状态模式还可以提高代码的可重用性,因为不同的状态类可以具有相似的行为。

1.3.5、命令模式:定义一个命令接口,通过接收命令对象来实现命令的执行。

命令模式允许你将请求封装为一个对象,从而使请求可以被取消,被记录,或者被参数化。

以下是一个简单的示例代码,演示了命令模式的使用:

public interface Command {
    void execute();
}

public class ConcreteCommand implements Command {
    private Receiver receiver;

    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }

    @Override
    public void execute() {
        receiver.doSomething();
    }
}

public interface Receiver {
    void doSomething();
}

public class ConcreteReceiver implements Receiver {
    @Override
    public void doSomething() {
        System.out.println("Receiver did something");
    }
}

public classInvoker`public class Invoker {
    private Command command;

    public Invoker(Command command) {
        this.command = command;
    }

    public void setCommand(Command command) {
        this.command = command;
    }

    public void executeCommand() {
        command.execute();
    }
}

在上面的代码中,Command 是一个抽象命令接口,定义了一个 execute() 方法。ConcreteCommand 是一个具体命令类,实现了 Command 接口,并关联了一个 Receiver 对象。当调用 execute() 方法时,具体命令类会调用接收者的 doSomething() 方法。Receiver 是一个接收者接口,定义了一个 doSomething() 方法。ConcreteReceiver 是一个具体接收者类,实现了 Receiver 接口,并实现了 doSomething() 方法。Invoker 是调用者接口,它持有对命令的引用,并调用命令的 execute() 方法来执行请求。通过这种方式,调用者与具体命令的实现解耦,使得可以灵活地增加新的命令类,而不需要修改调用者的代码。

1.3.6、迭代器模式:提供一种方法来顺序访问一个聚合对象的元素,而不暴露其底层表示。

迭代器模式提供了一种在集合对象中遍历元素的标准方法。迭代器模式允许我们顺序访问一个聚合对象的元素,而不暴露其底层表示。

以下是一个简单的示例代码,演示了迭代器模式的使用:

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

public class IteratorExample {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            System.out.println(item);
        }
    }
}

在上面的代码中,我们创建了一个 List 对象,并向其中添加了一些元素。然后,我们使用 iterator() 方法获取了一个迭代器对象,并使用 hasNext() 方法检查是否还有下一个元素。如果有,我们使用 next() 方法获取下一个元素,并输出到控制台。通过这种方式,我们可以遍历整个列表。

迭代器模式的主要优点是它提供了一种统一的访问聚合对象的方法,而不必关心底层表示。此外,迭代器模式还可以在遍历过程中删除元素,而不会导致 ConcurrentModificationException 异常。

1.3.7、访问者模式:在不改变数据结构的前提下增加新的操作,通过定义一个访问者类来实现对数据的操作。

访问者模式允许一个操作作用于一个对象结构中的各个元素,而这个对象结构可以是一个简单的数组,也可以是一个复杂的树形结构。访问者模式主要解决的是一种数据结构或者对象结构中,数据类型复杂且变化多端的问题。

访问者模式包含以下主要角色:

  • 访问者(Visitor):定义了一个访问操作的方法,该方法接收一个元素对象作为参数。
    被访问对象(Element):提供访问者所需的方法,一般该方法被称为 accept。
  • 具体元素类(ConcreteElement):实现 Element 类,并接受访问者的访问,在访问完毕后执行一些具体的操作。
    以下是一个简单的示例代码,演示了访问者模式的使用:
// 定义元素类
class ConcreteElementA {
    private String name;
    public ConcreteElementA(String name) {
        this.name = name;
    }
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class ConcreteElementB {
    private String name;
    public ConcreteElementB(String name) {
        this.name = name;
    }
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

// 定义访问者接口
interface Visitor {
    void visit(ConcreteElementA elementA);
    void visit(ConcreteElementB elementB);
}

// 定义具体访问者类
class ConcreteVisitor implements Visitor {
    public void visit(ConcreteElementA elementA) {
        System.out.println(elementA.name + " visited by ConcreteVisitor");
    }
    public void visit(ConcreteElementB elementB) {
        System.out.println(elementB.name + " visited by ConcreteVisitor");
    }
}

在上面的代码中,我们定义了两个元素类 ConcreteElementA 和 ConcreteElementB,它们都实现了 Element 类的 accept 方法。然后,我们定义了一个访问者接口 Visitor,其中包含了两个访问方法分别对应两个元素类。最后,我们创建了一个具体的访问者类 ConcreteVisitor,实现了 Visitor 接口中的方法。通过这种方式,我们可以对不同的元素类执行不同的操作。

1.3.8、备忘录模式:保存一个对象的内部状态,并在需要时恢复该对象的状态。

备忘录模式提供了一种保存对象内部状态的机制,并在以后需要时能够恢复这个状态。它允许在不暴露对象内部状态的情况下保存和恢复对象的状态。

在 Java 中,备忘录模式可以使用以下步骤实现:

定义一个备忘录类(Memento),该类包含了原始对象(Originator)的内部状态。

public class Memento {
    private String state;

    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }
}

定义一个原始对象(Originator),该对象允许其他对象保存和恢复其内部状态。

public class Originator {
    private String state;

    public Originator(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public Memento getMemento() {
        return new Memento(state);
    }

    public void restoreFromMemento(Memento memento) {
        this.state = memento.getState();
    }
}

定义一个管理者类(Caretaker),该类负责保存和恢复原始对象的备忘录。

public class Caretaker {
    private Memento memento;

    public Caretaker(Memento memento) {
        this.memento = memento;
    }

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}

在上述代码中,Originator 类的 getMemento() 方法用于创建一个新的备忘录,restoreFromMemento() 方法用于从备忘录中恢复状态。Caretaker 类负责保存和恢复备忘录。使用备忘录模式时,需要先保存原始对象的当前状态,然后在需要时恢复该状态。

1.3.9、中介者模式:将一组对象之间的交互封装到一个中介者对象中,降低对象之间的耦合性。

中介者模式提供了一种将对象之间的交互集中管理的方法,从而降低对象之间的耦合度,增强系统的可维护性和可复用性。

在 Java 中,中介者模式可以使用以下步骤实现:

定义一个中介者类(Mediator),该类包含了与各个同事类(Colleague)交互的逻辑。

public class Mediator {
    public void performAction(Colleague colleague) {
        // 处理同事类之间的交互逻辑
    }
}

定义一个同事类(Colleague),该类与中介者类交互,并将自身的状态传递给中介者。

public class Colleague {
    private Mediator mediator;

    public Colleague(Mediator mediator) {
        this.mediator = mediator;
    }

    public void changeState() {
        // 修改自身的状态
        mediator.performAction(this); // 调用中介者的方法,处理与其它同事类的交互逻辑
    }
}

在客户端代码中,创建中介者和同事类的实例,并调用相应的方法。

public class Client {
    public static void main(String[] args) {
        Mediator mediator = new Mediator();
        Colleague colleague1 = new Colleague(mediator);
        Colleague colleague2 = new Colleague(mediator);
        // 设置同事1的状态,触发中介者处理逻辑
        colleague1.changeState();
        // 设置同事2的状态,同样会触发中介者处理逻辑
        colleague2.changeState();
    }
}

在上述代码中,中介者类 Mediator 集中处理同事类之间的交互逻辑,避免了同事类之间的直接交互,降低了系统的耦合度。同时,通过将交互逻辑集中管理,也方便了对系统行为的扩展和维护。

1.3.10、解释器模式:定义一个解释器接口,通过解释器来解释并执行特定的语法规则。

解释器模式提供了一种构建解析、解释和处理语言表达式的方式。在 Java 中,解释器模式可以使用以下步骤实现:

定义一个抽象表达式类(Expression),该类声明了解释表达式的方法。

public interface Expression {
    int interpret(Context context);
}

定义一个具体表达式类(ConcreteExpression),该类实现了抽象表达式类中的 interpret() 方法。

public class ConcreteExpression implements Expression {
    @Override
    public int interpret(Context context) {
        // 实现解释表达式的逻辑
    }
}

定义一个上下文类(Context),该类包含了与表达式相关的数据和操作。

public class Context {
    private int value;

    public Context(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

创建一个解释器类(Interpreter),该类包含了与表达式相关的解释逻辑。

public class Interpreter {
    public int interpret(Expression expression) {
        return expression.interpret(new Context(0)); // 传入默认的上下文对象
    }
}

在客户端代码中,创建具体表达式和上下文对象,并调用解释器类的 interpret() 方法来解释表达式。

public class Client {
    public static void main(String[] args) {
        Expression expression = new ConcreteExpression(); // 创建具体表达式对象
        Context context = new Context(10); // 创建上下文对象
        Interpreter interpreter = new Interpreter(); // 创建解释器对象
        int result = interpreter.interpret(expression); // 调用解释器解释表达式,并获取结果
        System.out.println(result); // 输出结果
    }
}

在上述代码中,解释器模式通过定义抽象表达式类和具体表达式类,使得不同类型的表达式可以灵活地添加到系统中。同时,通过将解释逻辑封装在解释器类中,使得系统的可维护性和可复用性得到了提高。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
java设计模式大体上分为三大类: 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式设计模式遵循的原则有6个: 1、开闭原则(Open Close Principle)   对扩展开放,对修改关闭。 2、里氏代换原则(Liskov Substitution Principle)   只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。 3、依赖倒转原则(Dependence Inversion Principle)   这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。 4、接口隔离原则(Interface Segregation Principle)   使用多个隔离的借口来降低耦合度。 5、迪米特法则(最少知道原则)(Demeter Principle)   一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 6、合成复用原则(Composite Reuse Principle)   原则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

科学熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值