工厂模式

133 阅读6分钟

简单工厂

处理创建对象的细节,将Pizza.typeorderPizza()中分离出来。

实现一个简单的披萨工厂:

public class SimplePizzaFactory {
    public Pizza createPizza(String type) {
        Pizza pizza = null;

        if(type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        } else if (type.equals("clam")) {
            pizza = new ClamPizza();
        } else if (type.equals("veggie")) {
            pizza = new VeggiePizza();
        }
        return pizza;
    }
}

好处

SimplePizzaFactory可以有很多客户,虽然目前只有orderPizza()方法是它的客户,然而,可能还有PizzaShopMenu类会利用这个工厂来取得披萨的价钱和描述。可能还有一个HomeDelivery(宅急送)类,会以与PizzaShop类不同的方式来处理披萨等等。
所以把创建披萨代码包装进一个类,当以后实现改变时,只需修改这个类就好了。

静态工厂

利用静态方法定义一个简单的工厂,常被称为静态工厂。使用静态方法可以不使用创建对象的方法来实例化对象。但也有相应的缺点,不能通过继承来改变创建方法行为

相应的PizzaStore类:

public class PizzaStore {
    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory) {
        this.factory = factory;
    }

    public Pizza orderPizza(String type) {
        Pizza pizza;

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

    //其他方法
}

简单工厂的小节

  • 工厂的客户PizzaStore现在通过SimplePizzaFactory取得披萨的实例
  • SimplePizzaFactory创建披萨,应用中唯一用到具体披萨类的地方,通常创建方法声明为静态的
  • Pizza定义为抽象类,具有一些有用的实现,这些实现可以被覆盖
  • CheesePizzaVegglePizzaClamPizzaPepperoniPizza为具体披萨,实现了Pizza接口
    • 注:实现了Pizza接口,泛指实现了一个超类型可以是接口也可以是类

工厂方法模式

  • 进军其他地区,多个地域风味需要实现多个工厂?
  • 想要更多质量控制:火候、切片、盒子的厂商等,如何解决?
NYPizzaFactory nyFactory = new NYPizzaFactory();
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.orderPizza("Veggie");

此时制造披萨的方法要声明为抽象方法,这样PizzaStore作为超类(定义orderPizza()createPizza(抽象的)),NYPizzaStoreChicagoPizzaStoreCaliforniaPizzaStore都继承这个PizzaStore,每个子类(只需要实现createPizza方法)各自决定如何制造披萨。

声明一个工厂方法

abstract Product factoryMethod(String type)

  • 工厂方法是抽象的,所以依赖子类来处理对象的创建
  • 工厂方法必须返回一个产品。超类定义方法,通常使用到工厂方法的返回值
  • 工厂方法将客户(也就是超类中的代码,如orderPizza())和实际创建具体产品的代码分开
  • 工厂方法可能需要参数(也可能不需要)来指定所要的产品

根据订单生成Pizza

PizzaStore nyPizzaStore = new NYPizzaStore();
nyPizzaStore.orderPizza("cheese");
Pizza pizza = createPizza("cheese");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

披萨类

  • 抽象父类
public abstract class Pizza{
    String name;
    String dough;
    String sauce;
    ArrayList topping = new ArrayList();

    void prepare() {
        System.out.println("Preparing " + name);
        System.out.println("Tossing dough...");
        System.out.println("Adding sauce...");
        System.out.println("Adding topping...");
        for(int i = 0; i < toppings.size(); i++) {
            System.out.println("  " + toppings.get(i));
        }
    }

    void bake() {
        System.out.println("Bake for 25min at 350");
    }

    void cut() {
        System.out.println("Cutting the pizza into diagonal slices");
    }

    void box() {
        System.out.println("Place pizza in official PizzaStore bos");
    }
    public String getName() {
        return name;
    }
}
  • 具体披萨 NYStyleCheesePizza
public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
        name = "NY style Sauce and Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";

        toppings.add("Grated Reggiano Cheese");
    }
}

ChicagoStyleCheesePizza

