前言:

除了使用new操作符之外,还有更多制造对象的方法。你将了解到实例化这个活动不应该总是公开的进行,也会意识到初始化会造成“耦合”的问题。工厂模式将会从复杂的依赖中帮你脱困。

1.   简单的工厂

当看到“new”,就会想到“具体”,的确也是在实例化一个具体的类,而不是接口。代码绑着具体的类导致代码更脆弱,更缺乏弹性。当有一群相关的具体类时,通常会有如下代码:

  1. Duck duck
  2. If(picnic)
  3. duck=new MallardDuck();
  4. else if(hunting)
  5. duck=new DecoyDuck();
  6. else if(inBathTub)
  7. duck=new RubberDuck();

这样的代码一旦有变化或者扩展,就必须重新修改此段代码进行检查和修改,这样的修改容易导致系统的维护和更新更难,也更容易犯错。

针对接口编程,可以隔离掉以后系统可能发生的一大堆改变,因为针对接口而写,可以通过多态,与任何新类实现该接口。当代码使用具体类时,一旦加入新的一些具体类就必须改变代码。这就违背了“对修改关闭”的原则。为了解决这样的问题,我们可以通过“找出变化,隔离并封装变化“的方式来解决。

现实场景:

披萨店生产披萨,当需要生产更多类型的披萨的时候,压力来自于如何增加更多的披萨类型。

  1. public class Pizza
  2. {
  3. Pizza OrderPizza(stringpizzaType)
  4. {
  5. Pizza pizza;
  6. if (pizzaType.Equals("cheese"))
  7. pizza = newCheesePizza();
  8. else if(pizzaType.Equals("greek"))
  9. pizza = newGreekPizza();
  10. else if(pizzaType.Equals("pepperoni"))
  11. pizza = newPepperoniPizza();
  12. pizza.prepare();
  13. pizza.bake();
  14. pizza.cut();
  15. pizza.box();
  16. return pizza;
  17. }
  18. }

如同开始是讲的那样,要新增新的pizza,就需要修改这段代码,修改如下:

  1. public class Pizza
  2. {
  3. Pizza OrderPizza(stringpizzaType)
  4. {
  5. Pizza pizza;
  6. if(pizzaType.Equals("cheese"))
  7. pizza = newCheesePizza();
  8. else if(pizzaType.Equals("greek"))
  9. pizza = newGreekPizza();
  10. else if(pizzaType.Equals("pepperoni"))
  11. pizza = newPepperoniPizza();
  12. else if(pizzaType.Equals("clam")) //新增的pizza类型
  13. pizza = newCalmPizza();
  14. else if(pizzaType.Equals("veggie"))//新增的pizza类型
  15. pizza = newVeggiePizza();
  16.  
  17. pizza.prepare();
  18. pizza.bake();
  19. pizza.cut();
  20. pizza.box();
  21. return pizza;
  22. }
  23. }

根据我们上边提到的“将变化抽离并封装“的原则,我们可以将创建pizza实例这一块给抽离出来,因为这块后边可能会新增一些别的pizza类型,由一个对象来专职创建pizza对象。我们称这个新对象为”工厂“。代码如下:

  1. public class SimplePizzaFactory
  2. {
  3. public PizzaCreatePizza(string pizzaType)
  4. {
  5. Pizza pizza = null;
  6. if(pizzaType.Equals("cheese"))
  7. pizza = newCheesePizza();
  8. else if (pizzaType.Equals("pepperoni"))
  9. pizza = newPepperoniPizza();
  10. else if(pizzaType.Equals("clam"))
  11. pizza = newCalmPizza();
  12. else if(pizzaType.Equals("veggie"))
  13. pizza = newVeggiePizza();
  14. return pizza;
  15. }
  16. }

这样做的好处在于,把创建pizza对象的方法包装成一个类,当以后实现改变时,只需要修改这个类即可。与此同时我们还可以把生成其他类的方法也放在这个简单的工厂中。

