设计模式 之 工厂模式

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

      假设有一个披萨店,下面,我们就“做披萨”这件事来进行代码的分析。(注:在这里,我们把做披萨分为四个步骤:准备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张图片

 

二、深入了解工厂模式:

未完待续。。。。。。。。

 

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