杰瑞科技汇

Java工厂模式的核心优势与应用场景是什么?

工厂模式是 Java 中最常用的设计模式之一,它属于创建型模式,其核心思想是将对象的创建和使用分离

Java工厂模式的核心优势与应用场景是什么?-图1
(图片来源网络,侵删)

就是创建对象的地方被封装起来,客户端(调用者)不需要知道具体对象是如何创建的,只需要向工厂请求一个对象即可,这极大地降低了系统的耦合度。


为什么需要工厂模式?(解决的问题)

假设我们没有使用工厂模式,要创建一个产品对象:

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 客户端直接依赖于具体的产品类
        ProductA product = new ProductA();
        product.use();
    }
}

这里存在几个问题:

  1. 高耦合:客户端代码 new ProductA() 直接依赖于 ProductA 这个具体类,如果将来需要更换 ProductAProductB,或者需要根据配置动态选择产品,就必须修改客户端代码。
  2. 违反开闭原则:对扩展开放,对修改关闭,如果系统需要增加一个新的产品 ProductC,我们就必须修改客户端代码,去添加 new ProductC() 的逻辑,而不是简单地扩展。
  3. 职责混乱:客户端的职责是“使用产品”,但它却承担了“创建产品”的职责。

工厂模式就是为了解决这些问题而生的。

Java工厂模式的核心优势与应用场景是什么?-图2
(图片来源网络,侵删)

工厂模式的几种形态

工厂模式不是单一的模式,它根据抽象程度的不同,主要有以下三种形态:

  1. 简单工厂模式
  2. 工厂方法模式
  3. 抽象工厂模式

下面我们逐一讲解。


简单工厂模式

它不属于 GoF 的 23 种经典设计模式,但因其简单易懂,在实际开发中非常常用,所以通常作为学习工厂模式的入门。

核心思想:由一个工厂类来负责创建所有产品类的实例,客户端传入一个参数,工厂根据这个参数决定创建哪种产品。

结构

  • 产品接口:定义所有产品共有的接口。
  • 具体产品:实现产品接口的具体类。
  • 工厂类:负责创建所有具体产品。

代码示例

产品接口

// Product.java
public interface Product {
    void use();
}

具体产品

// ProductA.java
public class ProductA implements Product {
    @Override
    public void use() {
        System.out.println("使用产品 A");
    }
}
// ProductB.java
public class ProductB implements Product {
    @Override
    public void use() {
        System.out.println("使用产品 B");
    }
}

工厂类

// SimpleFactory.java
public class SimpleFactory {
    // 根据参数创建不同的产品实例
    public static Product createProduct(String type) {
        if ("A".equalsIgnoreCase(type)) {
            return new ProductA();
        } else if ("B".equalsIgnoreCase(type)) {
            return new ProductB();
        }
        throw new IllegalArgumentException("未知的产品类型: " + type);
    }
}

客户端使用

// Client.java
public class Client {
    public static void main(String[] args) {
        // 客户端通过工厂获取产品,而不直接 new
        Product productA = SimpleFactory.createProduct("A");
        productA.use(); // 输出: 使用产品 A
        Product productB = SimpleFactory.createProduct("B");
        productB.use(); // 输出: 使用产品 B
    }
}

优点

  • 客户端代码非常简单,只需知道工厂和产品接口即可。
  • 将创建逻辑集中到了一处,便于管理。

缺点

  • 工厂类职责过重,包含了所有产品的创建逻辑,一旦产品种类增多,工厂类会变得非常庞大和臃肿。
  • 违反开闭原则,每当增加一个新产品时,都需要修改工厂类的 createProduct 方法。

工厂方法模式

这是 GoF 正式定义的工厂模式,它解决了简单工厂模式中工厂类职责过重的问题。

核心思想:定义一个用于创建对象的接口(工厂接口),但让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。

结构

  • 产品接口:定义产品的接口。
  • 具体产品:实现产品接口。
  • 工厂接口:定义一个创建产品的抽象方法 createProduct()
  • 具体工厂:实现工厂接口,负责创建一个具体的产品。