这样我们生成pizza类的代码就变成如下的样子:

  1. public class Pizza
  2. {
  3. Pizza OrderPizza(stringpizzaType)
  4. {
  5. Pizza pizza;
  6. SimplePizzaFactorysimplePizzaFactory = new SimplePizzaFactory();//生成pizza
  7. pizza =simplePizzaFactory.CreatePizza(pizzaType);
  8. pizza.prepare();
  9. pizza.bake();
  10. pizza.cut();
  11. pizza.box();
  12. return pizza;
  13. }
  14. }

经过这样一系列的修改,我们的Pizza的类图就变成如下的样子:

虽然我们一直在说简单的工厂,但事实上简单工厂并不是一个设计模式,更像是一种编程习惯。这里讲简单的工厂,主要是为了引出下面的两个重量级的模式,它们都是工厂。

2.   工厂方法

在1中,我们通过简单的工厂解决了生产不同披萨的问题,但是,如果有新的加盟店加盟进来,如何解决不同加盟店的区域差异、质量问题呢?或许,我们可以像1中那样利用简单的工厂,对应不同地区的加盟店创建不同的工厂。

这样做导致的另一个问题就是,不同的加盟店披萨的制作流程、方法可能不同,如何才能把加盟店和创建披萨捆绑在一起的同时又保持一定的弹性?

我们可以把CreatePizza()方法放回到PizzaStore中,但是要把它设置为抽象方法,然后为每一个区域加盟店创建一个PizzaStore的子类。

如下所示:

  1. public abstract class PizzaStore
  2. {
  3. public PizzaOrderPizza(string pizzaType)
  4. {
  5. Pizza pizza =CreatePizza(pizzaType);
  6. pizza.Prepare();
  7. pizza.Bake();
  8. pizza.Cut();
  9. pizza.Box();
  10. return pizza;
  11. }
  12.  
  13. public abstract PizzaCreatePizza(string pizzaType);//把工厂对象移到该方法中,该方法为抽象方法
  14. }

现在我们有了PizzaStore超类,让各个不同地域的加盟店继承此超类即可。具体的类图如下:

2.1  声明一个工厂方法

原本是由一个对象复制所有具体类的实例化,现在通过对PizzaStore做一些转变,变成由一群子类负责实例化。

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

2.2 具体代码实现

2.2.1 定义抽象的PizzaStore类,并抽象出工厂方法
  1. public abstract class PizzaStore
  2. {
  3. public Pizza OrderPizza(stringtype)
  4. {
  5. Pizza pizza = CreatePizza(type);
  6. pizza.Prepare();
  7. pizza.Bake();
  8. pizza.Cut();
  9. pizza.Box();
  10.  
  11. return pizza;
  12. }
  13. public abstract Pizza CreatePizza(stringtype);//抽象出创建Pizza的工厂方法,由子类实现该方法并创建具体的Pizza
  14. }
2.2.2 实现具体的PizzaStore类,让子类做决定
  1. public class MYPizzaStore:PizzaStore
  2. {
  3. public override Pizza CreatePizza(string type)
  4. {
  5. Pizza pizza=null;
  6. switch(type)
  7. {
  8. case "cheese":
  9. pizza = new NYStyleCheesePizza();
  10. break;
  11. case "veggie":
  12. pizza=new NYStyleVeggiePizza();
  13. break;
  14. case "clam":
  15. pizza=new NYStyleClamPizza();
  16. break;
  17. case "pepperoni":
  18. pizza=new NYStylePepperoniPizza();
  19. break;
  20. }
  21. return pizza;
  22. }
  23. }
2.2.3抽象Pizza类,并实现具体的Pizza类
2.2.3.1 抽象Pizza类
  1. public abstract class Pizza
  2. {
  3. public string name;
  4. public string dough;
  5. public string sauce;
  6. public ArrayList toppings = newArrayList();
  7. public void Prepare()
  8. {
  9. System.Console.WriteLine("Preparing" + name);
  10. System.Console.WriteLine("Tossingdough...");
  11. System.Console.WriteLine("Addingsauce..");
  12. System.Console.WriteLine("Addingtoppings: ");
  13. for(int i = 0; i < toppings.Count; i++)
  14. {
  15. System.Console.WriteLine(" "+ toppings[i]);
  16. }
  17. }
  18.  
  19. public void Bake()
  20. {
  21. System.Console.WriteLine("Bakefor 25 minutes at 350");
  22. }
  23.  
  24. public void Cut()
  25. {
  26. System.Console.WriteLine("Cuttingthe pizza into diagonal slices");
  27. }
  28.  
  29. public void Box()
  30. {
  31. System.Console.WriteLine("Placepizza in official PizzaStore box");
  32. }
  33.  
  34. public string GetName()
  35. {
  36. return name;
  37. }
  38. }
