摘要

这篇博客记录了new操作符新建对象不够灵活的问题,通过一个披萨系统讲解了简单工厂,工厂方法模式,抽象工厂模式。并对依赖倒置原则进行了简单的说明。

new的噩梦

小明又接到了新活,这次甲方是一家披萨店,要求设计一个披萨订单系统。作为一名优秀的程序设计师,小明很快有了思路。

//返回一个做好的披萨
Pizza orderPizza(){Pizza pizza = new Pizza()//下面是披萨的加工,烘烤,切片,装箱的过程pizza.prepare();pizza.bake();pizza.cut();pizza.box();return pizza;
}

但是过了几天,披萨店老板表示:"小明,时代变了。现在我们有很多种披萨,我们要求用户要什么披萨,我们就做什么披萨。”

小明想,这个简单,只要把披萨抽象成一个接口,再用不同种类的披萨去继承它就好了。最后根据用户输入的字符串,生成所需的披萨。

//根据类型返回一个用户选定的披萨
Pizza orderPizza(String type){Pizza pizza;//根据用户的口味生成不同的披萨if(type.equals("cheese"))pizza = new CheesePizza();else if(type.equals("greek"))pizza = new GreekPizza();else if(type.equals("pipperoni"))pizza = new Pipperoni();//下面是披萨的加工,烘烤,切片,装箱的过程pizza.prepare();pizza.bake();pizza.cut();pizza.box();return pizza;
}

然而过了几天,老板表示:“小明,时代又变了,有的披萨经营不善,我们不做了。我们又加入了一些新的披萨种类。”这意味着小明又得忙活了。

然而就算这次修改了代码,随着时间过去,菜单改变,代码必须一改再改。这里的代码没有对修改封闭。

图1 不断修改的代码

问题出在哪里呢?小明仔细观察了一下代码,哦,原来问题出在了new这个关键字上。当看到new,就会想到具体。例如

Pizza pizza = new CheesePizza();

这里等号左边Pizza pizza使用接口的确让代码更具备弹性,但是等号右边new CheesePizza()还是得建立具体的实例。当使用具体类的时候,一旦引入新的具体类或者修改具体类,就必须修改代码。能不能不用new就新建对象呢?小明心想。

简单工厂

小明补习了设计模式秘籍,终于找到了好办法。秘籍告诉他,有一种方法叫简单工厂,把创建披萨的代码移到另一个对象,由这个对象专门负责制造披萨。这个负责制造披萨的对象,我们就叫它披萨工厂吧。

披萨工厂SimplePizzaFactory的代码。

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

没有neworderPizza方法。再也无需再关心它所要制造什么种类的披萨,如果要修改披萨种类,只需要修改工厂代码就可以了。

public class PizzaStore {SimplePizzaFactory factory;public PizzaStore(SimplePizzaFactory factory) { this.factory = factory;}public Pizza orderPizza(String type) {Pizza pizza;pizza = factory.createPizza(type);pizza.prepare();pizza.bake();pizza.cut();pizza.box();return pizza;}}

有人会觉得这是个障眼法,实际上不过是把new的操作放到了另一个对象中,要修改类的时候,还是得不停地去修改那些if-else语句,不过是换了一个地方修改,这有什么意义呢?

小明表示,别忘了,我们的工厂可以有很多的客户,我们可能有个快递系统,也要创建披萨,假如所有的客户都要自己去new实例化这披萨,那么修改起来会更加麻烦。我们给所有的客户提供工厂创建接口,将所有的new实例化代码全部从客户程序删除。要修改实例化代码,只需要集中到工厂中修改就好了。

简单工厂并不是一个设计模式,只是一种编程习惯,即将创建对象A的任务放到简单工厂B中,以后创建A时候都调用简单工厂B就行了。接下来我们将讲讲两个重量级的设计模式。

工厂方法模式

披萨店老板在小明的设计下,击败了对手,在全国开起了连锁店。现在分店开到了纽约,芝加哥,加州。每个地方的披萨口味都不一样。
假如还是按照之前的简单工厂方法。那就要新建三个工厂。但是这么做未免太过繁琐,这三个工厂仅仅是披萨的味道不一样,加工流程都是一样的。小明想,我能不能从中抽出不变的部分,让它们继承同一个超类呢?说干就干,小明决定新建一个抽象类PizzaStore,将creatPizza()方法设为抽象。

图2 修改后的工厂

下面是他改造的抽象披萨店的代码

public abstract class PizzaStore {abstract Pizza createPizza(String item);public Pizza orderPizza(String type) {Pizza pizza = createPizza(type);System.out.println("--- Making a " + pizza.getName() + " ---");pizza.prepare();pizza.bake();pizza.cut();pizza.box();return pizza;}
}

实现纽约披萨店的代码