代码示例

产品接口和具体产品 (与简单工厂模式相同)

// Product.java
public interface Product {
    void use();
}
// ProductA.java
public class ProductA implements Product {
    @Override
    public void use() {
        System.out.println("使用产品 A");
    }
}
// ProductB.java
public class ProductB implements Product {
    @Override
    public void use() {
        System.out.println("使用产品 B");
    }
}

工厂接口和具体工厂

// Factory.java
public interface Factory {
    Product createProduct();
}
// FactoryA.java
public class FactoryA implements Factory {
    @Override
    public Product createProduct() {
        return new ProductA(); // 具体工厂 A 创建具体产品 A
    }
}
// FactoryB.java
public class FactoryB implements Factory {
    @Override
    public Product createProduct() {
        return new ProductB(); // 具体工厂 B 创建具体产品 B
    }
}

客户端使用

// Client.java
public class Client {
    public static void main(String[] args) {
        // 客户端需要知道并选择具体的工厂
        Factory factoryA = new FactoryA();
        Product productA = factoryA.createProduct();
        productA.use(); // 输出: 使用产品 A
        Factory factoryB = new FactoryB();
        Product productB = factoryB.createProduct();
        productB.use(); // 输出: 使用产品 B
    }
}

优点

  • 符合开闭原则:增加新产品时,只需增加一个具体产品类和一个对应的具体工厂类,而无需修改已有的工厂代码。
  • 职责清晰:每个具体工厂只负责创建一个具体的产品,符合单一职责原则。

缺点

  • 类的数量增多:每增加一个产品,就需要增加一个对应的工厂类,这会导致系统中类的数量成对增加,增加了系统的复杂度。

抽象工厂模式

当产品族出现时,工厂方法模式就显得力不从心了,抽象工厂模式应运而生。

核心思想:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类,它强调的是一系列相关产品的创建。

结构

  • 产品族接口ProductAProductB 可以是一个产品族(都属于品牌1),ProductA2ProductB2 可以是另一个产品族(都属于品牌2)。
  • 具体产品:实现产品族接口。
  • 抽象工厂接口:定义创建整个产品族的方法,如 createProductA()createProductB()
  • 具体工厂:实现抽象工厂接口,负责创建一个完整的产品族。

代码示例

假设我们有两个产品族:Product1Product2,每个产品族都有 AB 两种产品。

产品族接口

// 产品族 1
public interface ProductA1 {
    void use();
}
// 产品族 2
public interface ProductA2 {
    void use();
}
// 产品族 1
public interface ProductB1 {
    void use();
}
// 产品族 2
public interface ProductB2 {
    void use();
}

具体产品

// 品牌1 的产品
public class ConcreteProductA1 implements ProductA1 {
    @Override
    public void use() {
        System.out.println("使用品牌1的产品 A1");
    }
}
public class ConcreteProductB1 implements ProductB1 {
    @Override
    public void use() {
        System.out.println("使用品牌1的产品 B1");
    }
}
// 品牌2 的产品
public class ConcreteProductA2 implements ProductA2 {
    @Override
    public void use() {
        System.out.println("使用品牌2的产品 A2");
    }
}
public class ConcreteProductB2 implements ProductB2 {
    @Override
    public void use() {
        System.out.println("使用品牌2的产品 B2");
    }
}

抽象工厂接口

public interface AbstractFactory {
    // 创建产品族 A
    ProductA1 createProductA1();
    // 创建产品族 B
    ProductB1 createProductB1();
}

具体工厂

// 品牌1 的工厂
public class Factory1 implements AbstractFactory {
    @Override
    public ProductA1 createProductA1() {
        return new ConcreteProductA1();
    }
    @Override
    public ProductB1 createProductB1() {
        return new ConcreteProductB1();
    }
}
// 品牌2 的工厂
public class Factory2 implements AbstractFactory {
    @Override
    public ProductA1 createProductA1() {
        return new ConcreteProductA2(); // 注意:这里返回的是产品族2的产品,这只是为了演示,通常接口名会不同
    }
    @Override
    public ProductB1 createProductB1() {
        return new ConcreteProductB2(); // 同上
    }
}

