读headFirst设计模式 - 工厂模式
每次写博客都不知道要怎么引入要写的主题,挺头疼的一件事。今天就直接开门见山,今天要学的就是工厂模式,工厂就是批量生产制造东西的地方。在这里,工厂就是批量生产对象的地方。
学习书上的例子
假如你现在有一个披萨店PizzaStore,店里有各种各样的pizza,现在你需要根据订单生产pizza,可能会这样做
- public Pizza orderPizza(String type) {
- Pizza pizza = null;
- if (type.equals("cheese"))
- pizza = new CheesePizza();
- else
- pizza = new GreekPizza();
- }
将具体需要哪种pizza直接放在orderPizza方法中,那需要增加新的pizza呢……好吧,这里先跳过,先看一下如果使用简单工厂要怎么做。(书上说简单工厂不算一个“真正的”模式,更像一种编程习惯,好吧,这个我们就不纠结了)需要一个专门生产pizza的类SimplePizzaFactory,把生产pizza的任务就交给它了,就像这样:
- /**
- * 专门生产pizza的工厂
- */
- public class SimplePizzaFactory {
- public static Pizza createPizza(String type) {
- Pizza pizza = null;
- if (type.equals("cheese"))
- pizza = new CheesePizza();
- else
- pizza = new GreekPizza();
- return pizza;
- }
- }
好了,得到了想要的pizza,现在接着完成pizza的订单,还需要这样的几个步骤:准备prepare(), 烘烤bake(), 切片cut(), 装盒box(). 这几个方法对所有pizza都是一样的, 所以需要一个pizza的抽象类,这几个方法可以直接在这个抽象类中实现
- public abstract class Pizza {
- public void prepare() {
- System.out.println("prepare");
- description();
- }
- public void bake() {
- System.out.println("bake");
- }
- public void cut() {
- System.out.println("cut");
- }
- public void box() {
- System.out.println("box");
- }
- public abstract void description();//描述pizza
- }
具体的pizza(如cheesePizza)继承这个Pizza这个类,在description方法中添加自己的描述
- public class CheesePizza extends Pizza {
- @Override
- public void description() {
- System.out.println("default NY Cheese Pizza");
- }
- }
其他的我就不写了,现在可以用工厂生产pizza了,这时候orderPizza方法大概就是这样的
- public class PizzaStore {
- public Pizza orderPizza(String type) {
- Pizza pizza = SimplePizzaFactory.createPizza(type);
- pizza.prepare();
- pizza.bake();
- pizza.cut();
- pizza.box();
- return pizza;
- }
- }
你可能会问,这不就是把问题从一个地方搬到了另一个地方吗,问题依然存在啊。是的,我开始也是这样想的,但是仔细想想,这样还是有好处的,如果说其他的类中也需要生产pizza呢?难道你还要在把生产pizza的代码写一遍?用书上表达意思就是简单工厂SimplePizzaFactory的客户可能还有其他的,就是说在其他的类中也需要生产pizza。所以说需要依据具体情况创建对象时简单工厂是一个好的选择。
书上把工厂模式分成了3类(我这里把简单工厂也当作一类),分别是简单工厂,工厂方法和抽象工厂,简单工厂上面已经介绍了,然后看一下工厂方法和抽象工厂
工厂方法
现在pizza店的生意比较好,准备开加盟店了,在纽约和芝加哥等地方开一些加盟店,就有了NYPizzaStore和ChicagoPizzaStore等,可是由于地域差异造成了人们的口味也不同,就是说同一种pizza的风味都不一样,比如纽约的cheesePizza的饼要厚一些,而芝加哥的cheesePizza的饼要薄一些。这样就不能使用简单工厂呢?先不作回答,接着往下看,由于各个地方的pizza的风味不一样,所以让他们自己制造自己的pizza,就在PizzaStore中写一个抽象的制作pizza的方法
- public abstract Pizza createPizza(String type);
然后在orderPizza中调用这个方法就可以得到想要的pizza,原来的简单工厂就可以替换为
- Pizza pizza = createPizza(type);
具体的pizza店(如NYPizzaStore和ChiragoPizzaStore)继承自抽象的pizza店PizzaStore,然后实现各自制作pizza的方法createPizza,比如像这样
NYPizzaStore
- //默认为纽约风味的pizza
- @Override
- public Pizza createPizza(String type) {
- Pizza pizza = null;
- if (type.equals("cheese"))
- pizza = new CheesePizza();
- else
- pizza = new GreekPizza();
- return pizza;
- }
ChiragoPizzaStore
- @Override
- public Pizza createPizza(String type) {
- Pizza pizza = null;
- if (type.equals("cheese")) {
- pizza = new ChicagoCheesePizza();
- }
- else
- pizza = new ChicagoGreekPizza();
- return pizza;
- }
可以测试一下
- ChicagoPizzaStore chicagoPizzaStore = new ChicagoPizzaStore();
- chicagoPizzaStore.orderPizza("cheese");
可以看到,不同pizza店生产不同的pizza,都是依靠public abstract Pizza createPizza(String type);这个方法,能够生产不同的pizza,就像一个工厂一样。所以把这个方法称为工厂方法。工厂方法都是要返回一个具体的产品的。
工厂方法模式的定义
定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,工厂方法把类的实例化推迟到子类。
还没有完,可以看到,2个具体的pizza店都是负责创建pizza的,于是我就想能不能把这2个pizza店创建pizza的动作交给一个简单工厂来处理,就像这样
- public class NoSimplePizzaFactory {
- //通过pizza店clazz来区分地域
- public static Pizza createPizza(Class<? extends PizzaStore> clazz, String type) {
- if (clazz == null)
- throw new NullPointerException("address不能为空");
- if (StringUtils.isNullOrEmpty(type))
- throw new NullPointerException("type不能为空");
- if (clazz.getSimpleName().equals(NYPizzaStore.class.getSimpleName())) {
- if (type.equals("cheese"))
- return new CheesePizza();
- else
- return new GreekPizza();
- } else if (clazz.getSimpleName().equals(ChicagoPizzaStore.class.getSimpleName())) {
- if (type.equals("cheese"))
- return new ChicagoCheesePizza();
- else
- return new ChicagoGreekPizza();
- } else
- return null;
- }
- }
于是在具体的pizza店中调用制作pizza的方法就可以得到pizza了
- NoSimplePizzaFactory.createPizza(getClass(), type);
再来看一下刚才提出的问题:能不能在抽象类PizzaStore中使用简单工厂来制作各地区不同的pizza?答案是可以的,因为我们现在有了一个简单工厂NoSimplePizzaFactory(简单工厂不简单啊),它可以生产各地区不同的pizza。所以来一个重载的orderPizza方法或把原来的orderPizza方法修改一下,就像这样
- public Pizza orderPizza(Class<? extends PizzaStore> clazz, String type) {
- Pizza pizza = NoSimplePizzaFactory.createPizza(clazz, type);
- pizza.prepare();
- pizza.bake();
- pizza.cut();
- pizza.box();
- return pizza;
- }
这样就可以制作不同地区不同的pizza了。
现在pizza店的生意越来越好了,随机而来的问题是制作pizza的原料供不应求,而且各个地区的pizza所用的原料还可能不同,如图
为了解决原料供不应求和各地制作pizza所用原料不同的问题,我们可以在每个地区都建一座原料工厂。来供应本地区pizza店所需要的原料。新建一个抽象的原料工厂PizzaIngredientFactory,里面有制造原料的方法,比如:
- Dough createDough();
- Sauce createSauce();
这个工厂是一个接口,然后2个具体的原料工厂ChicagoPizzaIngredientFactory和NYPizaaIngredientFactory实现它,不同的工厂制作不同的原料,
ChicagoPizzaIngredientFactory
- @Override
- public Dough createDough() {
- return new ChicagoDough();
- }
- @Override
- public Sauce createSauce() {
- return new ChicagoSauce();
- }
NYPizaaIngredientFactory
- @Override
- public Dough createDough() {
- return new NYDough();
- }
- @Override
- public Sauce createSauce() {
- return new NYSauce();
- }
现在在pizza中加入这些原料,也就是在Pizza类中加入原料属性
- Dough dough; //面团
- Sauce sauce; //酱料
大家还可以自己添加其他的原料属性,这些原料是工厂生产的,所以把工厂也放在Pizza类中
- private PizzaIngredientFactory ingredientFactory;
- public Pizza(PizzaIngredientFactory ingredientFactory) {
- this.ingredientFactory = ingredientFactory;
- }
然后在具体的pizza中添加对应的构造方法,然后在准备prepare阶段生产原料,所以prepare方法修改一下
- public void prepare() {
- System.out.println("prepare");
- description();
- if (ingredientFactory != null) {
- dough = ingredientFactory.createDough();
- sauce = ingredientFactory.createSauce();
- System.out.println(dough.getName());
- System.out.println(sauce.getName());
- }
- }
然后在简单工厂NoSimplePizzaFactory中就可以使用原料工厂了,然后在该类中引入原料工厂
- private static PizzaIngredientFactory chicagoPizzaIngredientFactory = new ChicagoPizzaIngredientFactory();
- private static PizzaIngredientFactory nyPizaaIngredientFactory = new NYPizaaIngredientFactory();
然后创建pizza是注入工厂,修改其中的即可
- if (clazz.getSimpleName().equals(NYPizzaStore.class.getSimpleName())) {
- if (type.equals("cheese"))
- return new CheesePizza();
- else
- return new GreekPizza(nyPizaaIngredientFactory);
测试一下,你会发现可以通过工厂获得不同的pizza。本例只为说明工厂模式,所以还有不足之处,例如,一旦确定了pizza的区域和种类,通过orderPizza方法得到的pizza就是确定的,并不能在动态的改变制作pizza的原料,我的思路:只要控制原料工厂就可以在制作pizza时动态的改变原料。
从本例可以看到,pizza原料工厂就是一个抽象工厂
抽象工厂模式的定义
提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
工厂方法是不是潜伏在抽象工厂里面
可以看到PizzaIngredientFactory接口中有2个制造原料的抽象方法,这2个方法的作用是生产原料,所以这2个是工厂方法。
小结
简单工厂专职创建对象
工厂方法是一个生产产品的抽象方法,具体生产什么产品由具体的子类决定
抽象工厂用于制造产品的“家族”,具体产品家族由具体工厂决定,抽象工厂里的方法通常是工厂方法
读headFirst设计模式 - 工厂模式的更多相关文章
- 读headFirst设计模式 - 策略模式
有些人已经解决你的问题了 什么是设计模式?我们为什么要使用设计模式?怎样使用?按照书上的说法和我自己的理解,我认为是这样的:我们遇到的问题其他开发人员也遇到过,他们利用他们的智慧和经验将问题解决了,把 ...
- .NET设计模式: 工厂模式
.NET设计模式: 工厂模式(转) 转自:http://www.cnblogs.com/bit-sand/archive/2008/01/25/1053207.html .NET设计模式(1): ...
- 【设计模式】Java设计模式 -工厂模式
[设计模式]Java设计模式 -工厂模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 目 ...
- 10.Java设计模式 工厂模式,单例模式
Java 之工厂方法和抽象工厂模式 1. 概念 工厂方法:一抽象产品类派生出多个具体产品类:一抽象工厂类派生出多个具体工厂类:每个具体工厂类只能创建一个具体产品类的实例. 即定义一个创建对象的接口(即 ...
- [Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式
系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...
- javascript 设计模式-----工厂模式
所谓的工厂模式,顾名思义就是成批量地生产模式.它的核心作用也是和现实中的工厂一样利用重复的代码最大化地产生效益.在javascript中,它常常用来生产许许多多相同的实例对象,在代码上做到最大的利用. ...
- JavaScript设计模式——工厂模式
工厂模式:是一种实现“工厂”概念的面上对象设计模式.实质是定义一个创建对象的接口,但是让实现这个接口的类来决定实例化哪个类.工厂方法让类的实例化推迟到子类中进行.创建一个对象常常需要复杂的过程,所以不 ...
- 学习:java设计模式—工厂模式
一.工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的. 工厂模式在<Java与模式>中分为三类: 1)简单工厂模式(Simple Facto ...
- 设计模式——工厂模式 (C++实现)
软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径.设计模式中运用了面向对象编程语言的重要特性:封装.继承.多态,真正领悟设计模式的精髓是可能一个漫长的过程,需要大量实践经验的积累. ...
随机推荐
- PyQt5多点触控写字板实现及困惑
Qt支持程序多点触控,就想使用PyQt5做一个触控画板,经过几番周折,查阅了TouchEvent官方文档,又参考了一篇QT for Android的例子,采用eventfilter过滤器来识别触屏事件 ...
- 搜索引擎的缓存(cache)机制
什么是缓存? 在搜索领域中,所谓缓存,就是在高速内存硬件设备上为搜索引擎开辟一块存储区,来存储常见的用户查询及其结果,并采用一定的管理策略来维护缓存区内的数据.当搜索引擎再次接收到用户的查询请求时,首 ...
- ERROR 1045 (28000): Access denied for user xxx & ERROR 1449 (HY000): The user specified as a definer xxx does not exists
今天在一个修改过权限的MySQL数据库遇到了"ERROR 1045 (28000): Access denied for user 'xxx'@'xxx.xxx.xxx.xxx' (usin ...
- TOE(TCP/IP Offload Engine)网卡与一般网卡的区别
TCP减压引擎,第一次听说这个名词,但是并不是一个新的概念了,若干年前听说过设备厂商在研究在FPGA之中实现TCP Stack,但是后来没有听到任何的产品出来,应该是路由设备to host的traff ...
- 【mysql】mysql基本操作
mysql基本操作 1.mysql表复制 mysql 表结构的复制 create table t2 like t2 mysql 表数据的复制 insert into t2 select * from ...
- Exception sending context initialized event to listener instance of class
1.错误描述 严重:Exception sending context initialized event to listener instance of class org.springframew ...
- Linux显示已经挂载的分区列表
Linux显示已经挂载的分区列表 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ df -h 文件系统 容量 已用 可用 已用% 挂载点 /dev/sda8 1 ...
- windows下键盘常用快捷键整理
以下快捷键均在win7环境下测试有效: 声明:本博文由多篇博文经实测整理而出. win键相关的快捷键多用于桌面场景,如开起资源管理器.切换任务窗口.最大化最小化窗口等等. 场景一: 1. 任何情况下想 ...
- js call的方法
call 方法 请参阅 应用于:Function 对象 要求 版本 5.5 调用一个对象的一个方法,以另一个对象替换当前对象. call([thisObj[,arg1[, arg2[, [,.argN ...
- 一些比较隐秘的OJ的网址
( 1 )COGS:cogs.pro:8080 ( 2 )AGC:agc012.contest.atcoder.jp ( 3 )CS Acaemy:csacademy.com ( 4 )JoyOI:w ...