工厂模式【简单工厂、工厂方法、抽象工厂】

工厂方法模式

披萨项目

某天,你赚了钱,打算开一家自己的披萨店,披萨有若干种类,不过每种披萨的制作步骤都差不多,都要经过准备、烘烤、切片、装盒这几个步骤。非常自然,我们在设计中,会抽象出一个披萨的父类,父类中定义了披萨的制作步骤(prepare,bake,cut,box),不同的披萨有不同的准备过程,而烘烤、切片和装盒都一样操作,因此在父类中实现。具体的披萨类型继承父类,实现自己的准备过程。

V1版本

好了,我们来实现一版。

披萨和他们的子类们

/**
 * 披萨父类
 */
public abstract class Pizza {

    protected String name;

    //具体的子类披萨知道如何准备自己
    public abstract void prepare();

    public void bake(){
        System.out.println(getName() + " 烘烤...");
    }

    public void cut(){
        System.out.println(getName() + " 切片...");
    }

    public void box(){
        System.out.println(getName() + " 装盒...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class CheesePizza extends Pizza{

    public CheesePizza() {
        setName("芝士披萨");
    }

    @Override
    public void prepare() {
        System.out.println("芝士披萨需要准备些芝士、芝麻、番茄、火腿,当然还有面团");
    }
}


public class ClamPizza extends Pizza{
    public ClamPizza() {
        setName("蛤蜊披萨");
    }

    @Override
    public void prepare() {
        System.out.println("蛤蜊披萨,必须有蛤蜊啊");
    }
}

public class PepperoniPizza extends Pizza{
    public PepperoniPizza() {
        setName("意式披萨");
    }
    @Override
    public void prepare() {
        System.out.println("意式披萨需要准备些芝麻、番茄、意大利火腿,橄榄油等,当然还有面团");
    }
}

/**
 * 蔬菜披萨
 */
public class VeggiePizza extends Pizza{
    public VeggiePizza() {
        setName("蔬菜披萨");
    }
    @Override
    public void prepare() {
        System.out.println("蔬菜披萨全TM是素的,吃毛线...");
    }
}

很简单,不多讲。

现在我们实现一个披萨店,可以接受披萨订单,并创建不同种类的披萨。
我们看下披萨店的V1版本实现

/**
 * V1版披萨店
 */
public class PizzaStoreV1 {

    /**
     * 下单
     * @param type 披萨类型
     * @return
     */
    public Pizza orderPizza(String type){
        Pizza pizza;

        if("cheese".equals(type)){
            pizza = new CheesePizza();
        }else if("pepperoni".equals(type)){
            pizza = new PepperoniPizza();
        }else if("clam".equals(type)){
            pizza = new ClamPizza();
        }else if("veggie".equals(type)){
            pizza = new VeggiePizza();
        }else{
            pizza = new CheesePizza();//不知道类型时,默认芝士披萨
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;

    }
}

测试下

/**
 * V1版测试
 */
public class PizzaV1Main {
    public static void main(String[] args) {
        PizzaStoreV1 pizzaStore = new PizzaStoreV1();
        //下单一个
        pizzaStore.orderPizza("clam");

        pizzaStore.orderPizza("veggie");
    }
}

V1版的披萨店也没什么特殊的,下单方法中,传入披萨的类型,创建对应类型的披萨,按照披萨制作步骤制作,最后返回。

在经营过程中,由于某些原因,你需要将菜单中加入一些新的披萨类型,也会将一些卖的不好的类型从菜单中拿掉。对于这样的修改,披萨类型容易,我们只要扩展一种新的披萨子类即可,符合开闭原则。但是在下单方法中,
通过使用if-else方式,和具体的披萨子类是强耦合的,也就说,如果要添加或者移除披萨类型,需要修orderPizza方法中的if-else块,违反开闭原则,另外,我们之前学到,要将变化和固定的区分开来,将变化的部分封装起来,也就是封装变化。
在我们的orderPizza方法中,哪些是变化的部分,哪些是固定的部分呢?

V2版本

现在,通过识别和封装变化,我们来实现V2版的披萨店看看效果吧。

/**
 * V2版披萨店
 */
public class PizzaStoreV2 {

    PizzaFactoryV2 pizzaFactory;

    public PizzaStoreV2(PizzaFactoryV2 pizzaFactory) {
        this.pizzaFactory = pizzaFactory;
    }

    /**
     * 下单
     * @param type 披萨类型
     * @return
     */
    public Pizza orderPizza(String type){
        Pizza pizza = pizzaFactory.createPizzaByType(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;

    }
}
/**
 * 披萨工厂(简单工厂)
 */
public class PizzaFactoryV2 {

    public Pizza createPizzaByType(String type){
        Pizza pizza;

        if("cheese".equals(type)){
            pizza = new CheesePizza();
        }else if("pepperoni".equals(type)){
            pizza = new PepperoniPizza();
        }else if("clam".equals(type)){
            pizza = new ClamPizza();
        }else if("veggie".equals(type)){
            pizza = new VeggiePizza();
        }else{
            pizza = new CheesePizza();//不知道类型时,默认芝士披萨
        }

        return pizza;
    }
}

咦,创建披萨的代码(变化的部分)移到一个单独的方法中,这个方法专门用于创建披萨。这样PizzaStoreV2在增加和移除类型时,是不用变化的。
你可能会说,增加或者移除类型时,PizzaFactoryV2这个类要修改啊,一样不符合开闭原则的。
嗯,你说的对,上面这种方式称之为简单工厂模式(如果创建方法定义为静态的,就叫静态工厂),实际开发中,相信大家不知觉的都会使用这种模式,简单实用有效。
不过吧,不符合开闭原则也是它的缺点。

说到这里,是不是一个系统的所有变化都要满足开闭原则呢?
答案是否,而且通常你也做不到。一般来说,我们没有必要将系统的每个部分都设计成满足开闭原则的(就算做到了,也可能是一种浪费)你需要把注意力集中到最后可能变化的部分,然后设计它满足开闭原则。

V3版本

上面这个简单工厂很好理解,我们继续。现在披萨店生意不错,名气也越来越好了,很多外地人也想享受这美味的披萨,是时候开启加盟模式了。
那么,现在我们需要在纽约和芝加哥开分店,而且纽约制作的是纽约风味的披萨,芝加哥制作的是芝加哥风味的披萨。为了便于后面理解,我们先把简单的产品(也就是披萨)建立起来。
纽约披萨风味

public class NYCheesePizza extends Pizza{

    public NYCheesePizza() {
        setName("纽约味芝士披萨");
    }

    @Override
    public void prepare() {
        System.out.println("芝士披萨需要准备些芝士、芝麻、番茄、火腿,当然还有面团");
    }
}

public class NYClamPizza extends Pizza{
    public NYClamPizza() {
        setName("纽约味蛤蜊披萨");
    }

    @Override
    public void prepare() {
        System.out.println("蛤蜊披萨,必须有蛤蜊啊");
    }
}

芝加哥披萨风味

public class ChicagoCheesePizza extends Pizza{

    public ChicagoCheesePizza() {
        setName("芝加哥味芝士披萨");
    }

    @Override
    public void prepare() {
        System.out.println("芝士披萨需要准备些芝士、芝麻、番茄、火腿,当然还有面团");
    }
}

public class ChicagoClamPizza extends Pizza{
    public ChicagoClamPizza() {
        setName("纽约味蛤蜊披萨");
    }

    @Override
    public void prepare() {
        System.out.println("蛤蜊披萨,必须有蛤蜊啊");
    }
}

现在,我们如何将不同口味的披萨和我们的披萨店配合起来,首先我们可以按照简单工厂的做法,用两个简单工厂来实现。披萨店类依赖工厂类,为了不依赖具体的工厂,我们抽象出来一个工厂接口,这就是不依赖具体,也就是依赖倒置原则的体现。
抽象工厂接口

/**
 * 披萨工厂抽象
 */
public interface PizzaFactoryV3 {
    Pizza createPizzaByType(String type);
}

芝加哥工厂(生产芝加哥风味披萨)和纽约工厂(生成纽约风味)实现,他们都是简单工厂

/**
 * 芝加哥的披萨工厂(简单工厂)
 */
public class ChicagoPizzaFactoryV3 implements PizzaFactoryV3{

    @Override
    public Pizza createPizzaByType(String type){
        Pizza pizza;

        if("cheese".equals(type)){
            pizza = new ChicagoCheesePizza();
        }else if("clam".equals(type)){
            pizza = new ChicagoClamPizza();
        }else{
            pizza = new ChicagoCheesePizza();//不知道类型时,默认芝士披萨
        }

        return pizza;
    }
}

/**
 * 纽约的披萨工厂(简单工厂)
 */
public class NYPizzaFactoryV3 implements PizzaFactoryV3 {

    @Override
    public Pizza createPizzaByType(String type){
        Pizza pizza;

        if("cheese".equals(type)){
            pizza = new NYCheesePizza();
        }else if("clam".equals(type)){
            pizza = new NYClamPizza();
        }else{
            pizza = new NYCheesePizza();
        }

        return pizza;
    }
}

披萨店实现

/**
 * V3版披萨店
 */
public class PizzaStoreV3 {

    PizzaFactoryV3 pizzaFactory;

    public PizzaStoreV3(PizzaFactoryV3 pizzaFactory) {
        this.pizzaFactory = pizzaFactory;
    }

    /**
     * 下单
     * @param type 披萨类型
     * @return
     */
    public Pizza orderPizza(String type){
        Pizza pizza = pizzaFactory.createPizzaByType(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;

    }
}

可以看到披萨点和V2中的差不多,现在只是依赖工厂接口而已。
看看怎么使用

/**
 * V3版测试
 */
public class PizzaV3Main {
    public static void main(String[] args) {
        //纽约的店
        PizzaStoreV3 nyPizzaStore = new PizzaStoreV3(new NYPizzaFactoryV3());
        //下单
        nyPizzaStore.orderPizza("clam");
        nyPizzaStore.orderPizza("cheese");

        //芝加哥的店
        PizzaStoreV3 chicagoPizzaStore = new PizzaStoreV3(new ChicagoPizzaFactoryV3());
        chicagoPizzaStore.orderPizza("clam");
        chicagoPizzaStore.orderPizza("cheese");
    }
}

done,完美,如果要扩展新的披萨店,只需要实现一个新的工厂给到PizzaStoreV3就可以了。

同样的,我们可以换个思路,我们仔细观察V3版本中,我们是通过扩展工厂的方式来扩展,另一种方式是,我们可以抽象一个PizzaStore的基类,通过扩展其子类来达到开分店的目的,比如NYPizzaStore, ChicagoPizzaStore,然后将创建披萨的代码移到子类中。

V4版本

实现一波
首先把产品(也就是披萨)搞定,这里我们可以直接复用V3版本的披萨。

然后是抽象的披萨店 PizzaStoreV4

/**
 * V4版披萨店
 */
public abstract class PizzaStoreV4 {
    
    /**
     * 下单
     * @param type 披萨类型
     * @return
     */
    public Pizza orderPizza(String type){
        Pizza pizza = createPizzaByType(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;

    }
    
    protected abstract Pizza createPizzaByType(String type);
}

披萨分店

/**
 * 纽约分店
 */
public class NYPizzaStoreV4 extends PizzaStoreV4 {
    @Override
    protected Pizza createPizzaByType(String type) {
        Pizza pizza;

        if("cheese".equals(type)){
            pizza = new NYCheesePizzaV4();
        }else if("clam".equals(type)){
            pizza = new NYClamPizzaV4();
        }else{
            pizza = new NYCheesePizzaV4();
        }
        return pizza;
    }
}

/**
 * 芝加哥分店
 */
public class ChicagoPizzaStoreV4 extends PizzaStoreV4 {
    @Override
    protected Pizza createPizzaByType(String type) {
        Pizza pizza;

        if("cheese".equals(type)){
            pizza = new ChicagoCheesePizzaV4();
        }else if("clam".equals(type)){
            pizza = new ChicagoClamPizzaV4();
        }else{
            pizza = new ChicagoCheesePizzaV4();
        }
        return pizza;
    }
}

最后我们来订购披萨吧

/**
 * V4版测试
 */
public class PizzaV4Main {
    public static void main(String[] args) {
        //纽约的店
        PizzaStoreV4 nyPizzaStore = new NYPizzaStoreV4();
        //下单
        nyPizzaStore.orderPizza("clam");
        nyPizzaStore.orderPizza("cheese");

        //芝加哥的店
        PizzaStoreV4 chicagoPizzaStore = new ChicagoPizzaStoreV4();
        chicagoPizzaStore.orderPizza("clam");
        chicagoPizzaStore.orderPizza("cheese");
    }
}

这种思路将创建披萨的任务放到子类中(披萨分店)去完成,在抽象的父类中(披萨店类)直接使用子类创建好的产品,而不管具体的创建细节。

诶,现在再要开分店,就可以扩展披萨店这个的子类来完成,也是可以满足开闭原则的。

V4的这种玩法(招式),是一种新的模式,叫工厂方法。简单的说,就是在父类中定义一个工厂方法(创建产品的方法,通常是抽象的),然后获得创建的产品直接使用。不同的子类可以实现(或者重写)这个方法,来生产不同种类的产品。

事实上也可以把V3版本的工厂抽象和工厂实现也可以叫工厂方法,只不过是工厂方法的一种特殊表现形式。

V4版本的UML图
工厂模式【简单工厂、工厂方法、抽象工厂】_第1张图片

工厂方法定义

工厂方法的正式定义,工厂方法定义了一个创建的对象的接口,但有子类决定要创建哪个对象。工厂方法将类的实例化推迟到子类。

按照惯例,用代码来实现一波定义。
首先,需要有待创建的产品吧,先把产品写好。

/**
 * 要被创建的产品抽象
 */
public interface Product {
    void showMe();
}

/**
 * 具体的产品A
 */
public class ConcreteProductA implements Product {
    @Override
    public void showMe() {
        System.out.println("具体产品A");
    }
}

/**
 * 具体的产品B
 */
public class ConcreteProductB implements Product {
    @Override
    public void showMe() {
        System.out.println("具体产品B");
    }
}

然后,我们定义一个创建者父类,它会定义抽象的创建产品方法(这个方法就是工厂方法啦)。

/**
 * 抽象的创建者
 */
public abstract class Creator {

    /**
     * 这个方法就是工厂方法
     * @return
     */
    protected  abstract Product createProduct();

    /**
     * 其他方法可以使用工厂方法获取产品对象,而不用关心产品对象是怎么创建出来的。
     */
    public void showProduct(){
        Product product = createProduct();
        product.showMe();
    }
}

然后,我们创建两个不同的子类,分别来决定要创建的产品是哪一个。


/**
 * 具体创建者A
 */
public class ConcreteCreatorA extends Creator{
    @Override
    protected Product createProduct() {
        return new ConcreteProductA();
    }
}

/**
 * 具体创建者B
 */
public class ConcreteCreatorB extends Creator{
    @Override
    protected Product createProduct() {
        return new ConcreteProductB();
    }
}

最后,测试看看效果。

/**
 * 工厂方法模式测试类
 */
public class FactoryMethodMain {
    public static void main(String[] args) {
        Creator creatorA = new ConcreteCreatorA();
        creatorA.showProduct();

        Creator creatorB = new ConcreteCreatorB();
        creatorB.showProduct();
    }
}

现在,我们要扩展新的产品时,只需要扩展产品子类,以及创建者子类即可,不修改原来的代码,符合开闭原则。

观察下工厂模式的UML图
工厂模式【简单工厂、工厂方法、抽象工厂】_第2张图片

还没完,我们继续往下看看抽象工厂模式。有点长,但原理简单。

抽象工厂模式

披萨店成功的关键在于新鲜、高质量的原料,如果加盟分店使用低价原料来增加利润,会毁了披萨店的品牌。
因此我们需要保持所有披萨店的原料一致。

披萨中加入原料

上面的代码中,我们的披萨非常简单,为了后面更清晰的表达,先将披萨丰满起来,加入原料。

/**
 * 披萨父类
 */
public abstract class PizzaV5 {

    protected String name;

    protected Dough dough;

    protected Cheese cheese;

    protected Clam  clam;

    //具体的子类披萨知道如何准备自己
    public abstract void prepare();

    public void bake(){
        System.out.println(getName() + " 烘烤...");
    }

    public void cut(){
        System.out.println(getName() + " 切片...");
    }

    public void box(){
        System.out.println(getName() + " 装盒...");
    }

    /**
     * 显示披萨原料
     */
    public void showMaterial(){
        System.out.println("显示披萨原料:");
        dough.display();
        clam.display();
        cheese.display();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

其中3种原料,简单的由接口和两组实现组成

/**
 * 面团
 */
public interface Dough {
    void display();
}
/**
 * 蛤蜊
 */
public interface Clam {
    void display();
}
/**
 * 奶酪
 */
public interface Cheese {
    void display();
}

/**
 * 纽约版本的面团
 */
public class NYDough implements Dough {
    @Override
    public void display() {
        System.out.println("纽约版本的面团");
    }
}

/**
 * 纽约版本的蛤蜊(新鲜的蛤蜊)
 */
public class NYClam implements Clam {
    @Override
    public void display() {
        System.out.println("纽约版本的蛤蜊");
    }
}

/**
 * 纽约版本奶酪
 */
public class NYCheese implements Cheese {
    @Override
    public void display() {
        System.out.println("纽约版本奶酪");
    }
}

/**
 * 芝加哥版本的面团
 */
public class ChicagoDough implements Dough {
    @Override
    public void display() {
        System.out.println("芝加哥版本的面团");
    }
}

/**
 * 芝加哥版本的蛤蜊
 */
public class ChicagoClam implements Clam {
    @Override
    public void display() {
        System.out.println("芝加哥版本的蛤蜊");
    }
}

/**
 * 芝加哥版本奶酪
 */
public class ChicagoCheese implements Cheese {
    @Override
    public void display() {
        System.out.println("芝加哥版本奶酪");
    }
}

原料有了之后,披萨店的不同分店使用一组不同的原料版本,纽约的分店使用纽约版本的一组原料、芝加哥分店使用芝加哥分店的一组原料。

V5版本

披萨店的父类和纽约分店、芝加哥分店和V4版本一样,使用的工厂方法模式来创建具体的披萨。

/**
 * V5版披萨店
 */
public abstract class PizzaStoreV5 {
    
    /**
     * 下单
     * @param type 披萨类型
     * @return
     */
    public PizzaV5 orderPizza(String type){
        PizzaV5 pizza = createPizzaByType(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        pizza.showMaterial();

        return pizza;

    }

    protected abstract PizzaV5 createPizzaByType(String type);
}

具体分店代码

/**
 * 纽约分店
 */
public class NYPizzaStoreV5 extends PizzaStoreV5 {
    @Override
    protected PizzaV5 createPizzaByType(String type) {
        PizzaV5 pizza;
        MaterialFactory materialFactory = new NYMaterialFactory();

        if("cheese".equals(type)){
            pizza = new NYCheesePizzaV5(materialFactory);
        }else if("clam".equals(type)){
            pizza = new NYClamPizzaV5(materialFactory);
        }else{
            pizza = new NYCheesePizzaV5(materialFactory);
        }
        return pizza;
    }
}

/**
 * 芝加哥分店
 */
public class ChicagoPizzaStoreV5 extends PizzaStoreV5 {
    @Override
    protected PizzaV5 createPizzaByType(String type) {
        PizzaV5 pizza;

        MaterialFactory materialFactory = new ChicagoMaterialFactory();

        if("cheese".equals(type)){
            pizza = new ChicagoCheesePizzaV5(materialFactory);
        }else if("clam".equals(type)){
            pizza = new ChicagoClamPizzaV5(materialFactory);
        }else{
            pizza = new ChicagoCheesePizzaV5(materialFactory);
        }
        return pizza;
    }
}

在具体创建披萨的代码中,传入了一个新的工厂类MaterialFactory,这个和我们之前使用的工厂有些区别。

在披萨父类中,我们发现要创建一个披萨出来,需要准备3种原料,面团、奶酪、蛤蜊,如果在具体的披萨中使用new来处理,这又回到了原始的耦合上去了,即具体的披萨耦合了具体的原料,如果某一种披萨要修改配方时,这种披萨的实现就会被修改,不符合开闭原则。
为了解决这个问题,我们继续引入新的工厂(专门用来创建对象的代码)来创建原料,因为每个披萨的创建,必然会创建依赖的3中原料,这3中原料的关系我们称之为产品家族,也就是相关联的一组产品。同工厂方法一样,首先定义创建原料的抽象方法(这儿使用接口来承载)

/**
 * 原料工厂接口
 */
public interface MaterialFactory {

    Dough createDough();
    Cheese createCheese();
    Clam  createClam();

}

接下来,纽约的分店使用一组纽约版本的原料,那么实现一个纽约版本的原料工厂

/**
 * 纽约的原料工厂
 */
public class NYMaterialFactory implements MaterialFactory {
    @Override
    public Dough createDough() {
        return new NYDough();
    }

    @Override
    public Cheese createCheese() {
        return new NYCheese();
    }

    @Override
    public Clam createClam() {
        return new NYClam();
    }
}

同样, 芝加哥的分店使用一组芝加哥版本的原料,那么实现一个芝加哥版本的原料工厂

/**
 * 芝加哥的原料工厂
 */
public class ChicagoMaterialFactory implements MaterialFactory {
    @Override
    public Dough createDough() {
        return new ChicagoDough();
    }

    @Override
    public Cheese createCheese() {
        return new ChicagoCheese();
    }

    @Override
    public Clam createClam() {
        return new ChicagoClam();
    }
}

好了,我们在实现具体的披萨时,就可以使用不同的原料工厂来创建原料,具体披萨与工厂接口交互,不与具体的原料类交互,从而解耦。
看下4种披萨的实现

/**
 * 纽约味蛤蜊披萨
 */
public class NYClamPizzaV5 extends PizzaV5{

    MaterialFactory materialFactory;

    public NYClamPizzaV5(MaterialFactory materialFactory) {
        setName("纽约味蛤蜊披萨");
        this.materialFactory = materialFactory;
    }

    @Override
    public void prepare() {
        System.out.println("纽约味蛤蜊披萨,必须有蛤蜊啊");
        dough = materialFactory.createDough();
        cheese = materialFactory.createCheese();
        clam = materialFactory.createClam();
    }
}

/**
 * 纽约味芝士披萨
 */
public class NYCheesePizzaV5 extends PizzaV5{

    MaterialFactory materialFactory;

    public NYCheesePizzaV5(MaterialFactory materialFactory) {
        setName("纽约味芝士披萨");
        this.materialFactory = materialFactory;
    }

    @Override
    public void prepare() {
        System.out.println("芝士披萨需要准备些芝士、芝麻、番茄、火腿,当然还有面团");
        dough = materialFactory.createDough();
        cheese = materialFactory.createCheese();
        clam = materialFactory.createClam();
    }
}

/**
 * 芝加哥味蛤蜊披萨
 */
public class ChicagoClamPizzaV5 extends PizzaV5{

    MaterialFactory materialFactory;

    public ChicagoClamPizzaV5(MaterialFactory materialFactory) {
        setName("芝加哥味蛤蜊披萨");
        this.materialFactory = materialFactory;
    }

    @Override
    public void prepare() {
        System.out.println("芝加哥味蛤蜊披萨,必须有蛤蜊啊");
        dough = materialFactory.createDough();
        cheese = materialFactory.createCheese();
        clam = materialFactory.createClam();
    }
}

/**
 * 芝加哥味芝士披萨
 */
public class ChicagoCheesePizzaV5 extends PizzaV5{

    MaterialFactory materialFactory;

    public ChicagoCheesePizzaV5(MaterialFactory materialFactory) {
        setName("芝加哥味芝士披萨");
        this.materialFactory = materialFactory;
    }

    @Override
    public void prepare() {
        System.out.println("芝士披萨需要准备些芝士、芝麻、番茄、火腿,当然还有面团");
        dough = materialFactory.createDough();
        cheese = materialFactory.createCheese();
        clam = materialFactory.createClam();
    }
}

这种通过一个抽象的父类(接口和抽象类),定义产品族(相关联的一组产品)的创建接口,并提供不同版本的产品族创建实现,客户直接使用抽象的接口来获取一组创建好的对象,而不需要和具体的产品类耦合。这就是抽象工厂模式。

观察下V5版本的UML关系图
工厂模式【简单工厂、工厂方法、抽象工厂】_第3张图片
工厂方法解决了创建单个产品的问题(一个抽象方法来创建产品),抽象工厂解决了创建产品族的问题,即多个抽象的方法来创建相关的不同产品。

抽象工厂定义

还是通过代码来深入理解下抽象工厂。
首先,定义下产品族

/**
 * 产品家族成员A
 */
public interface ProductMemberA {
    void showA();
}

/**
 * 产品A的具体实现A1
 */
public class ConcreteProductA1 implements ProductMemberA {
    @Override
    public void showA() {
        System.out.println("我是A1");
    }
}

/**
 * 产品A的具体实现A2
 */
public class ConcreteProductA2 implements ProductMemberA {
    @Override
    public void showA() {
        System.out.println("我是A2");
    }
}

/**
 * 产品家族成员B
 */
public interface ProductMemberB {
    void showB();
}

/**
 * 产品B的具体实现B1
 */
public class ConcreteProductB1 implements ProductMemberB {
    @Override
    public void showB() {
        System.out.println("我是B1");
    }
}

/**
 * 产品B的具体实现B2
 */
public class ConcreteProductB2 implements ProductMemberB {
    @Override
    public void showB() {
        System.out.println("我是B2");
    }
}

接下来,定义工厂接口,包括创建产品族的一组方法。

/**
 * 产品家族工厂
 */
public interface ProductMemberFactory {
    ProductMemberA createProductA();
    ProductMemberB createProductB();
}

然后,提供不同版本的工厂实现

/**
 * 具体工厂(系列1)
 */
public class ConcreteProductSeries1Factory implements ProductMemberFactory{
    @Override
    public ProductMemberA createProductA() {
        return new ConcreteProductA1();
    }

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

/**
 * 具体工厂(系列1)
 */
public class ConcreteProductSeries2Factory implements ProductMemberFactory{
    @Override
    public ProductMemberA createProductA() {
        return new ConcreteProductA2();
    }

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

最后,RUN起来看看

/**
 * 测试
 */
public class AbstractFactoryMain {

    public static void main(String[] args) {
        //系列1产品
        ProductMemberFactory factory = new ConcreteProductSeries1Factory();
        factory.createProductA().showA();
        factory.createProductB().showB();

        System.out.println();
        //系列2产品
        factory = new ConcreteProductSeries2Factory();
        factory.createProductA().showA();
        factory.createProductB().showB();
    }
}

输出结果

我是A1
我是B1

我是A2
我是B2

同样,看下UML关系图。
工厂模式【简单工厂、工厂方法、抽象工厂】_第4张图片

最后,请大家思考下,抽象工厂在什么情境下满足开闭原则,在什么情景下不满足。

另外扩展一下,在V5版本的代码中,具体披萨代码中有大量的重复,你有什么方法来消除重复吗,会不会用到一种新的模式?

源码

https://gitee.com/cq-laozhou/design-pattern

你可能感兴趣的:(设计模式)