设计模式-工厂模式

当看到“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. 工厂方法可能需要参数(也可能不需要)来指定所需要的产品。

比萨本身

如果没有比萨可出售,我们的比萨店开得再多也不行,现在我们来实现比萨:

public abstract class Pizza {
    String name;
    String dough;
    String sauce;
    ArrayList toppings = new ArrayList();

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

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

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

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

    public String getName(){
        return name;
    }
}
public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza(){
        name = "Chicago Style Deep Dish Cheese Pizza";
        dough = "Extra Thick Crust Dough";
        sauce = "Plum Tomato Sauce";

        toppings.add("Shredded Mozzarella Cheese");
    }

    @Override
    void cut(){
        System.out.println("Cutting the pizza into square slices");
    }
}
public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza(){
        name = "NY Style Sauce and Cheese Pizza";
        dough = "Thin Crest Dough";
        sauce = "Marinara Sauce";

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

认识工厂方法模式

所有工厂模式都是用来封装对象的创建。工厂方法模式(Factory Method Pattern)通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。

创建者

image

  1. PizzaStore是抽象创建者类。它定义了一个抽象的工厂方法,让子类实现此方法制造产品。
  2. PizzaStore通常会包含依赖于抽象产品代码,而这些抽象产品由子类制造。创建者不需要真的知道在制造哪种具体产品。
  3. createPizza()方法正是工厂方法,用来制造产品。
  4. 能够产生产品的类称为具体创建者。
  5. 因为每个加盟店都有自己的PizzaStore子类,所以可以利用实现createPizza()创建自己风味的比萨。

产品类

  1. 工厂生产产品。对PizzaStore来说,产品就是Pizza。
  2. NYStyleCheesePizza等是具体的产品。

平行的类层级

将一个orderPizza()方法和一个工厂方法联合起来,就可以成为一个框架。除此之外,工厂方法将生产知识封装进各个创建者,这样的做法,也可以被视为是一个框架。

Pizza和PizzaStore两个类层级是平行的:因为它们都有抽象类,而抽象类都有许多具体的子类,每个子类都有自己特定的实现。

定义工厂方法

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

image

  1. 工厂方法模式能够封装具体类型的实例化。抽象的creator提供了一个创建对象的方法的接口,也称为“工厂方法”。在抽象的creator中,任何其他实现的方法,都可能使用到这个工厂方法所制造出来的产品,但只有子类真正实现这个工厂方法并创建产品
  2. 工厂方法让子类决定要实例化的类是哪一个。所谓的“决定”,并不是指模式允许子类本身在运行时做决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个。选择了使用哪个子类,自然就决定了实际创建的产品是什么。

依赖倒置原则

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

这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件,“两者”都应该依赖于抽象。

所谓“高层”组件,是由其他低层组件定义其行为的类。例如,PizzaStore是个高层组件,因为它的行为是由比萨定义的:PizzaStore创建所有不同的比萨对象,准备、烘烤、切片、装盒;而比萨本身属于低层组件。

定义抽象工厂模式

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

抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。

image

  1. 抽象工厂定义了一个接口,所有的具体工厂都必须实现此接口,这个接口包含一组方法用来生产产品。
  2. 具体工厂实现不同的产品家族。要创建一个产品,客户只要使用其中的一个工厂而完全不需实例化任何产品对象。
  3. 抽象产品,是一个产品家族,每个具体工厂都能够生产一整套的产品。
  4. 客户的代码中只需涉及抽象工厂,运行时将自动使用实际的工厂。

image

抽象工厂的每个方法实际上看起来都像是工厂方法(例如:createDough()、createSource()等)。每个方法都被声明成抽象,而子类的方法覆盖这些方法来创建某些对象,这不正是工厂方法吗?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值