2.2.3.2 具体的Pizza类
  1. public class NYStyleCheesePizza : Pizza
  2. {
  3. public NYStyleCheesePizza()
  4. {
  5. name = "NY StyleSauc and Cheese Pizza";
  6. dough="Thin Crust Dough";
  7. sauce="Marinara Sauce";
  8. toppings.Add("GratedReggiano Cheese");
  9. }
  10. }

2.2  总结

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

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

创建者(Creator)类

产品类

简单工厂和工厂方法之间的差异?

简单工厂是在一个地方把所有的事都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现。简单工厂的做法,可以将对象的创建封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。

3.   依赖倒置原则

要依赖抽象,不要依赖具体类

不能让高层组件依赖底层组件,而且不管高层、底层组件,两者都应该依赖于抽象。

如何避免违反依赖倒置原则:

l  变量不可以持有具体类的引用。

如果使用new,则会持有具体类的引用,可以用工程来避开这样的做法

l  不要让类派生自具体类。

如果派生自具体类,你就会依赖具体类(请派生自一个抽象类或接口)

l  不要覆盖基类中已实现的方法。

如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。

4.   抽象工厂

4.1 创建工厂接口

回到上文的Pizza店,现在有新的需求,想要确保每家加盟店使用高质量的材料,打算创建一家生产原料的加工厂,并将原料送到各个加盟店。这个工厂负责创建原料家族中的每一种原料,工厂需要生产面团、酱料、芝士等。先为工厂定义一个接口,该接口负责所有原料:

  1. public interface PizzaIngredientFactory
  2. {
  3. Dough CreateDough();
  4.  
  5. Sauce CreateSauce();
  6.  
  7. Cheese CreateCheese();
  8.  
  9. Veggies[] CreateVeggies();
  10.  
  11. Pepperoni CreatePepperoni();
  12.  
  13. Clams CreateClam();
  14. }