(注意:为了演示,上面的接口设计有点特殊,更常见的是 AbstractFactory 会定义 createA()createB(),然后每个工厂实现它们来创建自己品牌下的A和B产品。)

客户端使用

// Client.java
public class Client {
    public static void main(String[] args) {
        // 客户端使用抽象工厂
        AbstractFactory factory1 = new Factory1();
        ProductA1 a1 = factory1.createProductA1();
        ProductB1 b1 = factory1.createProductB1();
        a1.use(); // 输出: 使用品牌1的产品 A1
        b1.use(); // 输出: 使用品牌1的产品 B1
        AbstractFactory factory2 = new Factory2();
        ProductA1 a2 = factory2.createProductA1();
        ProductB1 b2 = factory2.createProductB1();
        a2.use(); // 输出: 使用品牌2的产品 A2
        b2.use(); // 输出: 使用品牌2的产品 B2
    }
}

优点

  • 保证产品族的一致性:客户端一次性获取的是一整个产品族,保证了产品之间的兼容性。
  • 符合开闭原则:增加新的产品族时,只需增加一个新的具体工厂即可。

缺点

  • 扩展产品非常困难:如果需要在产品族中增加一个新产品(ProductC),那么所有的具体工厂都需要进行修改,这违反了开闭原则,抽象工厂模式易于扩展产品族,但难于扩展新产品

总结与对比

特性 简单工厂模式 工厂方法模式 抽象工厂模式
创建对象 一个工厂类创建所有产品 每个产品对应一个工厂 一个工厂创建一个产品族
核心 if-elseswitch 判断 抽象工厂接口,子类实现 抽象工厂接口,创建多个产品
优点 客户端代码简单,创建集中 符合开闭原则,扩展产品方便 保证产品族一致性,扩展产品族方便
缺点 工厂类臃肿,违反开闭原则 类的数量增多,扩展产品族困难 扩展新产品困难,违反开闭原则
适用场景 产品种类少,创建逻辑简单 产品种类多,但产品族单一 需要创建多个相互关联的产品对象

何时使用?

  • 使用简单工厂模式:当你系统中的产品种类非常有限,并且未来不太可能频繁增加时,它是一个快速实现的好方法。
  • 使用工厂方法模式:当你系统中有多种产品,并且你希望在未来可以轻松地增加新的产品,而不影响现有代码时,这是最常用、最经典的工厂模式。
  • 使用抽象工厂模式:当你需要创建的对象是一个产品族,并且你希望确保客户端使用的是同一品牌下的、相互兼容的产品时,UI 皮肤(Windows 风格、macOS 风格),或者数据库连接(MySQL 连接池、Oracle 连接池)。

Java 中的实际应用

工厂模式在 Java 标准库和框架中无处不在:

  1. java.util.CalendargetInstance() 方法:这是一个典型的简单工厂或静态工厂方法,你不需要关心具体是 GregorianCalendar 还是其他 Calendar 的子类,调用 getInstance() 就会返回一个合适的实例。
  2. java.text.NumberFormatgetInstance() 方法:同样,根据你传入的 Locale(区域设置),它会返回一个适合该区域的数字格式化器。
  3. java.net.URLopenConnection() 方法:它根据 URL 的协议(http, https, ftp 等)返回一个合适的 URLConnection 对象。
  4. Spring 框架:Spring 的核心是 IoC(控制反转)容器,它就是一个巨大的工厂,你通过 @AutowiredgetBean() 方法向 Spring 工厂请求一个 Bean(对象),而无需关心这个 Bean 是如何被创建和配置的。

希望这个详细的解释能帮助你彻底理解 Java 中的工厂模式!

分享:
扫描分享到社交APP
上一篇
下一篇