一、工厂模式​

(一)简单工厂模式​

想象一下,你走进一家奶茶店,想要一杯珍珠奶茶。在简单工厂模式中,奶茶店就像是一个工厂,它负责创建各种奶茶。我们先来定义一个抽象的奶茶类Drink,作为所有具体奶茶类的父类:​

TypeScript取消自动换行复制

// 抽象产品:饮料​

然后,创建具体的珍珠奶茶类BubbleTea,它实现了Drink接口:​

TypeScript取消自动换行复制

// 具体产品:珍珠奶茶​

接下来,就是简单工厂类DrinkFactory登场了,它有一个静态方法createDrink,根据传入的类型参数来创建相应的奶茶对象:​

TypeScript取消自动换行复制

// 工厂类​

在客户端代码中,我们就可以通过DrinkFactory来获取一杯珍珠奶茶,而无需关心奶茶的具体制作过程:​

TypeScript取消自动换行复制

// 使用​

简单工厂模式的优点在于它将对象的创建逻辑封装在工厂类中,客户端只需关心传入的参数,就能获取到所需的对象,实现了创建和使用的分离。然而,它也存在一些缺点。比如,当我们需要添加新的奶茶品种时,就需要修改DrinkFactory类的createDrink方法,这违背了开闭原则(对扩展开放,对修改关闭)。而且,如果奶茶的种类不断增加,createDrink方法的逻辑会变得越来越复杂,不利于维护。​

(二)工厂方法模式​

为了解决简单工厂模式的不足,我们引入工厂方法模式。在工厂方法模式中,我们不再由一个单一的工厂类来创建所有的产品,而是为每种产品创建一个对应的工厂类。​

首先,定义一个抽象的工厂接口DrinkFactory,它声明了一个创建Drink对象的抽象方法createDrink:​

TypeScript取消自动换行复制

// 抽象工厂​

interface DrinkFactory {​

Drink createDrink();​

}​

然后,创建珍珠奶茶的具体工厂类BubbleTeaFactory,它实现了DrinkFactory接口,并在createDrink方法中创建BubbleTea对象:​

TypeScript取消自动换行复制

// 具体工厂:珍珠奶茶工厂​

class BubbleTeaFactory implements DrinkFactory {​

@Override​

public Drink createDrink() {​

return new BubbleTea();​

}​

}​

客户端代码使用时,通过创建BubbleTeaFactory对象,再调用其createDrink方法来获取珍珠奶茶:​

TypeScript取消自动换行复制

// 使用​

DrinkFactory factory = new BubbleTeaFactory();​

Drink tea = factory.createDrink();​

tea.make(); ​

工厂方法模式的优点非常明显。当我们要添加新的奶茶品种时,只需要创建一个新的具体产品类和对应的具体工厂类,而无需修改现有的工厂类代码,完全符合开闭原则。同时,每个工厂类只负责创建一种产品,职责更加单一,代码的可维护性和扩展性大大提高。但是,工厂方法模式也带来了一些额外的类,增加了系统的复杂度。如果奶茶的种类非常多,就会导致大量的工厂类出现。​

(三)抽象工厂模式​

抽象工厂模式是工厂模式的进一步扩展,它用于创建一系列相关或相互依赖的对象。假设我们的奶茶店不仅提供奶茶,还提供小吃,如薯条、鸡翅等。我们可以使用抽象工厂模式来创建奶茶和小吃的组合。​

首先,定义抽象的奶茶类Drink和小吃类Snack:​

TypeScript取消自动换行复制

// 抽象产品:饮料​

interface Drink {​

void make();​

}​

// 抽象产品:小吃​

interface Snack {​

void make();​

}​

然后,创建具体的珍珠奶茶类BubbleTea和薯条类FrenchFries,分别实现Drink和Snack接口:​

TypeScript取消自动换行复制

// 具体产品:珍珠奶茶​

class BubbleTea implements Drink {​

@Override​

public void make() {​

System.out.println("制作珍珠奶茶:泡茶+加珍珠+加糖");​

}​

}​

// 具体产品:薯条​

class FrenchFries implements Snack {​

@Override​

public void make() {​

System.out.println("制作薯条:切土豆+油炸");​

}​

}​

接着,定义抽象工厂接口ShopFactory,它包含创建Drink和Snack对象的抽象方法:​

TypeScript取消自动换行复制

// 抽象工厂​

interface ShopFactory {​

Drink createDrink();​

Snack createSnack();​

}​

再创建具体的工厂类BubbleTeaShopFactory,实现ShopFactory接口,创建珍珠奶茶和薯条对象:​

TypeScript取消自动换行复制

客户端代码使用时,通过创建BubbleTeaShopFactory对象,调用其方法来获取珍珠奶茶和薯条的组合:​

TypeScript取消自动换行复制

// 使用​

ShopFactory factory = new BubbleTeaShopFactory();​

Drink tea = factory.createDrink();​

Snack snack = factory.createSnack();​

tea.make();​

snack.make(); ​

