吃透这六大设计模式,你也能写出优雅代码!

2,881 阅读10分钟

一、单例模式

1.1 使用场景

在系统中,当某个全局使用的类频繁地进行创建与销毁操作,为节省系统资源并确保实例的唯一性,可使用单例模式。例如,日志记录器在整个系统中通常只需要一个实例来记录日志信息,此时单例模式就非常适用。

1.2 注意事项

  1. 单例类在整个系统中只能有一个实例。
  2. 单例类需自行创建并管理这个唯一的实例。
  3. 单例类要为系统中的其他对象提供获取该实例的方法。

1.3 饿汉模式

饿汉模式是单例模式的一种实现方式,在类加载时就创建单例实例。以下是 Java 代码示例:

public class SingletonHungry {
    // 私有静态实例
    private static final SingletonHungry instance = new SingletonHungry();

    // 私有构造函数,防止外部实例化
    private SingletonHungry() {}

    // 提供获取实例的方法
    public static SingletonHungry getInstance() {
        return instance;
    }
}

这种方式的优点是简单直接,在类加载时就完成实例的创建,保证了线程安全;缺点是如果实例创建过程复杂或实例在很长时间内不被使用,会造成资源的浪费。

1.4 懒汉模式

懒汉模式是在需要使用实例时才进行创建。为保证线程安全,通常采用双重检查锁定机制。以下是 Java 代码示例:

public class SingletonLazy {
    // 私有静态实例
    private static SingletonLazy instance;

    // 私有构造函数,防止外部实例化
    private SingletonLazy() {}

    // 提供获取实例的方法
    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

这种方式的优点是实现了延迟加载,避免了资源的浪费;缺点是实现相对复杂,需要正确处理多线程环境下的并发问题。

二、代理模式

2.1 模式概述

代理模式属于结构型设计模式,一个类(代理类)代表另一个类(目标类)来提供功能。当需要在访问一个类时添加额外的控制逻辑,如权限验证、日志记录等,可使用代理模式。代理类在客户端和目标类之间起到中介作用,客户端通过代理类来访问目标类的方法。

2.2 优点

  1. 职责清晰:代理类负责处理额外的控制逻辑,目标类专注于核心业务逻辑,使代码结构更加清晰。
  2. 高扩展性:新增功能或修改控制逻辑时,只需修改代理类,不影响目标类,便于系统的扩展。
  3. 智能化:可以根据不同的需求,在代理类中灵活地添加各种智能操作。

2.3 缺点

  1. 性能损耗:由于增加了代理对象,某些类型的代理模式可能会导致请求的处理速度变慢。
  2. 实现复杂:实现代理模式需要编写额外的代码,部分代理模式的实现逻辑较为复杂。

2.4 使用场景

  1. 远程代理:用于处理对远程对象的访问,如访问远程服务器上的资源。
  2. 虚拟代理:实现对象的延迟加载,如在图形系统中,当对象较大或创建成本较高时,使用虚拟代理来延迟对象的创建。
  3. 保护代理:控制对对象的访问权限,确保只有授权的客户端才能访问目标类。
  4. Cache 代理:缓存数据,提高数据的访问效率,如缓存数据库查询结果。

2.5 与其他模式的区别

  1. 与适配器模式的区别:适配器模式主要用于转换对象的接口,使其能够与其他不兼容的接口协同工作;代理模式不改变所代理类的接口,只是在访问接口的过程中增加控制逻辑。
  2. 与装饰器模式的区别:装饰器模式侧重于为对象添加新的功能,增强对象的能力;代理模式着重于对对象访问的控制。

2.6 静态代理示例

假设我们有一个Subject接口和其实现类RealSubject,现在使用静态代理来添加额外的功能(如日志记录)。

// 定义接口
interface Subject {
    void request();
}

// 实现类
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: handling request");
    }
}

// 代理类
class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        System.out.println("Proxy: Logging before request");
        realSubject.request();
        System.out.println("Proxy: Logging after request");
    }
}

在客户端中使用代理:

public class ProxyPatternDemo {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        Proxy proxy = new Proxy(realSubject);
        proxy.request();
    }
}

2.7 动态代理示例

动态代理通过反射机制在运行时动态生成代理类。以下是使用 Java 内置的动态代理机制的示例:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义接口
interface Subject {
    void request();
}

// 实现类
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: handling request");
    }
}

// 调用处理器
class MyInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Dynamic Proxy: Logging before request");
        Object result = method.invoke(target, args);
        System.out.println("Dynamic Proxy: Logging after request");
        return result;
    }
}

