Head First 设计模式笔记 4.工厂模式
摘要
这篇博客记录了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;}
}
没有new
的orderPizza
方法。再也无需再关心它所要制造什么种类的披萨,如果要修改披萨种类,只需要修改工厂代码就可以了。
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.工厂模式相关推荐
- JAVA设计模式笔记(简单工厂模式)
有一个水果加工工厂,我们的需求是实现加工水果的功能.常见的做法是创建一个水果抽象类,写一个抽象的加工方法,然后创建具体的水果类去继承抽象类,再去实现抽象方法,最后客户端再去新建对象实例. public ...
- getinstance方法详解_二、设计模式总览及工厂模式详解
二.架构师内功心法之设计模式 2.架构师内功心法之设计模式 2.1.课程目标 1.通过对本章内容的学习,了解设计模式的由来. 2.介绍设计模式能帮我们解决哪些问题. 3.剖析工厂模式的历史由来及应用场 ...
- C#设计模式(2)——简单工厂模式
一.引言 这个系列也是自己对设计模式的一些学习笔记,希望对一些初学设计模式的人有所帮助的,在上一个专题中介绍了单例模式,在这个专题中继续为大家介绍一个比较容易理解的模式--简单工厂模式. 二.简单工厂 ...
- .NET设计模式(1): 简单工厂模式
.NET设计模式(1): 简单工厂模式 最近一直在看设计模式,想把自己的学习笔记与大家分享一下,如果能帮助大家的话,我会非常高兴,同时也欢迎大家指出里面的不足.园子里其实关于此类文章已经很多了,如果d ...
- php工程模式,PHP设计模式(八):工厂模式
Introduction 在PHP设计模式(七):设计模式分类中我们提到过创建设计模式(Creation patterns),创建设计模式专注于设计对象(Object)和实例(Instance)的创建 ...
- 设计模式系列·抽象工厂模式
前言 以小说的笔法写的设计模式系列文章,你绝对看得懂![首发于公众号:"聊聊代码"] 设计模式系列·王小二需求历险记(一) 设计模式系列·王小二需求历险记(二) 设计模式系列·封装 ...
- 设计模式三—抽象工厂模式
设计模式三-抽象工厂模式 一.定义 抽象工厂模式是工厂方法模式的进一步抽象.如果产品簇中只有一种产品,则退化为工厂方法模式. 二.原理图 三.代码实例 * 苹果和土豆是园丁1的杰作 * 葡萄和西红柿是 ...
- 乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern)
原文:乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern) [索引页] [源码下载] 乐在其中设计模式(C#) - 抽象工厂模式(Abstract Facto ...
- java设计模式---三种工厂模式
工厂模式提供创建对象的接口. 工厂模式分为三类:简单工厂模式(Simple Factory), 工厂方法模式(Factory Method)和抽象工厂模式(Abstract Factory). GOF ...
- 设计模式复习-抽象工厂模式
设计模式复习-抽象工厂模式 有两种硬件,PC和Phone,有两种系统,Windows和Linux,现在假设PC和Phone上全都能安装这两个系统,并且将来硬件不会在变化,但是系统可能需要扩展,比如扩展 ...
最新文章
- 解决GNS3桥接本地网卡后无法跟本地网卡通信的故障。
- 网络编程学习笔记(TCP套接口选项)
- C# socket编程第二篇
- 读《三体Ⅱ · 黑暗森林》| 人能相互理解的前提是力量对等
- android builder模式,模式设计(三:Builder模式) (转)
- SAP CRM呼叫中心里的事件注册机制
- Java调试器–权威的工具列表
- ANSI/UTF-8/UCS2(UTF-16),以及回车换行
- java高级之泛型详解
- 老板说,我请你来,不是叫你无脑拷贝的,竟然在线上搞出这么大的一个BUG......
- rest framework 权限
- 寻找两个正序数组中的中位数 数组
- Ant 执行 YUICompressor
- ACM 程序设计竞赛 数学题目
- java ssh 404,SSH框架上的404异常
- Centos yum 包管理工具离线安装
- .rpt文件内容读取java_Java项目读取配置文件时,找不到指定的文件???
- qq远程听到计算机声音,QQ2017远程播放视频没有声音_软件应用_电脑知识学习_培训之家...
- 【论文阅读笔记】Structured-light 3D surface imaging: a tutorial(结构光三维表面成像)
- 数据库服务器常见操作系统,服务器的数据库和操作系统
热门文章
- uniapp:轮播里如何加入视频
- ubuntu搭建PHP网站完整实例教程
- Greenplum小把戏 - 几个常用数据库对象大小查询SQL
- 深圳大学物计算机黄yilin,中国科学引文数据库(CSCD)收录本校教师论文情况.doc...
- 调色板的原理和调色板显示模式
- java调用按键精灵安卓_安卓版按键精灵基本功能版
- 和程序员谈恋爱的7种体验
- 步态情绪识别:STEP学习
- 苹果android系统版本,给Mac装上的Android系统?Remix OS PC版
- XPDL与WS-BPEL的比较之二:二者内容的大致概述