一、引言

在 Java 开发的世界里,设计模式就像是一把把神奇的钥匙,能够帮助我们解决各种复杂的问题,让代码更加优雅、可维护和可扩展。今天咱们就来聊聊三种非常实用的设计模式:单例模式、工厂模式和观察者模式。这三种模式在实际开发中应用广泛,理解并掌握它们,能让你的 Java 编程水平更上一层楼。

二、单例模式

2.1 应用场景

单例模式就像是一个独一无二的“管家”,在整个系统中,它只允许创建一个实例。这种模式在很多场景下都非常有用,比如数据库连接池、日志记录器、线程池等。这些组件在系统中只需要一个实例就够了,如果创建多个实例,可能会导致资源浪费或者数据不一致的问题。

2.2 实现方式

2.2.1 饿汉式单例

// 饿汉式单例,在类加载时就创建实例
public class EagerSingleton {
    // 私有静态成员变量,存储单例实例
    private static final EagerSingleton INSTANCE = new EagerSingleton();

    // 私有构造方法,防止外部创建实例
    private EagerSingleton() {}

    // 公共静态方法,获取单例实例
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

饿汉式单例的优点是实现简单,线程安全。因为在类加载时就创建了实例,所以不会出现多个线程同时创建实例的问题。缺点是如果这个单例实例在系统中一直没有被使用,会造成内存的浪费。

2.2.2 懒汉式单例

// 懒汉式单例,在第一次使用时创建实例
public class LazySingleton {
    // 私有静态成员变量,存储单例实例
    private static LazySingleton instance;

    // 私有构造方法,防止外部创建实例
    private LazySingleton() {}

    // 公共静态方法,获取单例实例
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

懒汉式单例的优点是在第一次使用时才创建实例,避免了内存的浪费。缺点是使用了synchronized关键字,会影响性能,因为每次调用getInstance()方法都会进行同步操作。

2.2.3 双重检查锁定单例

// 双重检查锁定单例,结合了懒汉式和线程安全
public class DoubleCheckedLockingSingleton {
    // 私有静态成员变量,使用 volatile 关键字保证可见性
    private static volatile DoubleCheckedLockingSingleton instance;

    // 私有构造方法,防止外部创建实例
    private DoubleCheckedLockingSingleton() {}

    // 公共静态方法,获取单例实例
    public static DoubleCheckedLockingSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLockingSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
}

双重检查锁定单例结合了懒汉式和线程安全的优点,既避免了内存的浪费,又保证了线程安全。使用volatile关键字可以防止指令重排序,确保在多线程环境下不会出现问题。

2.3 技术优缺点

优点:

  • 节省系统资源,避免重复创建实例。
  • 保证一个类只有一个实例,便于对实例进行控制和管理。

缺点:

  • 可能会造成内存泄漏,如果单例实例持有大量的资源,并且在系统中一直不释放。
  • 违反了单一职责原则,单例类既要负责自身的创建,又要负责业务逻辑。

2.4 注意事项

  • 在多线程环境下,要确保单例的创建是线程安全的。
  • 避免在单例类中使用静态变量,因为静态变量会在类加载时就被初始化,可能会导致单例模式失效。

三、工厂模式

3.1 应用场景

工厂模式就像是一个“工厂老板”,负责创建对象。当创建对象的逻辑比较复杂,或者需要根据不同的条件创建不同类型的对象时,使用工厂模式可以将对象的创建和使用分离,提高代码的可维护性和可扩展性。比如在游戏开发中,根据不同的关卡创建不同的怪物;在电商系统中,根据不同的支付方式创建不同的支付对象。

3.2 实现方式

3.2.1 简单工厂模式

// 定义一个产品接口
interface Product {
    void use();
}

// 具体产品类 A
class ConcreteProductA implements Product {
    @Override
    public void use() {
        System.out.println("使用产品 A");
    }
}

// 具体产品类 B
class ConcreteProductB implements Product {
    @Override
    public void use() {
        System.out.println("使用产品 B");
    }
}

// 简单工厂类
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;
    }
}