package headfirst.designpatterns.factory.pizzafm;public class NYPizzaStore extends PizzaStore {Pizza createPizza(String item) {if (item.equals("cheese")) {return new NYStyleCheesePizza();} else if (item.equals("veggie")) {return new NYStyleVeggiePizza();} else if (item.equals("clam")) {return new NYStyleClamPizza();} else if (item.equals("pepperoni")) {return new NYStylePepperoniPizza();} else return null;}
}

这里实际上用到了新的设计模式——工厂方法模式。工厂模式中,有两个角色

  • 抽象创建者类,定义了抽象的工厂方法,让子类去实现它制造产品。如PizzaStore,它提供的工厂方法是orderPizza()
  • 具体创建者类,继承了抽象创建者类,实现了工厂方法。如NYPizzaStore

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

图3 工厂模式图解

依赖倒置原则

让我们来看看一个垃圾的披萨店设计

package headfirst.designpatterns.factory.pizzafm;public class DependentPizzaStore {public Pizza createPizza(String style, String type) {Pizza pizza = null;if (style.equals("NY")) {if (type.equals("cheese")) {pizza = new NYStyleCheesePizza();} else if (type.equals("veggie")) {pizza = new NYStyleVeggiePizza();} else if (type.equals("clam")) {pizza = new NYStyleClamPizza();} else if (type.equals("pepperoni")) {pizza = new NYStylePepperoniPizza();}} else if (style.equals("Chicago")) {if (type.equals("cheese")) {pizza = new ChicagoStyleCheesePizza();} else if (type.equals("veggie")) {pizza = new ChicagoStyleVeggiePizza();} else if (type.equals("clam")) {pizza = new ChicagoStyleClamPizza();} else if (type.equals("pepperoni")) {pizza = new ChicagoStylePepperoniPizza();}} else {System.out.println("Error: invalid type of pizza");return null;}pizza.prepare();pizza.bake();pizza.cut();pizza.box();return pizza;}
}

这里我们在实例化不同的对象,实际上就是在依赖它的具体类。当这些具体类要修改的时候,就要修改依赖它的类。

图4 高度依赖的类

所以,在代码中,减少对具体类的依赖是我们的追求。这实际上就是“依赖倒置原则”所提倡的。

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

听起来是不是很像“针对接口编程,不针对实现编程”,并不一样,这里更加强调的是抽象。它想要强调的是:不要让高层组件依赖低层组件。并且高层组件和低层组件都应该依赖于抽象。例如上文中,高层组件“PizzaStore”就依赖于各种低层组件披萨类。我们应该去重写代码以便于我们依赖于抽象类,而不依赖具体类。

现在就让根据这个原则改造一下代码,在上述代码中,我们需要自己去实例化每个具体的披萨类。我们可以用工厂方法把实例化的过程抽取出来。

图5 依赖置换后

那么为什么要叫依赖置换原则呢?我们置换了什么呢?

在以往的设计的流程中,我们要开一个披萨店,就要提供各种口味的披萨。这样披萨店就会牢牢依靠这些具体的披萨种类。后面的设计里,我们抛开具体的披萨种类,将所有口味的披萨抽象化。得到一个抽象的Pizza类。这样,我们的过去的高层组件(披萨店)依赖于底层组件(各种口味的披萨),现在,无论高层组件(披萨店)还是底层组件(各种口味的披萨)都依赖于抽象的Pizza类。

抽象工厂模式

现在披萨店已经具备了弹性的框架,并且遵循设计原则。只剩下具体的披萨没有设计了。这里,我们发现,披萨是由面团,酱料,芝士,佐料构成。而面团,酱料等组件有不同的种类。这些不同种类搭配出了不同口味的披萨。

分析一下,就可以发现,所有的披萨具有相同的组件,而这些披萨的组件有不同的实现。不同区域的披萨店实现了完整的原料家族。

  • 根据之前的工厂模式,我们建立一个抽象的原料工厂PizzaIngredientFactory。然后用具体制造的工厂实现它,用来生产不同的原料。
  • 把每种披萨的原料如面粉,酱料等设计为接口,让具体的类去继承这个接口。
  • 披萨店的具体实例作为抽象原料工厂PizzaIngredientFactory的客户。

我们设计出了这样的类图。

图5 抽象工厂类图

这里,我们邂逅了新的设计模式——抽象工厂模式,这个模式可以创建对象的家族,例如披萨,一套时装。PizzaIngredientFactory就是一个抽象工厂。

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

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

抽象工厂模式和工厂方法模式都用于创建对象,将创建对象这一任务单独抽出。但是它们的实现方法不同。

  • 工厂方法模式主要用继承,用具体类继承抽象类,覆盖它的工厂方法。这样,客户就只需要关心他们所使用的抽象类型就可以了,而不必去担心具体的类。一般适用于计划不同条件下创建不同实例时。
  • 抽象工厂模式主要使用组合,将一组产品集合为一个抽象类,然后让子类去实现它。主要用于创建一个产品家族的抽象类型。不过,当加入新的产品的时候,就需要修改接口。一般用于创建对象家族。

