设计模式 - D4 - 工厂模式


开发过程中,实例化这个活动不应该总是公开地进行,因为这样会造成耦合问题。除了使用new操作符之外,还有更多制造对象的方法,在此将会介绍一种新的制造对象的方法-工厂模式。

使用 new 的缺陷

使用 new 时,如:Duck duck = new MallardDuck(),是在实例化一个具体类,而不是接口,这种代码绑定具体类的设计会使得代码更脆弱并缺乏弹性。
例如,有一群相关类 MallardDuck、DecoyDuck、RubberDuck 时,若要实例化具体类,需要在运行时由一些条件决定:

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

对于上述的代码,一旦条件有变化,或相关类有扩展时,就必须进行修改,也就是违反了开闭原则,很容易引入bug

工厂模式

简单工厂

假设现在有一个抽象超类Pizza,及其实现子类 CheesePizza、GreekPizza和PepperoniPizza,则在PizzaStore对象内,对Pizza下单可通过以下代码实现

public Pizza orderPizza(String type) {
        Pizza pizza = null;

        if(type.equals("cheese")) {
           pizza = new CheesePizza();
        } else if(type.equals("greek")) {
            pizza = new GreekPizza();
        } else if(type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

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

同样地,上面的条件判断是违反开闭原则的(当然,上面的代码可能会导致NullPointerException,但在此先忽略)。当引入新Pizza实现类时,需要对这段代码进行修改。因此,需要将这段“变化”的代码抽取并封装到orderPizza()之外的一个新对象,由这个新对象专职创建Pizza实现类,这个对象就称为“工厂类“。

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

这样设计的好处是 SimplePizzaFactory 可以有多个客户使用它,当需要修改时,仅修改这个类方法即可
注:可以将工厂方法 createPizza() 定义为一个静态方法,这样就无须实例化工厂类。但这种方式无法通过继承来改变工厂方法的行为
引入简单的工厂类 SimplePizzaFactory 后,PizzaStore 类改写为

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

    public Pizza orderPizza(String type) {
        Pizza pizza = null;

        pizza = factory.createPizza(type);

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

        return pizza;
    }
}

此处,SimplePizzaFactory 是一个简单工厂,但实质上并不是一个设计模式,而是一种编程习惯

工厂方法模式

定义

工厂方法模式定义一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。注:设计模式中的接口并非需要implements 的 interface,而是某个超类型(类/接口)的某个方法

工厂方法模式能够封装具体类型的实例化,在抽象的创建者 Creator 中提供一个创建对象的方法的接口(工厂方法,factoryMethod),其中任何其他实现的方法都可能使用到这个工厂方法所制造出来的产品,但只有子类 ConcreteCreator 决定并真正实现这个工厂方法创建产品。
在这里插入图片描述
优点:将产品的”实现“从”使用“中解耦,如果增加或改变产品,创建者 Creator 并不会受到影响

举例:PizzaStore

例如,当我们将 SimplePizzaFactory 类中的工厂方法 createPizza() 改为抽象方法并移到 PizzaStore 类后,PizzaStore 就成了抽象超类,他的子类,如:NYPizzaStore 和 ChicagoPizzaStore,就可以实现抽象方法 createPizza,在不修改制作流程的情况下,自定义需要制作的 Pizza 类型。

  • 创建者类
    • PizzaStore 是抽象创建者类,定义了一个让子类实现的抽象工厂方法 createPizza(),它通常还会依赖抽象产品,这些抽象产品由子类制造,创建者不需要知道在哪里制造哪种具体产品
    • NYPizzaStore 和 GreekPizzaStore 是具体创建者类,可实现抽象工厂方法,制造具体产品
  • 产品类
    • Pizza 是抽象产品类,NYCheesePizza等实现该类并被具体创建者类制造
      在这里插入图片描述
//创建者
public abstract class PizzaStore {
    // 工厂方法
    protected abstract Pizza createPizza(String type);

    public Pizza orderPizza(String type) {
        Pizza pizza;

        pizza = createPizza(type);

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

        return pizza;
    }
}
public class NYPizzaStore extends PizzaStore{
    @Override
    protected Pizza createPizza(String type) {
        if(type.equals("cheese")) {
            return new NYCheesePizza();
        } else if(type.equals("pepperoni")) {
            return new NYPepperoniPizza();
        } else {
            return null;
        }
    }
}
``````java
public class GreekPizzaStore extends PizzaStore{
    @Override
    protected Pizza createPizza(String type) {
        if(type.equals("cheese")) {
            return new GreekCheesePizza();
        } else if(type.equals("pepperoni")) {
            return new GreekPepperoniPizza();
        } else {
            return null;
        }
    }
}
``````java
// 产品类
public abstract class Pizza {
    String name;
    String dough;
    String sauce;
    List<Object> toppings = new ArrayList<>();

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

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

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

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

    public String getName() {
        return name;
    }
}
``````java
public class NYCheesePizza extends Pizza{
    public NYCheesePizza() {
        name = "NYCheesePizza";
        dough = "Thin Crust Dough";
        sauce = "Mainara Sauce";
        toppings.add("Grated Reggiano Cheese");
    }
}

public class NYPepperoniPizza extends Pizza{
    public NYPepperoniPizza() {
        name = "NYPepperoniPizza";
        dough = "Thin Crust Dough";
        sauce = "Mainara Sauce";
        toppings.add("Grated Reggiano Cheese");
    }
}

public class GreekCheesePizza extends Pizza {
    public GreekCheesePizza() {
        name = "GreekCheesePizza";
        dough = "Thick Crust Dough";
        sauce = "Berry Sauce";
        toppings.add("Grated Reggiano Cheese");
    }
}

public class GreekPepperoniPizza extends Pizza {
    public GreekPepperoniPizza() {
        name = "GreekPepperoniPizza";
        dough = "Thick Crust Dough";
        sauce = "Berry Sauce";
        toppings.add("Grated Reggiano Cheese");
    }
}

依赖倒置原则

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

当我们直接使用 new 实例化一个对象时,就是在依赖它的具体类。显然,代码里减少对具体类的依赖是有很大好处的。因为,每新增一个依赖或依赖的实现改变了,就需要修改依赖代码。对此,依赖倒置原则要求:不能让高层组件依赖低层组件,且两者都应该依赖于抽象。
例如,PizzaStore是高层组件,而 NYCheesePizza 等具体实现类属于低层组件,因为 PizzaStore 的行为是由 NYCheesePizza 这些具体实现类定义的,而 Pizza 类就是两者都依赖的抽象
在这里插入图片描述

避免在OO设计中违反依赖倒置原则的方法:

  • 变量不可以持有具体类的引用(不使用new,改用工厂)
  • 不要让类派生自具体类(派生自具体类就会依赖它,请派生自一个抽象)
  • 不要覆盖基类中以实现的方法(基类中已实现的方法应由所有子类共享)

抽象工厂模式

定义

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

抽象工厂允许客户使用抽象的接口创建一组相关的产品(如:举例中的Dough、Sauce等产品),而不需要知道(或关心)实际产出的具体产品是什么,借此客户从具体产品中被解耦。
在这里插入图片描述

  • AbstractFactory 是抽象工厂,定义了一个接口,所有的具体工厂必须实现该接口,这个接口包含一组方法生产产品
    • 举例的 PizzaIngredientFactory 就是一个抽象工厂
  • ConcreteFactory1 和 ConcreteFactory2 是具体工厂,实现了不同的产品家族(ProdcutA1、ProductB1…),客户只需要使用其中一个工厂而不需实例化任何产品对象
    • 举例的 NYPizzaIngredientFactory 就是一个具体工厂
  • Client 是客户,只需设计抽象工厂,运行时自动使用实际的工厂
    • 举例的 CheesePizza 就是抽象工厂的客户
  • AbstractProduct、Product 就是产品家族,每个具体工厂都能生产一整组的产品
    • 举例的 Dough、ThinCrustDough 等就是产品家族

举例:PizzaIngredientFactory

假设现在 NYPizzaStore 等比萨店需要不同的一组原料来生产Pizza,我们可以创建一个工厂来负责生产这些不同的原料组。开始,我们需要先为工厂定义一个接口,该接口负责创建所有的原料

public interface PizzaIngredientFactory {

    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClams();
  
}

然后,我们需要完成:

  1. 为每个比萨店建造一个工厂,即:创建一个继承自PizzaIngredientFactory的子类来实现每一个原料创建方法
  2. 实现一组原料供工厂使用,即:实现 Dough 等抽象类
  3. 将新的原料工厂整合进旧的 Pizza 和 PizzaStore 代码中
// 此处,Dough为原料抽象,ThinCrustDough等为原料的实现类,为了节省篇幅,省略实现代码
public class NYPizzaIngredientFactory implements PizzaIngredientFactory{
    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

    @Override
    public Sauce createSauce() {
        return new MarinaraSource();
    }

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

    @Override
    public Veggies[] createVeggies() {
        return new Veggies[]{new Garlic(), new Onion()};
    }

    @Override
    public Pepperoni createPepperoni() {
        return new SlicePepperoni();
    }

    @Override
    public Clams createClams() {
        return new FreshClaims();
    }
}
``````java
public abstract class Pizza {
    String name;
    Dough dough;
    Sauce sauce;
    Veggies[] veggies;
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clams;

    public abstract void prepare();

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

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

    public void box() {
        System.out.println("Place pizza in official PizzaStore box");
    }
    
}
``````java
public class CheesePizza extends Pizza{

    PizzaIngredientFactory ingredientFactory;

    // 不需要为NYPizzaStore和GreekPizzaStore设计不同的CheesePizza类
    // 因为通过不同的PizzaIngredientFactory即可提供不同风味的CheesePizza
    public CheesePizza(PizzaIngredientFactory ingredientFactory) {
        this.ingredientFactory = ingredientFactory;
    }

    @Override
    public void prepare() {
        System.out.println("Preparing " + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}
``````java
public class NYPizzaStore extends PizzaStore{
    @Override
    protected Pizza createPizza(String type) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
        if (type.equals("cheese")) {
            pizza = new CheesePizza(ingredientFactory);
        } else if(type.equals("veggie")) {
            ...
        }
        return pizza;
    }
}

工厂方法模式 vs 抽象工厂模式

抽象工厂的方法常以工厂方法的方式实现(抽象工厂这个接口内的方法都负责创建一个具体的产品,而抽象工厂的实现子列负责提供这些具体做法)
区别:

  • 工厂方法采用继承方式实现,而抽象工厂采用组合方式实现
  • 工厂方法通过客户的子类来创建对象,即由子类负责决定具体类型,客户只需知道所使用的抽象类型即可,也就是工厂方法只负责将客户从具体类型中解耦;抽象工厂提供一个创建一个产品家族的抽象类型,该类型定义了产品被产生的方法,但在使用前,必须实例化它,然后传入一些针对抽象类型所写的代码中,与工厂方法一样同样可以将客户从具体类型中解耦,但实现方式不同
  • 抽象工厂可以集合一群相关的产品
  • 抽象工厂的具体工厂常使用工厂方法来实现

使用场景:

  • 抽象工厂 - 创建产品家族和让相关产品集合起来时使用
  • 工厂方法 - 需要将客户代码从需要实例化的具体类中解耦时
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值