3.2创建原料工厂

  1. public class NYPizzaIngredientFactory : PizzaIngredientFactory//具体原料工厂必须实现这个接口
  2. {
  3. public Dough CreateDough()
  4. {
  5. return new ThinCrustDough();
  6. }
  7.  
  8. public Sauce CreateSauce()
  9. {
  10. return new MarinaraSauce();
  11. }
  12.  
  13. public Cheese CreateCheese()
  14. {
  15. return new ReggianoCheese();
  16. }
  17.  
  18. public Veggies[] CreateVeggies()
  19. {
  20. Veggies[] veggies = new Veggies[]{ new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
  21. return veggies;
  22. }
  23.  
  24. public Pepperoni CreatePepperoni()
  25. {
  26. return new SlicedPepperoni();
  27. }
  28.  
  29. public Clams CreateClam()
  30. {
  31. return new FreshClams();
  32. }
  33. }

4.3 重新抽象Pizza类

  1. public abstract class Pizza
  2. {
  3. public string name;
  4. public Dough dough;
  5. public Sauce sauce;
  6. public Veggies[] veggies;
  7. public Cheese cheese;
  8. public Pepperoni pepperoni;
  9. public Clams clam;
  10. public ArrayList toppings = newArrayList();
  11.  
  12. public abstract void Prepare();//把Prepare()方法声明成抽象,在这个方法中,我们需要收集Pizza所需的原材料,而这些原材料来自原料工厂。
  13. public void Bake()
  14. {
  15. System.Console.WriteLine("Bakefor 25 minutes at 350");
  16. }
  17.  
  18. public void Cut()
  19. {
  20. System.Console.WriteLine("Cuttingthe pizza into diagonal slices");
  21. }
  22.  
  23. public void Box()
  24. {
  25. System.Console.WriteLine("Placepizza in official PizzaStore box");
  26. }
  27.  
  28. public string GetName()
  29. {
  30. return name;
  31. }
  32. }

4.4 重新实现Pizza

  1. public class NYStyleCheesePizza : Pizza
  2. {
  3. PizzaIngredientFactory ingredientFactory;
  4. public NYStyleCheesePizza(PizzaIngredientFactoryingredientFactory)//制作Pizza需要工厂提供原材料,
  1. 所以每个pizza类都需要从构造器中得到一个工厂,并将工厂存储在变量中
  2. {
  3. this.ingredientFactory =ingredientFactory;
  4. name = "NY StyleSauc and Cheese Pizza";
  5. toppings.Add("GratedReggiano Cheese");
  6. }
  7.  
  8. public override void Prepare()
  9. {
  10. System.Console.WriteLine("Preparing" + name);
  11. dough = ingredientFactory.CreateDough();
  12. sauce = ingredientFactory.CreateSauce();
  13. cheese = ingredientFactory.CreateCheese();
  14. }
  15.  
  16. }

4.5 重新生产pizza

  1. public class MYPizzaStore:PizzaStore
  2. {
  3. public override Pizza CreatePizza(string type)
  4. {
  5. Pizza pizza=null;
  6. PizzaIngredientFactory ingredientFactory= new NYPizzaIngredientFactory();
  7. switch(type)
  8. {
  9. case "cheese":
  10. pizza = new NYStyleCheesePizza(ingredientFactory);
  11. break;
  12. case "veggie":
  13. pizza=new NYStyleVeggiePizza(ingredientFactory);
  14. break;
  15. case "clam":
  16. pizza=new NYStyleClamPizza(ingredientFactory);
  17. break;
  18. case "pepperoni":
  19. pizza=new NYStylePepperoniPizza(ingredientFactory);
  20. break;
  21. }
  22. return pizza;
  23. }
  24. }

通过这一系列的操作,我们引入了新类型的工厂,也就是所谓的“抽象工厂”,来创建pizza原来家族。通过抽象工厂所提供的接口创建产品家族,利用这个接口书写代码,我们的代码将从实际工厂解耦,以便在不同上下文中实现各式各样的工厂,制造出各种不同的产品。

4.6 定义抽象工厂模式

抽象工厂模式提供一个接口,用于创建相关或依赖对象家族,而且不需要致命具体类。

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

4.7 抽象工厂与工厂方法的对比

抽象工厂和工厂方法都是负责创建对象。

抽象工厂是通过对象的组合

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

所以利用工厂方法创建对象时,需要扩展一个类,并覆盖它的工厂方法。

整个工厂方法模式,只不过就是通过子类来创建对象,这种做法,客户只需要知道他们所使用的抽象类型就可以了,而由子类负责决定具体类型。将客户从具体类型中解耦。

工厂方法是继承。

抽象工厂方法是将一群相关的产品集合起来,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

Head First 设计模式之工厂模式(Factory Pattern)的更多相关文章

  1. python 设计模式之工厂模式 Factory Pattern (简单工厂模式,工厂方法模式,抽象工厂模式)

    十一回了趟老家,十一前工作一大堆忙成了狗,十一回来后又积累了一大堆又 忙成了狗,今天刚好抽了一点空开始写工厂方法模式 我看了<Head First 设计模式>P109--P133 这25页 ...

  2. 【设计模式】工厂模式 Factory Pattern

    1)简单工厂(不是模式) 简单工厂只是一种变成习惯,并非23种设计模式之一. 简单工厂提供将实例话那种类型留给运行时判断,而非编译时指定.简单工厂模式就是由一个工厂类根据传入的参数决定创建出哪一个类的 ...

  3. JAVA设计模式之工厂模式—Factory Pattern

    1.工厂模式简介 工厂模式用于对象的创建,使得客户从具体的产品对象中被解耦. 2.工厂模式分类 这里以制造coffee的例子开始工厂模式设计之旅. 我们知道coffee只是一种泛举,在点购咖啡时需要指 ...

  4. 设计模式 - 工厂模式(factory pattern) 具体解释

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u012515223/article/details/27081511 工厂模式(factory pa ...

  5. 设计模式之工厂模式(Factory)

    设计模式的工厂模式一共有三种:简单工厂模式,工厂模式,抽象工厂模式 简单工厂模式原理:只有一个工厂类,通过传参的形式确定所创建的产品对象种类 代码如下: #include <stdio.h> ...

  6. 23种设计模式--工厂模式-Factory Pattern

    一.工厂模式的介绍       工厂模式让我们相到的就是工厂,那么生活中的工厂是生产产品的,在代码中的工厂是生产实例的,在直白一点就是生产实例的类,代码中我们常用new关键字,那么这个new出来的实例 ...

  7. 创建型模式篇(工厂模式Factory Pattern)

    一.工厂模式(Factory Pattern) 1.定义: 在软件系统,经常面临着“某个对象”的创建工作,由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口.提供一种封 ...

  8. java_设计模式_工厂模式_Factory Pattern(2016-08-04)

    工厂模式主要是为创建对象提供了接口.工厂模式按照<Java与模式>中的提法分为三类: (1)简单工厂(Simple Factory)模式,又称静态工厂方法模式(Static Factory ...

  9. 设计模式之工厂模式(Factory模式)

    在面向对象系统设计中经常遇到以下两类问题: 1)为了提高内聚(Cohesion)和松耦合(Coupling),我们经常会抽象出一些类的公共接口以形成抽象基类或者接口.这样我们可以通过声明一个指向基类的 ...

  10. 设计模式~简单工厂模式(Factory)

    简单工厂模式Simple Factory根据提供给它的数据,返回一个类的实例.通常它返回的类都有一个公共的父类(或者接口对象). 简单工厂的作用是实例化对象,而不需要客户了解这个对象属于哪个具体的子类 ...

随机推荐

  1. MySQL数据库9 - 日期与时间函数

    一 日期和时间函数 函数的概念:按指定格式输入参数,返回正确结果的运算单元 1. 返回当前日期:curdate() current_date() current_date()+0可以将当前日期转换为数 ...

  2. script标签里的defer属性

    入职新公司,看代码的时候注意到有的script标签中有一个defer属性,查了一下.在这里分享出来. 需要注意的有三点,其中前两点是在错误中分辨出来的: 错误来源:http://www.w3schoo ...

  3. entity framework 新手入门篇(4)-entity framework扩展之 entityframework.extended

    对于EF的操作,我们已经有了大概的了解了,但对于实战来说,似乎还欠缺着一些常用的功能,那就是批量的删除,更新数据. 承接上面的部分,我们有一个叫做House的数据库,其中包含house表和seller ...

  4. std::vector<bool>中的坑

    http://www.cplusplus.com/reference/vector/vector/?kw=vector C++中,vector<bool>为了达到节省内存的目的,专门做了特 ...

  5. 长沙市轨道交通工程BIM应用招标公告

    摘要: 长沙市轨道交通集团有限公司对其长沙市轨道交通3号线一期工程建筑信息模型(BIM)技术应用项目进行国内公开招标 长沙市轨道交通集团有限公司对其长沙市轨道交通3号线一期工程建筑信息模型(BIM)技 ...

  6. python27(32位)安装RTree

    一开始用pip install -r requirements.txt 报错:OSError: could not find or load spatialindex_c.dll 1)从以下链接下载编 ...

  7. re.S

    在Python的正则表达式中,有一个参数为re.S.它表示多行匹配

  8. 安装.cer证书并将证书从.cer格式转化为.pem格式

    ## 安装.cer证书并将证书从.cer格式转化为.pem格式 ### 安装.cer证书到本地 打开*运行*窗口 输入MMC.exe, 单击*确定* 在打开的控制台1的窗口中. 选择*文件*, 选择* ...

  9. 关于jackson处理数据

    /**     * 将请求参数封装成Map对象     *     * @param json 参数     * @return Object     */    public static Map ...

  10. day26_网络编程第一天

    1.网络通信三要素(掌握) IP      端口号      协议(UPD&TCP) 2.UDP协议与TCP协议各自特点(掌握) UDP 1.不需要建立连接:     2.有数据大小限制,每个 ...