首页 > 编程学习 > 作为比萨店老板,如何设计一个松耦合的比萨菜单-工厂模式应用

当看到“new”,就会想到“具体”

Duck duck = new MallardDuck();
  • 使用接口让代码具有弹性
  • 使用new还是得建立具体类的实例

当有一群相关的具体类时,通常会写出这样的代码:

Duck duck;
if(picnic){
    duck = new MallardDuck();
}else if(hunting){
    duck = new DecoyDuck();
}else if(inBathTub){
    duck = new RubberDuck();
}

有一大堆不同的鸭子类,但是必须等到运行时,才知道该实例化是哪一个。

这样的代码,一旦有变化或扩展,就必须重新打开这段代码进行检查和修改。通常这样修改过的代码将造成部分系统更难维护和更新,而且也更容易犯错。

“new”有什么不对劲?

在技术上,new没有错,毕竟这是java的基础部分。真正的犯人是我们的老朋友“改变”,以及它是如何影响new的使用的。

上述代码一旦加入新的具体类,就必须改变代码。也就是说,代码并非是“对扩展开放,对修改关闭”。

识别变化的部分

假设你有一个比萨店,身为对象村内最先进的比萨店主人,代码可能这么写:

Pizza orderPizza(){
    Pizza pizza = new Pizza();
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

但是需要更多的比萨类型

Pizza orderPizza(String type){
    Pizza pizza;
    
    if("cheese".equals(type)){
        pizza = new CheesePizza();
    }else if("greek".equals(type)){
        pizza = new GreekPizza;
    }
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

但是压力来自于增加更多的比萨类型:
if else相关实例化的代码是变化的部分。随着时间过去,比萨菜单改变,这里就必须一改再改。此代码没有对修改关闭

很明显,如果实例化“某些”具体类,将使orderPizza()出问题,而且也无法让orderPizza()对修改关闭;

现在我们已经知道哪些会改变,哪些不会改变,该是使用封装的时候了。

封装创建对象的代码

现在最好将创建对象移到orderPizza()之外,移到另一个对象中,由这个新对象专职创建比萨。

我们称这个新对象(SimplePizzaFactory)为“工厂”。

一旦有了SimplePizzaFactory,orderPizza()就变成此对象的客户。当需要比萨时,就叫比萨工厂做一个。

建立一个简单比萨工厂

public class SimplePizzaFactory(){
    Pizza pizza;
    
    if("cheese".equals(type)){
        pizza = new CheesePizza();
    }else if("greek".equals(type)){
        pizza = new GreekPizza;
    }
    return pizza;
}

这个代码还是根据类型创建,代码没有实质性变动。

重做PizzaStore类:

pulic class PizzaStore{
    //添加工厂的引用
    SimplePizzaFactory factory;
    
    public PizzaStore(SimplePizzaFactory factory){
        this.factory = factory;
    }
    //披萨来自于工厂
    Pizza  pizza = factory.createPizza(type);
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}
}

简单工厂定义:
image

加盟比萨店

比萨店经营有成,击败了竞争者,现在需要有加盟店。身为加盟公司经营者,你希望确保加盟店营运的质量,所以希望这些店都使用你那些经过时间考验的代码。

但是区域的差异呢?每家加盟店都可能想要提供不同风味的比萨(比如纽约、芝加哥),这受到了开店地点及该地区比萨美食家口味的影响。

如果利用SimplePizzaFactory,写出三种不同的工厂,分别是NYPizzaFactory、ChicagoPizzaFactory那么各地加盟店都有适合的工厂使用,代码如下:

//纽约风味
NYPizzaFactory nyFactory = new NYPizzaFactory();
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.orderPizza("Veggie");

//芝加哥风味
ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore nyStore = new PizzaStore(chicagoFactory);
nyStore.orderPizza("Veggie");

推广SimpleFactory时,你发现加盟店的确是采用你的工厂创建比萨,但是其他部分,却开始采用他们自创的流程:做法差异、不要切片,使用其他厂商的盒子。

制作比萨的代码绑在PizzaStore里,但这么做却没有弹性。有个做法(抽象)可以让比萨制作活动局限于PizzaStore类,而同时又能让这些加盟店依然可以自由地制作该区域的风味。

具体做法是:把createPizza()方法放回到PizzaStore中,不过要把它设置成“抽象方法”,然后为每个区域风味创建一个PizzaStore的子类。

public abstract class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = createPizza(type);
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
    
    //现在把工厂对象移动到这个方法中
    abstract Pizza createPizza(String type);
}

允许子类做决定:
image

声明一个工厂方法

工厂方法用来处理对象的创建,并将这样的行为封装在子类中。这样,客户程序中关于超类的代码就和子类对象创建代码解耦了。

abstract Porduct factoryMethod(String type)
  1. 工厂方法是抽象的,所以依赖子类来处理对象的创建。
  2. 工厂方法必须返回一个产品。
  3. 工厂方法将客户(也就是超类中的代码,例如orderPizza())和实际创建具体产品的代码分隔开来。
  4. 工厂方法可能需要参数(也可能不需要)来指定所需要的产品。
Copyright © 2010-2022 mfbz.cn 版权所有 |关于我们| 联系方式|豫ICP备15888888号