工厂方法模式

73 阅读6分钟

模式介绍

定义了一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例化推迟到子类。

模式图解

原理类图

image.png

类图说明

  • 抽象工厂:担任这个角色的是工厂方法模式的核心,它是与应用程序无关的。任何在模式中创建对象的工厂类必须实现这个接口。这个角色提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。实际系统中,该角色常使用抽象 Java 类实现,对应类图中的CoffeeFactory抽象类。
  • 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。具体工厂角色含有与应用密切相关的逻辑,并且受到应用程序的调用以创建产品对象。对应类图中的AmericanCoffeeFactory类和LatteCoffeeFactory类。
  • 抽象产品:工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或者共同拥有的接口。该角色定义了产品的规范,描述了产品的主要特性和功能。实际系统中,该角色常使用抽象 Java 类实现,对应类图中的Coffee抽象类。
  • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。对应类图中的AmericanCoffee类和LatteCoffee类。

示例代码

package model;

/**
 * 抽象工厂
 *
 * @author asyyr
 */
public interface CoffeeFactory {

    Coffee createCoffee();
}
package model;

/**
 * 具体工厂
 *
 * @author asyyr
 */
public class AmericanCoffeeFactory implements CoffeeFactory {

    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}
package model;

/**
 * 具体工厂
 *
 * @author asyyr
 */
public class LatteCoffeeFactory implements CoffeeFactory {

    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}
package model;

/**
 * 抽象产品
 *
 * @author asyyr
 */
public abstract class Coffee {
    public abstract String getName();

    void addMilk() {
        System.out.println("add Milk");
    }

    void addSugar() {
        System.out.println("add Sugar");
    }
}
package model;

/**
 * 具体产品
 *
 * @author asyyr
 */
public class AmericanCoffee extends Coffee {

    @Override
    String getName() {
        return "American Coffee";
    }
}
package model;

/**
 * 具体产品
 *
 * @author asyyr
 */
public class LatteCoffee extends Coffee {

    @Override
    String getName() {
        return "Latte Coffee";
    }
}
package model;

/**
 * 客户端代码
 *
 * @author asyyr
 */
public class CoffeeStore {
    private CoffeeFactory factory;
    
    // 通过构造器注入工厂,避免直接依赖具体工厂
    public CoffeeStore(CoffeeFactory factory) {
        this.factory = factory;
    }
    
    public Coffee orderCoffee() {
        Coffee coffee = factory.createCoffee();
        coffee.addMilk();
        coffee.addSugar();
        System.out.println("Created: " + coffee.getName());
        return coffee;
    }
}
package model;

/**
 * 测试类
 *
 * @author asyyr
 */
public class Test {

    public static void main(String[] args) {
        // 使用美式咖啡工厂
        CoffeeStore store1 = new CoffeeStore(new AmericanCoffeeFactory());
        Coffee coffee1 = store1.orderCoffee();
        
        // 使用拿铁咖啡工厂
        CoffeeStore store2 = new CoffeeStore(new LatteCoffeeFactory());
        Coffee coffee2 = store2.orderCoffee();
    }
}

实现原理

工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的 优点,而且克服了它的缺点。

与简单工厂模式对比

工厂方法模式和简单工厂模式在结构上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。工厂方法模式可以允许很多具体工厂类从抽象工厂类中将创建行为继承下来,从而可以成为多个简单工厂模式的综合,进而推广了简单工厂模式。

工厂方法模式退化后可以变得很像简单工厂模式,设想如果非常确定一个系统只需要一个具体工厂类,那么就不妨把抽象工厂类合并到具体的工厂类中去。由于只有一个具体工厂类,所以不妨将工厂方法改为静态方法,这时候就得到了简单工厂模式。

与简单工厂模式中的情形一样的是,抽象工厂类中定义的方法返还的数据类型是一个抽象产品类,而不是一个具体产品类型,而客户端也不必知道所得到的产品的真实类型。这种多态性设计将工厂类选择创建哪一个产品对象、如何创建这个对象的细节完全封装在具体工厂类内部。

工厂方法模式之所以有一个别名叫多态性工厂模式,显然是因为具体工厂类都有共同的接口,或者都有共同的抽象父类。

如果系统需要加入一个新的产品,那么所需要的就是向系统中加入一个这个产品类以及它所对应的工厂类。没有必要修改客户端,也没有必要修改抽象工厂角色或者其它已有的具体工厂角色。对于增加新的产品类而言,这个系统完全支持“开-闭”原则。

优点

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改, 满足开闭原则

缺点

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

与其它模式的关系

模版方法模式

工厂方法模式常常与模版方法模式一起联合使用。原因为:第一,两个模式都是基于方法的,工厂方法模式是基于多态性的工厂方法的,而模版方法模式是基于模版方法和基本方法的;第二,两个模式都将具体工作交给子类。工厂方法模式将创建工作推延给子类,模版方法模式将剩余逻辑交给子类。

从各个工厂角色组成的工厂等级结构上看,抽象工厂角色中可以加入一个模版方法,代表某个顶级逻辑。而这个模版方法调用几个基本方法,这些基本方法中就可以有一个或者多个工厂方法。这些工厂方法代表剩余逻辑,交给具体子类实现。

模版方法本身也可能是工厂方法,它的对象创建过程就是所谓的顶级结构,而这个过程可以分为数个具体步骤,每一个步骤都是顶级逻辑的组成部分。

换言之,使用模版方法模式可以将某一个顶级行为分解成为数个创建行为,由子类中的工厂方法体现出现。不同的具体工厂类则提供了顶级逻辑中的剩余逻辑的不同实现。通过使用不同的具体工厂对象,客户端可以达到使用不同版本的顶级逻辑的目的。

MVC模式

享元模式

备忘录模式

应用场景

Spring 的 FactoryBean

注意事项

待补充