简单工厂模式的优点是实现简单,使用方便。缺点是工厂类的职责过重,如果需要添加新的产品,需要修改工厂类的代码,违反了开闭原则。

3.2.2 工厂方法模式

// 定义一个产品接口
interface Product {
    void use();
}

// 具体产品类 A
class ConcreteProductA implements Product {
    @Override
    public void use() {
        System.out.println("使用产品 A");
    }
}

// 具体产品类 B
class ConcreteProductB implements Product {
    @Override
    public void use() {
        System.out.println("使用产品 B");
    }
}

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

// 具体工厂类 A,负责创建产品 A
class ConcreteFactoryA implements Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductA();
    }
}

// 具体工厂类 B,负责创建产品 B
class ConcreteFactoryB implements Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductB();
    }
}

工厂方法模式将对象的创建延迟到具体的工厂子类中,每个具体的工厂类负责创建一种具体的产品。优点是符合开闭原则,如果需要添加新的产品,只需要创建一个新的具体工厂类即可,不需要修改现有的代码。缺点是工厂子类过多,会导致代码结构变得复杂。

3.3 技术优缺点

优点:

  • 降低了代码的耦合度,将对象的创建和使用分离。
  • 提高了代码的可维护性和可扩展性,符合开闭原则。

缺点:

  • 增加了代码的复杂度,需要创建多个工厂类。
  • 工厂类的职责可能会过重,尤其是在简单工厂模式中。

3.4 注意事项

  • 合理设计工厂类的职责,避免工厂类的职责过重。
  • 在使用工厂方法模式时,要注意工厂子类的管理,避免子类过多导致代码结构混乱。

四、观察者模式

4.1 应用场景

观察者模式就像是一场“发布会”,有一个“发布者”(被观察对象)和多个“观察者”。当发布者的状态发生变化时,会通知所有的观察者,让它们做出相应的处理。这种模式在很多场景下都非常有用,比如股票行情系统,当股票价格发生变化时,会通知所有的股民;在消息推送系统中,当有新的消息时,会通知所有的订阅者。

4.2 实现方式

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

// 定义一个观察者接口
interface Observer {
    void update(String message);
}

// 定义一个被观察对象类
class Subject {
    // 存储观察者的列表
    private List<Observer> observers = new ArrayList<>();
    // 存储状态信息
    private String state;

    // 注册观察者
    public void attach(Observer observer) {
        observers.add(observer);
    }

    // 移除观察者
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    // 通知所有观察者
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(state);
        }
    }

    // 设置状态信息
    public void setState(String state) {
        this.state = state;
        notifyObservers();
    }
}

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

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

    @Override
    public void update(String message) {
        System.out.println(name + " 收到消息:" + message);
    }
}

4.3 技术优缺点

优点:

  • 实现了对象之间的松耦合,被观察对象和观察者之间不需要直接依赖。
  • 支持广播通信,当被观察对象的状态发生变化时,会通知所有的观察者。

缺点:

  • 如果观察者过多,通知所有观察者的时间会变长,影响系统的性能。
  • 观察者和被观察对象之间可能会出现循环依赖的问题。

4.4 注意事项

  • 避免观察者和被观察对象之间的循环依赖,否则会导致系统出现死循环。
  • 在通知观察者时,要考虑性能问题,可以采用异步通知的方式。

五、文章总结

单例模式、工厂模式和观察者模式是 Java 开发中非常实用的设计模式。单例模式确保一个类只有一个实例,节省系统资源;工厂模式将对象的创建和使用分离,提高代码的可维护性和可扩展性;观察者模式实现了对象之间的松耦合,支持广播通信。

在实际开发中,我们要根据具体的应用场景选择合适的设计模式。同时,要注意设计模式的优缺点和注意事项,合理使用设计模式,才能让我们的代码更加健壮、高效。