public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza() {
        //细节略
    }

    //覆盖cut()方法
    void cut() {
        System.out.println("Cutting the pizza into square slice");
    }
}

工厂方法模式小节

  • 创建者(Creator)类
    • PizzaStore
      • NYStylePizzaStore
      • ChicagoStylePizzaStore
  • 产品类
    • Pizza
      • NYStyleCheesePizza
      • NYStyleCheesePizza

以上两大类是两个平行的类层级,都有抽象类,抽象类下都有许多具体的子类

  • 工厂方法模式:定义了一个创建对象的接口,但由于子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

工厂方法模式能够封装具体类型的实例化。任何其他方法都可以使用这个工厂方法制作产品,但只有子类真正实现这个工厂方法并创建产品。

常听说“工厂方法让子类决定要实例化的类是哪一个”,值得注意的是,这里并不是指模式运行子类本身在运行时决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个。选择哪个子类自然就决定实际创建哪个产品。

优点

解耦,更改产品细节不影响“客户”

问题

  • 工厂方法和创建者是否总是抽象的? 不,可以定义一个默认的工厂方法来产生某些具体的产品,这样即使创建者没有任何子类也可以创建产品了。
  • 字符串传入参数化的类型有拼错的危险 编译时期将参数错误挑出,可以创建代表参数类型的对象和使用静态常量或者enum
  • 简单工厂和工厂方法的差异,他们看起来很相似,区别在工厂方法返回子类披萨吗? 子类的确看起来很像简单工厂,但简单工厂把全部事情在一个地方处理完了,而工厂方法却是创建一个框架,让子类决定如何实现。简单工厂不具备工厂方法的弹性。

设计原则

依赖倒置:要依赖抽象,不要依赖具体类。即底层组件依赖高层的抽象。

几条指导方针:

  • 变量不可以持有具体类的引用(避免new
  • 不要让类派生自具体类
  • 不要覆盖基类中已实现的方法

抽象工厂方法

建造原料工厂

每个原料都是一个类并且都有对应的方法创建。

public interface PizzaIngredientFactory {
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Clams createClam();
}
  • 实现纽约原料工厂
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    
    public Dough createDough() {
        return new ThinCrustDough();
    }

    public Sauce createSauce() {
        return new MarinaraSauce();
    }

    public Cheese createCheese() {
        return new ReggianoCheese();
    }

    public Veggies[] createVeggies() {
        Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom(), new RedPepper()};
        return veggies;
    }

    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    public Clams createClam() {
        return new FreshClams();
    }
}

重做披萨

披萨抽象类

public abstract class Pizza {
    String name;
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;

    abstract void prepare();

    void bake() {
        System.out.println("Bake for 25min at 350 degree");
    }

    void cut() {
        System.out.println("Cutting the pizza into diagonal slices");
    }

    void box() {
        System.out.println("Place pizza in official PizzaStore box");
    }

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

    String getName() {
        this.name = name;
    }

    public String toString {
        //打印披萨代码
    }
}

具体披萨实现

public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;

    public CheesePizza(PizzaIngredientFactory ingredientFactory) {
        this.ingredientFactory = ingredientFactory;
    }

    void prepare() {
        System.out.println("Preparing" + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

sauce = ingredientFactory.createSauce();

  • Pizza实例变量设置为此披萨所使用的各种酱料
  • 原料工厂
  • 返回所使用的酱料,取得特殊酱料

披萨店

public class NYPizzaStore extends PizzaStore {
    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();

        if(item.equals("cheese")) {
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Sytle Clam Pizza");
        } else if (item.equals("clam")) {
            pizza = new ClamPizza(ingredientFactory);
        }
        //略
    }
    return pizza;
}

总结

  • 引入新类型的工厂,所谓的抽象工厂,创建了原料家族
  • 通过抽象工厂所提供的接口可以创建产品的家族
  • 从实际工厂解耦,以便在不同上下文中实现各种各样的工厂,制作各种不同产品

抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

工厂方法 vs 抽象工厂

  • 工厂方法使用继承,抽象工厂使用对象的组合