工厂方法模式

武培轩 2020年02月06日 122次浏览

简单工厂模式中,我们发现存在很多问题:

  • 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
  • 要新增产品类的时候,就要修改工厂类的代码,违反了开放封闭原则(对扩展的开放,对修改的关闭)。
  • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

为了解决上述的问题,我们学习一种新的设计模式:工厂方法模式。

模式定义

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

设计原则

依赖倒置原则:要依赖抽象,不要依赖具体类。

听起来像是针对接口编程,不针对实现编程,但是这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,两者都应该依赖于抽象。例如,下图中 Pizza 是抽象类,PizzaStore 和 Pizza 子类都依赖于 Pizza 这个抽象类。

工厂方法模式对象依赖图

模式类图

工厂方法模式类图

工厂方法模式实例

问题描述:

每个地区的 PizzaStore 卖的 Pizza 虽然种类相同,但是都有自己的风味。一个客户点了纽约的 cheese 种类的 Pizza 和在芝加哥点的相同种类的 Pizza 是不同的。要求设计出满足条件的 PizzaStore。

问题的解决方案类图

PizzaStore 有 orderPizza() 方法,顾客可以用它来下单。下单之后需要先使用 createPizza() 来制作 Pizza,这里的 createPizza() 就是 factoryMethod(),不同的 PizzaStore 子类实现了不同的 createPizza()。
工厂方法模式类图

首先定义产品接口-披萨

package com.wpx.factorymethod;

/**
 * 定义产品接口-披萨
 */
public interface Pizza {
    public void make();
}

再定义具体的产品类-纽约风味奶酪披萨、纽约风味蔬菜披萨、芝加哥风味奶酪披萨、芝加哥风味蔬菜披萨

package com.wpx.factorymethod;

/**
 * 具体的产品类,实现产品接口-纽约风味奶酪披萨
 */
public class NYStyleCheesePizza implements Pizza{
    @Override
    public void make() {
        System.out.println("制作纽约风味奶酪披萨");
    }
}

package com.wpx.factorymethod;

/**
 * 具体的产品类,实现产品接口-纽约风味蔬菜披萨
 */
public class NYStyleVeggiePizza implements Pizza {
    @Override
    public void make() {
        System.out.println("制作纽约风味蔬菜披萨");
    }
}

package com.wpx.factorymethod;

/**
 * 具体的产品类,实现产品接口-芝加哥风味奶酪披萨
 */
public class ChicagoStyleCheesePizza implements Pizza{
    @Override
    public void make() {
        System.out.println("制作芝加哥风味奶酪披萨");
    }
}
package com.wpx.factorymethod;

/**
 * 具体的产品类,实现产品接口-芝加哥风味蔬菜披萨
 */
public class ChicagoStyleVeggiePizza implements Pizza{
    @Override
    public void make() {
        System.out.println("制作芝加哥风味蔬菜披萨");
    }
}

定义工厂接口

package com.wpx.factorymethod;

/**
 * 定义工厂接口
 */
public interface PizzaStore {
    public Pizza orderPizza(String item);
}

接着定义两个具体的工厂类,实现工厂接口

package com.wpx.factorymethod;

/**
 * 具体的工厂类,实现工厂接口-纽约工厂
 */
public class NYPizzaStore implements PizzaStore {
    @Override
    public Pizza orderPizza(String item) {
        Pizza pizza = null;
        if (item.equals("乳酪比萨")) {
            pizza = new NYStyleCheesePizza();
        } else if (item.equals("蔬菜披萨")) {
            pizza = new NYStyleVeggiePizza();
        } else {
            throw new UnsupportedOperationException();
        }
        pizza.make();
        return pizza;
    }
}
package com.wpx.factorymethod;

/**
 * 具体的工厂类,实现工厂接口-芝加哥工厂
 */
public class ChicagoPizzaStore implements PizzaStore {
    @Override
    public Pizza orderPizza(String item) {
        Pizza pizza = null;
        if (item.equals("乳酪比萨")) {
            pizza = new ChicagoStyleCheesePizza();
        } else if (item.equals("蔬菜披萨")) {
            pizza = new ChicagoStyleVeggiePizza();
        } else {
            throw new UnsupportedOperationException();
        }
        pizza.make();
        return pizza;
    }
}

最后对工厂方法模式进行测试,先从纽约工厂那里来一份乳酪披萨,再从芝加哥工厂订一份乳酪披萨。

package com.wpx.factorymethod;

/**
 * 测试工厂方法模式
 */
public class PizzaDemo {
    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore();
        nyStore.orderPizza("乳酪比萨");
        PizzaStore chicagoStore = new ChicagoPizzaStore();
        chicagoStore.orderPizza("乳酪比萨");
    }
}

运行结果

制作纽约风味奶酪披萨
制作芝加哥风味奶酪披萨

Process finished with exit code 0

JDK中的工厂方法模式

Collection接口中的一段代码:

Iterator<E> iterator();  

继承Collction的List、Set等中有:

Iterator<E> iterator();  

接下来再看ArrayList中的代码:

    public Iterator<E> iterator() {
        return new Itr();
    }

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

Itr类实现了Interator接口

public interface Iterator<E> {

    boolean hasNext();

    E next();

    void remove();
}

通过查看JDK源码,我们就会明白这里是怎么应用的工厂方法模式了其中Iterator是抽象产品角色,Itr是Iterator下面的一个具体产品角色,List类应该是抽象工厂角色,ArrayList应该是具体工厂角色。如果我们需要在List下自定义一个集合类并给出相应的迭代方式,那么我们只需要添加一个实现List接口的集合类,然后在增加一个迭代类实现Iterator接口就可以了。并不需要去修改JDK源码的内容,满足了开-闭原则。

总结

优点:

  • 更符合开-闭原则:新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可
  • 符合单一职责原则:每个具体工厂类只负责创建对应的产品
  • 不使用静态工厂方法,可以形成基于继承的等级结构

缺点:

  • 添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销
  • 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类
  • 一个具体工厂只能创建一种具体产品