// 客户端
public class DynamicProxyPatternDemo {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        Subject proxy = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                new MyInvocationHandler(realSubject));
        proxy.request();
    }
}

三、工厂模式

3.1 模式定义

工厂模式是 Java 中常用的创建型设计模式,它提供了一种创建对象的方式,将对象的创建逻辑封装起来,客户端通过工厂类来获取对象,而无需关心对象的具体创建过程。

3.2 使用场景

  1. 日志记录器:根据不同的需求,选择将日志记录到本地硬盘、系统事件、远程服务器等,可通过工厂模式来创建不同类型的日志记录器。
  2. 数据库访问:当系统可能使用不同类型的数据库(如 MySQL、Oracle 等)时,使用工厂模式可以方便地切换数据库访问实现。
  3. 网络协议框架:设计一个连接服务器的框架,涉及多种协议(如 POP3、IMAP、HTTP 等),通过工厂模式创建相应的协议对象。

3.3 注意事项

工厂模式适用于创建复杂对象的场景。对于简单对象,特别是可以直接通过new关键字创建的对象,使用工厂模式可能会增加系统的复杂度,此时不一定需要使用工厂模式。

3.4 简单工厂模式

简单工厂模式是工厂模式的基础形式,它定义了一个工厂类,用于创建产品对象。以下是简单工厂模式的示例:

// 定义产品接口
interface Product {
    void operation();
}

// 具体产品类
class ConcreteProductA implements Product {
    @Override
    public void operation() {
        System.out.println("ConcreteProductA: performing operation");
    }
}

// 具体产品类
class ConcreteProductB implements Product {
    @Override
    public void operation() {
        System.out.println("ConcreteProductB: performing operation");
    }
}

// 工厂类
class SimpleFactory {
    public static Product createProduct(String type) {
        if ("A".equals(type)) {
            return new ConcreteProductA();
        } else if ("B".equals(type)) {
            return new ConcreteProductB();
        }
        return null;
    }
}

在客户端中使用简单工厂:

public class SimpleFactoryPatternDemo {
    public static void main(String[] args) {
        Product productA = SimpleFactory.createProduct("A");
        productA.operation();

        Product productB = SimpleFactory.createProduct("B");
        productB.operation();
    }
}

简单工厂模式的缺点是扩展性差,当需要新增产品时,需要修改工厂类的代码。

3.5 一般工厂模式(工厂方法模式)

工厂方法模式将对象的创建逻辑延迟到具体的工厂子类中实现。以下是工厂方法模式的示例:

// 定义产品接口
interface Product {
    void operation();
}

// 具体产品类
class ConcreteProductA implements Product {
    @Override
    public void operation() {
        System.out.println("ConcreteProductA: performing operation");
    }
}

// 具体产品类
class ConcreteProductB implements Product {
    @Override
    public void operation() {
        System.out.println("ConcreteProductB: performing operation");
    }
}

// 定义工厂接口
interface Factory {
    Product createProduct();
}

// 具体工厂类
class ConcreteFactoryA implements Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductA();
    }
}

// 具体工厂类
class ConcreteFactoryB implements Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductB();
    }
}

在客户端中使用工厂方法模式:

public class FactoryMethodPatternDemo {
    public static void main(String[] args) {
        Factory factoryA = new ConcreteFactoryA();
        Product productA = factoryA.createProduct();
        productA.operation();

        Factory factoryB = new ConcreteFactoryB();
        Product productB = factoryB.createProduct();
        productB.operation();
    }
}

工厂方法模式提高了代码的扩展性,新增产品时只需创建新的具体工厂类和产品类,无需修改原有的工厂接口和其他具体工厂类。

3.6 抽象工厂模式

抽象工厂模式提供了创建一系列相关或相互依赖对象的接口,由具体的工厂类来实现这些接口。以下是抽象工厂模式的示例:

// 定义产品接口
interface ProductA {
    void operationA();
}

interface ProductB {
    void operationB();
}

// 具体产品类
class ConcreteProductA1 implements ProductA {
    @Override
    public void operationA() {
        System.out.println("ConcreteProductA1: performing operationA");
    }
}

class ConcreteProductA2 implements ProductA {
    @Override
    public void operationA() {
        System.out.println("ConcreteProductA2: performing operationA");
    }
}

class ConcreteProductB1 implements ProductB {
    @Override
    public void operationB() {
        System.out.println("ConcreteProductB1: performing operationB");
    }
}

class ConcreteProductB2 implements ProductB {
    @Override
    public void operationB() {
        System.out.println("ConcreteProductB2: performing operationB");
    }
}