抽象工厂模式的优势在于它能够创建一系列相关的产品对象,客户端只需要与抽象工厂交互,而无需关心具体产品的创建细节。这使得系统具有更好的可维护性和可扩展性。当我们要添加新的产品组合时,只需要创建新的具体工厂类,实现抽象工厂接口即可。但是,抽象工厂模式也使得系统更加复杂,因为它涉及到多个产品族和多个抽象层次。如果产品的变化频繁,或者产品之间的依赖关系复杂,抽象工厂模式可能会变得难以维护。​

二、单例模式​

在奶茶店的运营中,我们可能会遇到一些需要全局唯一的资源或对象。比如,奶茶店的库存管理系统,整个系统只需要一个库存实例来管理所有商品的库存信息。这时候,单例模式就派上用场了。​

(一)饿汉式单例​

饿汉式单例在类加载时就立即创建唯一实例,就像奶茶店一开业就准备好了唯一的库存实例。其实现代码如下:​

TypeScript取消自动换行复制

饿汉式单例的优点是实现简单,并且天然具备线程安全性,因为类加载过程由 JVM 保证线程安全。不过,如果这个实例在整个应用生命周期中从未被使用,就会造成内存的浪费,就好比奶茶店准备了一个永远不会用到的库存实例,占用了宝贵的内存空间。​

(二)懒汉式单例(线程不安全)​

懒汉式单例则恰恰相反,它直到第一次被调用时才创建实例,就像奶茶店在第一次需要管理库存时才创建库存实例,做到了延迟加载,避免了资源浪费。但是,在多线程并发环境下,这种实现方式会出现问题,可能会创建多个实例。其代码如下:​

TypeScript取消自动换行复制

例如,当多个线程同时判断instance为空,都进入创建实例的代码块时,就会导致单例的唯一性被破坏。​

(三)懒汉式单例(线程安全版)​

为了解决懒汉式线程不安全的问题,我们可以在获取实例的方法上添加synchronized关键字,使其变为线程安全的。这就像给获取库存实例的通道加上了一把锁,每次只允许一个线程进入获取实例。代码如下:​

TypeScript取消自动换行复制

// 懒汉式单例(线程安全版)​

class Inventory {​

private static Inventory instance;​

private Inventory() {}​

public static synchronized Inventory getInstance() {​

if (instance == null) {​

instance = new Inventory();​

}​

return instance;​

}​

}​

然而,这种方式带来了性能损耗,因为每次调用getInstance方法都需要进行加锁和解锁操作,即使实例已经创建好了,后续的调用也会受到锁的影响,降低了程序的并发性能。​

(四)双重检查锁(DCL)​

双重检查锁(DCL)是对线程安全版懒汉式的优化。它通过两次检查实例是否为空,大大减少了不必要的加锁操作。第一次检查在同步代码块外,若实例已存在,直接返回,避免了进入同步代码块的开销;第二次检查在同步代码块内,确保在多线程环境下只有一个线程能创建实例。同时,使用volatile关键字修饰实例变量,保证了变量的可见性和禁止指令重排序,防止在多线程环境下出现空指针异常。代码如下:​

TypeScript取消自动换行复制

// 双重检查锁(DCL)单例​

class Inventory {​

private static volatile Inventory instance;​

private Inventory() {}​

public static Inventory getInstance() {​

if (instance == null) {​

synchronized (Inventory.class) {​

if (instance == null) {​

instance = new Inventory();​

}​

}​

}​

return instance;​

}​

}​

(五)静态内部类实现单例​

静态内部类实现单例模式巧妙地利用了 Java 的类加载机制。在外部类加载时,静态内部类并不会被加载,只有当第一次调用getInstance方法时,才会触发静态内部类的加载,从而创建唯一的实例。这就实现了延迟加载,同时 JVM 的类加载机制保证了实例创建的线程安全性,不需要额外的同步操作。代码如下:​

TypeScript取消自动换行复制

// 静态内部类单例​

class Inventory {​

private Inventory() {}​

private static class InventoryHolder {​

private static final Inventory INSTANCE = new Inventory();​

}​

public static Inventory getInstance() {​

return InventoryHolder.INSTANCE;​

}​

}​

(六)枚举实现单例​

枚举实现单例模式是一种简洁且强大的方式。枚举类型在 Java 中是天然线程安全的,并且由 JVM 保证其唯一性。它还能防止反射攻击,因为反射无法创建枚举实例。此外,枚举实现的单例支持序列化,在反序列化时不会创建新的实例,确保了单例在序列化和反序列化过程中的一致性。代码如下:​

TypeScript取消自动换行复制

// 枚举单例​

enum Inventory {​

INSTANCE;​

// 可以在枚举中添加其他方法和属性​

}​

在实际应用中,我们可以根据具体的需求选择合适的单例模式实现方式。如果对性能要求较高,并且不需要延迟加载,饿汉式单例是一个不错的选择;如果需要延迟加载,并且在多线程环境下使用,静态内部类或枚举实现的单例模式是比较推荐的。​

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