Head First 设计模式笔记 4.工厂模式相关推荐

  1. JAVA设计模式笔记(简单工厂模式)

    有一个水果加工工厂,我们的需求是实现加工水果的功能.常见的做法是创建一个水果抽象类,写一个抽象的加工方法,然后创建具体的水果类去继承抽象类,再去实现抽象方法,最后客户端再去新建对象实例. public ...

  2. getinstance方法详解_二、设计模式总览及工厂模式详解

    二.架构师内功心法之设计模式 2.架构师内功心法之设计模式 2.1.课程目标 1.通过对本章内容的学习,了解设计模式的由来. 2.介绍设计模式能帮我们解决哪些问题. 3.剖析工厂模式的历史由来及应用场 ...

  3. C#设计模式(2)——简单工厂模式

    一.引言 这个系列也是自己对设计模式的一些学习笔记,希望对一些初学设计模式的人有所帮助的,在上一个专题中介绍了单例模式,在这个专题中继续为大家介绍一个比较容易理解的模式--简单工厂模式. 二.简单工厂 ...

  4. .NET设计模式(1): 简单工厂模式

    .NET设计模式(1): 简单工厂模式 最近一直在看设计模式,想把自己的学习笔记与大家分享一下,如果能帮助大家的话,我会非常高兴,同时也欢迎大家指出里面的不足.园子里其实关于此类文章已经很多了,如果d ...

  5. php工程模式,PHP设计模式(八):工厂模式

    Introduction 在PHP设计模式(七):设计模式分类中我们提到过创建设计模式(Creation patterns),创建设计模式专注于设计对象(Object)和实例(Instance)的创建 ...

  6. 设计模式系列·抽象工厂模式

    前言 以小说的笔法写的设计模式系列文章,你绝对看得懂![首发于公众号:"聊聊代码"] 设计模式系列·王小二需求历险记(一) 设计模式系列·王小二需求历险记(二) 设计模式系列·封装 ...

  7. 设计模式三—抽象工厂模式

    设计模式三-抽象工厂模式 一.定义 抽象工厂模式是工厂方法模式的进一步抽象.如果产品簇中只有一种产品,则退化为工厂方法模式. 二.原理图 三.代码实例 * 苹果和土豆是园丁1的杰作 * 葡萄和西红柿是 ...

  8. 乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern)

    原文:乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern) [索引页] [源码下载] 乐在其中设计模式(C#) - 抽象工厂模式(Abstract Facto ...

  9. java设计模式---三种工厂模式

    工厂模式提供创建对象的接口. 工厂模式分为三类:简单工厂模式(Simple Factory), 工厂方法模式(Factory Method)和抽象工厂模式(Abstract Factory). GOF ...

  10. 设计模式复习-抽象工厂模式

    设计模式复习-抽象工厂模式 有两种硬件,PC和Phone,有两种系统,Windows和Linux,现在假设PC和Phone上全都能安装这两个系统,并且将来硬件不会在变化,但是系统可能需要扩展,比如扩展 ...

最新文章

  1. 解决GNS3桥接本地网卡后无法跟本地网卡通信的故障。
  2. 网络编程学习笔记(TCP套接口选项)
  3. C# socket编程第二篇
  4. 读《三体Ⅱ · 黑暗森林》| 人能相互理解的前提是力量对等
  5. android builder模式,模式设计(三:Builder模式) (转)
  6. SAP CRM呼叫中心里的事件注册机制
  7. Java调试器–权威的工具列表
  8. ANSI/UTF-8/UCS2(UTF-16),以及回车换行
  9. java高级之泛型详解
  10. 老板说,我请你来,不是叫你无脑拷贝的,竟然在线上搞出这么大的一个BUG......
  11. rest framework 权限
  12. 寻找两个正序数组中的中位数 数组
  13. Ant 执行 YUICompressor
  14. ACM 程序设计竞赛 数学题目
  15. java ssh 404,SSH框架上的404异常
  16. Centos yum 包管理工具离线安装
  17. .rpt文件内容读取java_Java项目读取配置文件时,找不到指定的文件???
  18. qq远程听到计算机声音,QQ2017远程播放视频没有声音_软件应用_电脑知识学习_培训之家...
  19. 【论文阅读笔记】Structured-light 3D surface imaging: a tutorial(结构光三维表面成像)
  20. 数据库服务器常见操作系统,服务器的数据库和操作系统

热门文章

  1. uniapp:轮播里如何加入视频
  2. ubuntu搭建PHP网站完整实例教程
  3. Greenplum小把戏 - 几个常用数据库对象大小查询SQL
  4. 深圳大学物计算机黄yilin,中国科学引文数据库(CSCD)收录本校教师论文情况.doc...
  5. 调色板的原理和调色板显示模式
  6. java调用按键精灵安卓_安卓版按键精灵基本功能版
  7. 和程序员谈恋爱的7种体验
  8. 步态情绪识别:STEP学习
  9. 苹果android系统版本,给Mac装上的Android系统?Remix OS PC版
  10. XPDL与WS-BPEL的比较之二:二者内容的大致概述