设计模式 之 工厂模式

 一、通过例子了解工厂模式:

      假设有一个披萨店,下面,我们就“做披萨”这件事来进行代码的分析。(注:在这里,我们把做披萨分为四个步骤:准备Prepare烘烤Bake切片Cut装盒Box
      刚开始,我们的店里面只有CheesePizzaGreekPizzaVeggiePizza三种披萨,在这种情况下,我们的代码可能这么写:

 1 public class PizzaStore {
 2     public Pizza orderPizza(String pizzaType) {
 3         Pizza pizza = null;
 4 
 5         // 根据披萨的类型实例化不同的披萨
 6         if ("cheese".equals(pizzaType)) {
 7             pizza = new CheesePizza();
 8         } else if ("greek".equals(pizzaType)) {
 9             pizza = new GreekPizza();
10         } else if ("veggie".equals(pizzaType)) {
11             pizza = new VeggiePizza();
12         }
13 
14         pizza.prepare();
15         pizza.bake();
16         pizza.cut();
17         pizza.box();
18 
19         return pizza;
20     }
21 }
最初的代码

      现在,我们的披萨店只有“做披萨”这项工作,所以,现在的代码看似编写的非常成功,但是,这里面隐藏着一个巨大的问题——那就是扩展性太差。试想,如果将来我们的披萨店提供“披萨外卖”服务(对于不同的披萨提供不同的运输机制),那么我们的“根据披萨类型实例化不同的披萨”这段代码就必须要再写一遍。如果再来一些其他的服务(加入说20个),这个时候如果披萨菜单发生变化(新加入了2中披萨),那么我们就要把与之相关的20段代码统统改一遍,枯燥无味不说,一旦改错了什么地方,那么我们就又要花费更多的时间去修改BUG,得不偿失。

      面对这个问题,我们最好的解决方案就是使用面向对象思想中的封装。我们把根据披萨类型实例化不同的披萨这段代码从PizzaStore中抽取出来,放到另外一个类中,这个类专门负责创建披萨对象,我们把这个类成为“工厂”。“工厂”是负责处理创建对象细节的一个类,当对一类对象创建工厂之后,那么项目中的所有该类对象都要由这个工厂进行“生产”。简单的说,在项目中,我们用工厂方法来封装“NEW”这个关键字。下面,我们就来看看我们的第一个工厂中的代码:

 1 public class PizzaFactory {
 2     public Pizza createPizza(String pizzaType) {
 3         Pizza pizza = null;
 4         // 根据披萨的类型实例化不同的披萨(从PizzaStore中剪切出来的代码)
 5         if ("cheese".equals(pizzaType)) {
 6             pizza = new CheesePizza();
 7         } else if ("greek".equals(pizzaType)) {
 8             pizza = new GreekPizza();
 9         } else if ("veggie".equals(pizzaType)) {
10             pizza = new VeggiePizza();
11         }
12         return pizza;
13     }
14 }
第一个披萨工厂的代码

      从目前的情况来看,我们把这段代码独立出来纯粹是多此一举——并没有什么卵用。那么这样做到底有什么作用呢?大家不要忘了,我们的程序现在还只有一个功能(做披萨),将来,随着需求的改变,我们可能会再添加一些其他的功能,比如说,建立一个披萨菜单的功能,把所有披萨都显示到一个菜单上供顾客阅览;还可以添加一个“披萨外卖”的功能,等等等等。所以,我们把创建披萨的代码装进这样一个工厂类,当以后实现发生改变时,我们只需要修改这里的代码即可。经过我们这一番修改,PizzaStore类中的代码就可以拜托实例化(NEW关键字),而直接使用工厂来创建实例了:

 1 public class PizzaStore {
 2     private PizzaFactory factory;
 3 
 4     // 初始化披萨店的时候顺便给披萨店绑定一个披萨工厂
 5     public PizzaStore(PizzaFactory factory) {
 6         this.factory = factory;
 7     }
 8 
 9     public Pizza orderPizza(String pizzaType) {
10         Pizza pizza = null;
11 
12         // 我们把new关键字提取出来放到披萨工厂中,这里不再使用具体的实例化
13         pizza = factory.createPizza(pizzaType);
14 
15         pizza.prepare();
16         pizza.bake();
17         pizza.cut();
18         pizza.box();
19 
20         return pizza;
21     }
22 }
第一次修改后的PizzaStore

      到此,我们来捋一捋我们所做的:首先,我们有一个披萨店(PizzaStore),用它来生产种类不同的披萨;我们还有一些披萨(Pizza),它们是我们的主营产品,有很多不同的口味;我们还有一个披萨工厂(PizzaFactory),用它来统一的生产披萨。这三个类的关系是:披萨店借由披萨工厂来上产披萨,它们具体的关系请看下面的类图:设计模式 之 工厂模式_第1张图片

      至此,我们已经大致解决了最初的那个问题,我们可以轻松的应对新加入的披萨,也可以轻松的应对其他功能的接入。但是,问题来了。随着我们披萨店的经营,我们逐渐的扩大经营范围,使我们的披萨店成为了一个风靡全国的品牌,在全国各地都有分店,为了满足不同地区人的饮食习惯,我们决定让菜单“地域化”,即名字还是那些名字,但是做法要适当的改变,比如四川人爱吃辣,所以我们就在CheesePizza中适当的加入辣椒;山东人爱吃咸,所以我们在CheesePizza中是电工的加入盐,另外,在包装、切片等工序上,不同的地方也会有差异。为了兼容这些差异,我们必须要设计一套非常有弹性的代码。

      我们可以考虑把PizzaStore升级成父类,把获取Pizza对象的方法抽取出来并设置为抽象方法;让各个地方的披萨店继承这个父类,并实现抽象方法。这样一来,披萨工厂就可以移动到各个地方的披萨店类中了。也就是说,在第二次更改后,实例化披萨对象的责任被移到了一个方法中,这个方法就如同是一个“工厂”。下面展示第二次修改后的披萨店父类PizzaStore和四川披萨店子类SCPizzaStore中的代码:

 1 public abstract class PizzaStore {
 2 
 3     public Pizza orderPizza(String pizzaType) {
 4         Pizza pizza = null;
 5 
 6         // 用定义的抽象方法从子类中获取披萨对象
 7         pizza = createPizza(pizzaType);
 8 
 9         pizza.prepare();
10         pizza.bake();
11         pizza.cut();
12         pizza.box();
13 
14         return pizza;
15     }
16 
17     protected abstract Pizza createPizza(String pizzaType);
18 }
第二次修改后的PizzaStore类代码
 1 public class SCPizzaStore extends PizzaStore {
 2 
 3     protected Pizza createPizza(String pizzaType) {
 4         if ("cheese".equals(pizzaType)) {
 5             return new SCCheesePizza();
 6         } else if ("greek".equals(pizzaType)) {
 7             return new SCGreekPizza();
 8         } else if ("veggie".equals(pizzaType)) {
 9             return new SCVeggiePizza();
10         }
11         return null;
12     }
13 }
四川地区的披萨店筛选披萨的代码

      PizzaStore类中的orderPizza()方法对Pizza对象做了很多事情(例如准备、烘烤、切片、装盒),但是由于Pizza对象是抽象的,所以orderPizza()对象并不知道哪些实际的具体类参与进来了,这就是解耦。当PizzaStore类调用orderPizza()方法时,某个披萨店子类将负责创建披萨。具体做哪种披萨,这个是由披萨店和“顾客”的选择共同决定的。可见,工厂方法用来处理对象的创建,并将这样的行为放在子类中,这样,客户程序中有关超类的代码就和子类中创建对象的代码解耦了

      说了这么半天,到现在还没有贴出Pizza实体类的代码。Pizza类和其一个子类SCCheesePizza类中的代码如下:

 1 public class Pizza {
 2     protected String name; // 名称
 3     protected String dough; // 面团类型
 4     protected String sauce; // 酱料类型
 5     protected String toppings; // 一套佐料
 6 
 7     public void prepare() {
 8         System.out.println("Preparing " + name + "...");
 9         System.out.println("Tossing dough...");
10         System.out.println("Adding sauce...");
11         System.out.println("Adding toppings:" + toppings + "...");
12     }
13 
14     public void bake() {
15         System.out.println("Bake for 25 minutes at temperature 350...");
16     }
17 
18     public void cut() {
19         System.out.println("Cutting the pizza into diagonal slices...");
20     }
21 
22     public void box() {
23         System.out.println("Place pizza in official PizzaStore box");
24     }
25 
26     public String getName() {
27         return this.name;
28     }
29 }
Pizza类的代码
 1 public class SCCheesePizza extends Pizza {
 2 
 3     public SCCheesePizza() {
 4         super.name = "Sichuan Style Cheese Pizza";
 5         super.dough = "Thin Crust Dough";
 6         super.sauce = "Marinara Sauce";
 7         super.toppings = "Grated Reggiano Cheese";
 8     }
 9 
10     // 覆盖父类中的cut()方法,将披萨切成方形
11     public void cut() {
12         System.out.println("Cutting the pizza into square slices...");
13     }
14 }
四川风味CheesePizza类的代码

      下面,我们来建立一个测试类MainClass,测试我们上面写的代码:

 1 public class MainClass {
 2 
 3     public static void main(String[] args) {
 4         // 创建一个四川风味的披萨店
 5         PizzaStore store = new SCPizzaStore();
 6         // 订购一个CheesePizza
 7         Pizza pizza = store.orderPizza("cheese");
 8 
 9         System.out.println("A " + pizza.getName() + " has been put on your desk.");
10     }
11 }
测试函数

      以下是运行结果:
设计模式 之 工厂模式_第2张图片

 

二、深入了解工厂模式:

      所有的工厂模式都用来封装对象的创建。工厂模式可以分为工厂方法模式抽象工厂模式

(一)工厂方法模式:

      工厂方法模式 通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的(这个定义是暂时的,下面我们会给出正式的定义)上面的“披萨店”的例子就是用工厂方法模式解决的。在这个例子中,我们创建了两类事物——创建者类(披萨店)和产品类(披萨)。具体来说,PizzaStore类是一个抽象的创建者类,它定义了一个抽象的工厂方法createPizza(),让子类实现它并创建产品(披萨),这个抽象类是用来制造产品的,但它不需要知道具体制造哪种具体的产品,它的子类需要知道;SCPizzaStore是PizzaStore许多子类中的一个,它与其他的子类一样,可以生产具体的产品,所以,它们又被成为具体创建者,也正因如此,每个地方特色的披萨店才可以利用实现自父类的createPizza()方法来生产自己地区的风味披萨。从水平的角度来分析,PizzaStore和Pizza类都是抽象类,在这两个类中定义了抽象的方法,供其子类实现。

设计模式 之 工厂模式_第3张图片

      有人可能会认为,当创建者类只有一个子类的时候,工厂模式就失去其效用了,这种方法是不对的。相比于直接创建对象,工厂方法有其优势,尽管只有一个创建者类,工厂方法依然很有用,因为它帮助我们将产品的“实现”从“使用”中解耦,另外,如果增减产品或者改变产品的实现,创建者类也不会受到影响。

      下面我们来给出工厂方法模式的正式定义:工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。简单的说,工厂方法让类把实例化推迟到子类中进行。将创建对象的方法集中在一个方法中,可以避免代码的重复,并且更方便以后的维护,这也意味着客户在实例化对象时,只会依赖于接口,而不是具体类。回到上面的类图,抽象的PizzaStore提供了一个创建对象的方法的接口,也成为“工厂方法”。在抽象的PizzaStore中,任何其他实现的方法都可能使用到这个工厂方法所制造出来的产品,但只有子类真正实现这个工厂方法并创建产品。我们常说,“工厂方法让子类决定要实例化的类是哪一个”,所谓的“决定”,并不是指模式允许子类本身在运行时做决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个——选择了使用哪个类,自然就决定了创建的产品是什么。在这里顺便声明一下,工厂方法和前面提到的简单工厂虽然看上去很相似,但其实不是一回事。简单工厂是把全部事情在一个地方都处理完了,而工厂方法却是创建一个框架,让子类决定如何实现。换言之,简单工厂可以把对象的创建封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。

      利用上面的理论,我们可以扩展前面的程序,比如:我们给每个地区的披萨店都提供原料源,这样一来,我们就需要再次使用工厂模式来创建原料的工厂。

(二)抽象工厂模式:

      先来看定义:抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。抽象工厂的任务是定义一个负责创建一组产品的接口,这个接口内的每个方法都负责创建一个具体的产品,同时我们通过利用实现抽象工厂接口的子类来提供这些具体的做法。也就是说,前面提到的“材料工厂”,更适合用抽象工厂模式进行处理。

(三)工厂方法模式和抽象工厂模式的异同与关系:

@相同点:两种模式都是负责将程序从特定的实现中解耦

@不同点:工厂方法模式完成解耦操作的方法是使用继承,而抽象工厂模式使用的方法是对象的组合;利用工厂方法模式创建对象,需要扩展一个类,并覆盖它“创建对象”的方法,而抽象工厂模式则是提供一个用来创建一个产品家族的抽象类型(可以把一群相关的产品集合起来);当有新的产品加入时,抽象工厂方法必须改变总的接口和每个子类的接口(毕竟是很多对象的集合),而工厂方法模式只需要改变工厂类中的工厂方法即可。

@关系:抽象工厂模式常常需要使用工厂方法来实现具体的工厂。

你可能感兴趣的:(设计模式 之 工厂模式)