// 定义抽象工厂接口
interface AbstractFactory {
    ProductA createProductA();
    ProductB createProductB();
}

// 具体工厂类
class ConcreteFactory1 implements AbstractFactory {
    @Override
    public ProductA createProductA() {
        return new ConcreteProductA1();
    }

    @Override
    public ProductB createProductB() {
        return new ConcreteProductB1();
    }
}

class ConcreteFactory2 implements AbstractFactory {
    @Override
    public ProductA createProductA() {
        return new ConcreteProductA2();
    }

    @Override
    public ProductB createProductB() {
        return new ConcreteProductB2();
    }
}

在客户端中使用抽象工厂模式:

public class AbstractFactoryPatternDemo {
    public static void main(String[] args) {
        AbstractFactory factory1 = new ConcreteFactory1();
        ProductA productA1 = factory1.createProductA();
        ProductB productB1 = factory1.createProductB();
        productA1.operationA();
        productB1.operationB();

        AbstractFactory factory2 = new ConcreteFactory2();
        ProductA productA2 = factory2.createProductA();
        ProductB productB2 = factory2.createProductB();
        productA2.operationA();
        productB2.operationB();
    }
}

抽象工厂模式适用于创建一系列相关对象的场景,通过抽象工厂接口和具体工厂类的实现,提高了系统的可维护性和扩展性。

四、观察者模式

4.1 模式概念

观察者模式是一种行为型设计模式,当对象间存在一对多关系时使用。一个对象(被观察者)的状态发生改变时,会自动通知所有依赖它的对象(观察者)。

4.2 应用场景

例如,在股票交易系统中,多个投资者(观察者)关注某只股票(被观察者)的价格变化。当股票价格发生变化时,系统会自动通知所有关注该股票的投资者。

4.3 示例代码

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

// 定义被观察者接口
interface Observable {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

// 具体被观察者类
class Stock implements Observable {
    private String symbol;
    private double price;
    private List<Observer> observers = new ArrayList<>();

    public Stock(String symbol) {
        this.symbol = symbol;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
        notifyObservers();
    }

    @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(this);
        }
    }
}

// 定义观察者接口
interface Observer {
    void update(Observable observable);
}

// 具体观察者类
class Investor implements Observer {
    private String name;

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

    @Override
    public void update(Observable observable) {
        if (observable instanceof Stock) {
            Stock stock = (Stock) observable;
            System.out.println(name + " received update: " + stock.getPrice());
        }
    }
}

在客户端中使用观察者模式:

public class ObserverPatternDemo {
    public static void main(String[] args) {
        Stock stock = new Stock("AAPL");
        Investor investor1 = new Investor("Investor 1");
        Investor investor2 = new Investor("Investor 2");

        stock.addObserver(investor1);
        stock.addObserver(investor2);

        stock.setPrice(150.0);
    }
}

五、享元模式

5.1 模式原理

享元模式是一种结构型设计模式,它通过共享对象来减少对象的创建数量,从而降低内存占用和提高性能。享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

5.2 优点

  1. 大大减少对象的创建,降低系统的内存消耗,提高系统的性能。
  2. 提高了对象的复用性,减少了资源的浪费。

5.3 缺点

  1. 提高了系统的复杂度,需要准确区分对象的外部状态和内部状态。
  2. 外部状态的管理需要额外的工作,且外部状态应保持稳定,否则可能会导致系统混乱。

5.4 使用场景

  1. 系统中有大量相似对象,如游戏中的大量相同类型的角色、地图元素等。
  2. 需要缓冲池的场景,如数据库连接池、线程池等。

5.5 示例代码

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

// 定义享元接口
interface Shape {
    void draw();
}

// 具体享元类
class Circle implements Shape {
    private String color;
    private int x;
    private int y;
    private int radius;

    public Circle(String color) {
        this.color = color;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public void setRadius(int radius) {
        this.radius = radius;
    }

上述六种设计模式从不同角度为软件开发提供了解决方案。单例模式确保实例唯一性,有效节省资源;代理模式通过中间层实现对目标对象的访问控制与功能增强;工厂模式将对象创建逻辑封装,便于管理与扩展;观察者模式实现对象间的联动响应;享元模式通过对象共享减少内存占用;策略模式支持算法的动态切换,提升系统灵活性。这些设计模式各有优劣和适用场景,开发者在实际项目中,需根据具体需求与业务场景灵活选择、组合运用,从而构建出结构清晰、可维护性高、性能优良的软件系统 。