从BWM生产学习工厂模式
工厂模式应用非常之广,在JDK
底层源码以及各大主流框架中随处可见,一般以Factory
结尾命名的类,比如Mybatis
中的SqlSessionFactory
,Spring
中的BeanFactory
等,都是工厂模式的典型代表。
一、简单工厂模式
1.1 概念
简单工厂模式又称为静态工厂模式,属于设计模式中的创建型模式。简单工厂模式通过对外提供一个静态方法来统一为类创建实例,目的是实现类与类之间解耦:客户端不需要知道这个对象是如何被穿创建出来的,只需要调用简单工厂模式的方法来统一创建就可以了,从而明确了各个类的职责。
1.2 示例
简单工厂模式,以生产汽车轮胎为例。
1.2.1 实体类
- 轮胎通用属性
public class Tire {
/**
* 通用属性
*/
private String common;
}
- 奔驰车轮胎
包含通用属性外还有自己的特有属性
public class TireForBenz extends Tire{
Tire tire;
/**
* 特有属性
*/
private String benz;
public TireForBenz() {
this.benz = "得到 Benz 轮胎";
}
@Override
public String toString() {
return "["+this.benz +"]";
}
}
- 宝马车轮胎
包含通用属性外还有自己的特有属性
public class TireForBwm extends Tire{
Tire tire;
/**
* 特有属性
*/
private String bwm;
public TireForBwm() {
this.bwm = "得到 Bwm 轮胎";
}
@Override
public String toString() {
return "["+this.bwm +"]";
}
}
1.2.2 生产工艺
- 生产轮胎的抽象方法,各个产线有自己的方式生产
public interface TireFactory {
Tire produceTire();
}
- 奔驰汽车轮胎产线
重写生产轮胎的方法返回奔驰型轮胎。
public class BenzTireFactory implements TireFactory {
/**
* 生产奔驰轮胎
*/
@Override
public Tire produceTire() {
System.out.println("奔驰轮胎生产中。。。");
return new TireForBenz();
}
}
- 宝马汽车轮胎产线
重写生产轮胎的方法返回宝马型轮胎。
public class BwmTireFactory implements TireFactory {
/**
* 生产宝马轮胎
*/
@Override
public TireForBwm produceTire() {
System.out.println("宝马轮胎生产中。。。");
return new TireForBwm();
}
}
1.2.3 轮胎工厂类
通过传入的品牌名称调用相应产线生产相应品牌的轮胎
public class SimpleFactoryMode {
public static TireFactory produceCar(String name) {
if ("BenzTireFactory".equals(name)) {
return new BenzTireFactory();
}
if ("BwmTireFactory".equals(name)) {
return new BwmTireFactory();
}
return null;
}
}
1.2.4 测试
客户端通过工厂类获取实例对象。
- 测试方法
@Test
public void simpleFactoryModeTest() {
// 造奔驰轮胎
TireFactory benz = SimpleFactoryMode.produceCar("BenzTireFactory");
if (null != benz) {
benz.produceTire();
}else {
System.out.println("工厂暂时无法生产奔驰轮胎");
}
// 造宝马轮胎
TireFactory bwm = SimpleFactoryMode.produceCar("BwmTireFactory");
if (null != bwm) {
bwm.produceTire();
}else {
System.out.println("工厂暂时无法生产宝马轮胎");
}
// 造本田汽轮胎(工厂无该方法)
TireFactory honda = SimpleFactoryMode.produceCar("Honda");
if (null != honda) {
honda.produceTire();
}else {
System.out.println("工厂暂时无法生产本田轮胎");
}
}
- 结果
奔驰轮胎生产中。。。
宝马轮胎生产中。。。
工厂暂时无法生产本田轮胎
该方式确实能完成不同品牌的轮胎生产,但是,有个问题:方法参数是字符串,可控性有待提升。
1.3 简单工厂模式优化
不要通过传入的字符串来判断需要创建对象,而是客户端想要创建什么对象,只需要传入具体的实现类就可以了,然后通过
Java
的反射来创建对象。
public static TireFactory produceCar(Class<? extends TireFactory> clazz) {
try {
// 通过Java的反射来创建对象
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
每次创建对象都是通过反射来创建的,所以在性能上是有一定的损耗。
- 测试
public void simpleFactoryModeUpgradeTest() {
// 造奔驰轮胎
TireFactory benzTire = SimpleFactoryMode.produceCar(BenzTireFactory.class);
TireForBenz benz = (TireForBenz) benzTire.produceTire();
System.out.println(benz.toString());
// 造宝马轮胎
TireFactory bwmTire = SimpleFactoryMode.produceCar(BwmTireFactory.class);
TireForBwm bwm = (TireForBwm) bwmTire.produceTire();
System.out.println(bwm.toString());
}
- 结果
奔驰轮胎生产中。。。
[得到 Benz 轮胎]
宝马轮胎生产中。。。
[得到 Bwm 轮胎]
1.4 小结
简单工厂模式确实在一定程度上实现代码的解耦,而这种解耦的特点在于,这种模式将对象的创建和使用分离。这种模式的本质在于通过一个传入的参数,做if...else
判断,来达到返回不同类型对象的目的。缺点也很明显,不符合开闭原则(比如新增一个保时捷轮胎的生产,除了需要增加实体和生产方法,还需要修改工厂类SimpleFactoryMode.java
)。因此,如果需要增加新的类型,就不得不去修改原来的代码,违反开闭原则。
- 简单工厂模式优点:
- 简单优化了软件体系结构,明确了各自功能模块的职责和权利;
- 通过工厂类,外界不需要直接创建具体产品对象,只需要负责消费,不需要关心内部如何创建对象。
- 简单工厂模式缺点:
- 改进前的简单工厂模式全部创建逻辑都集中在一个工厂类中,能创建的类只能是考虑到的,如果需要添加新的类,就必须改变工厂类了;
- 改进前的简单工厂模式随着具体产品的不断增多,可能会出现共产类根据不同条件创建不同实例的需求,这种对条件的判断和对具体产品类型的判断交错在一起,很难避免功能模块的蔓延,对系统的维护和扩展不利;
- 改进后的简单工厂模式主要是使用反射效率会低一些。
二、工厂方法模式
- 简单工厂模式之所以违反开闭原则,关键在于什么?
那就是它把所有对象的创建都集中在同一个工厂类里面了,因此,当新增一个新对象时,必然会需要修改这个共享工厂类,违反开闭原则自然不可避免。
- 解决方案
既然问题关键在于,所有对象的创建都跟这个唯一的工厂类耦合了,那我每个对象各自都配置一个单独的工厂类,这个工厂类只创建各自类型的对象,那这样不就解决耦合的问题了吗?
2.1 概念
工厂方法模式是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。在工厂方法模式中用户只需要关心所需产品对应的工厂,无须关心创建细节,而且加入新的产品符合开闭原则。
2.2 示例
工厂方法模式,以生产发动机为例。
2.2.1 实体
- 发动机的通用属性
public class Engine {
/**
* 型号
*/
private String common;
}
- 奔驰发动机
包含通用属性外还有自己的特有属性
public class EngineForBenz extends Engine{
Engine engine;
/**
* 特有属性
*/
private String benz;
public EngineForBenz() {
this.benz = "得到 Benz 发动机";
}
@Override
public String toString() {
return "["+this.benz +"]";
}
}
- 宝马发动机
包含通用属性外还有自己的特有属性
public class EngineForBwm extends Engine{
Engine engine;
/**
* 特有属性
*/
private String bwm;
public EngineForBwm() {
this.bwm = "得到 Bwm 发动机";
}
@Override
public String toString() {
return "["+this.bwm +"]";
}
}
2.2.2 生产工艺(发动机的工厂类)
- 抽象工厂类,定义生产发动机的方法,各个产线自己的去实现
public interface EngineFactory<T> {
Engine produceEngine();
}
- 创建奔驰子工厂,实现其的工艺
public class BenzEngineFactory implements EngineFactory<EngineForBenz> {
/**
* 生产奔驰发动机
*/
@Override
public Engine produceEngine() {
System.out.println("奔驰发动机生产中。。。");
return new EngineForBenz();
}
}
- 创建宝马子工厂,实现其的工艺
public class BwmEngineFactory implements EngineFactory<EngineForBwm> {
/**
* 生产宝马发动机
*/
@Override
public Engine produceEngine() {
System.out.println("宝马发动机生产中。。。");
return new EngineForBwm();
}
}
2.2.3 测试
@Test
public void factoryModeTest() {
// 造奔驰发动机
EngineFactory car = new BenzEngineFactory();
EngineForBenz benz = (EngineForBenz) car.produceEngine();
System.out.println(benz.toString());
// 造宝马发动机
EngineFactory carFactory = new BwmEngineFactory();
EngineForBwm bwm = (EngineForBwm) carFactory.produceEngine();
System.out.println(bwm.toString());
}
- 结果
奔驰发动机生产中。。。
[得到 Benz 发动机]
宝马发动机生产中。。。
[得到 Bwm 发动机]
2.3 小结
工厂方法模式轻松解决了简单工厂模式的问题,符合开闭原则。在上面例子中,当需要新增一个保时捷汽车,此时只需要提供一个对应的EngineForBSJ.java
实现produceEngine()
方法即可,对于原先代码再不需要做任何修改。
但是每个类型的对象都会有一个与之对应的工厂类。如果对象的类型非常多,意味着会需要创建很多的工厂实现类,造成类数量膨胀,对后续维护带来一些麻烦。
- 缺点
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节;
- 一个类通过其子类来指定创建哪个对象。
- 缺点
- 类的个数容易过多,增加复杂度;
- 增加了系统的抽象性和理解难度。
三、抽象工厂模式
抽象工厂模式出现,就是为了解决上述工厂方法模式存在的问题,可以看成是工厂方法模式的升级。
3.1 背景
- 类数量膨胀的情景
工厂方法模式创建的对象其实归根到底都是同一类对象。以汽车生产为例,无论是轮胎还是发动机,都是汽车生产的一部分,都是属于汽车生产的过程。如下图:
由上图我们可以发现,虽然分为奔驰车和宝马车,但是从工厂方法角度,他们都属于汽车这一类别,这就导致了需要单独为每一个零件指定各自的工厂类,从而导致了类数量膨胀的问题。
- 解决方案
既然这样,我们可以把每类汽车指定一个工厂,然后再让不同产线去生产他需要的产品,如下图
这样当每一类物品组件数量特别多,可以把它称为产品族。抽象工厂模式就是为了创建一系列以产品族为单位的对象,这样在需要创建大量系列对象时可以大大提高开发效率,降低维护成本。
3.2 示例
因为奔驰轮胎/宝马轮胎/奔驰发动机/宝马发动机的实体在前面已经创建过,这里就直接用了。
3.2.1 汽车工厂类(顶层抽象工厂类)
该类已包含轮胎/发动机生产,具体实体键一/二中相关实体。
public interface CarFactory {
/**
* 准备生产
*/
void init();
/**
* 生产轮胎
* @return
*/
Tire produceTire();
/**
* 生产发动机
* @return
*/
Engine produceEngine();
}
3.2.2 奔驰汽车产品族(奔驰汽车工厂类)
public class BenzCarFactory implements CarFactory{
@Override
public void init() {
System.out.println("----------------------- 奔驰汽车准备生产 -----------------------");
}
@Override
public Tire produceTire() {
System.out.println("正在生产奔驰轮胎");
return new TireForBenz();
}
@Override
public Engine produceEngine() {
System.out.println("正在生产奔驰发动机");
return new EngineForBenz();
}
}
3.2.2 宝马汽车产品族(宝马汽车工厂类)
public class BwmCarFactory implements CarFactory{
@Override
public void init() {
System.out.println("----------------------- 宝马汽车准备生产 -----------------------");
}
@Override
public Tire produceTire() {
System.out.println("正在生产宝马轮胎");
return new TireForBwm();
}
@Override
public Engine produceEngine() {
System.out.println("正在生产宝马发动机");
return new EngineForBwm();
}
}
3.2.3 测试
@Test
public void abstractFactoryModeTest() {
// 生产奔驰整车的零部件
CarFactory benz = new BenzCarFactory();
benz.init();
TireForBenz benzTire = (TireForBenz) benz.produceTire();
System.out.println(benzTire.toString());
EngineForBenz benzEngine = (EngineForBenz) benz.produceEngine();
System.out.println(benzEngine.toString());
// 生成宝马整车的零部件d
CarFactory bwm = new BwmCarFactory();
bwm.init();
TireForBwm bwmTire = (TireForBwm) bwm.produceTire();
System.out.println(bwmTire.toString());
EngineForBwm bwmEngine = (EngineForBwm) bwm.produceEngine();
System.out.println(bwmEngine.toString());
}
- 结果
----------------------- 奔驰汽车准备生产 -----------------------
正在生产奔驰轮胎
[得到 Benz 轮胎]
正在生产奔驰发动机
[得到 Benz 发动机]
----------------------- 宝马汽车准备生产 -----------------------
正在生产宝马轮胎
[得到 Bwm 轮胎]
正在生产宝马发动机
[得到 Bwm 发动机]
3.3 思考
既然说抽象工厂模式是工厂方法模式的升级,那到底升级了啥?
其实是由原来的单一产品的生产升级成为了系列产品的生产。设想一下,假设上面汽车的例子中,每一品牌汽车中就只生产一种部件,比如就只生产发动机,不生产轮胎等其他组件了,如下图
发现了什么没有?抽象工厂模式居然转变为我们之前讲过的工厂方法模式了!换句话说,当你的产品族中只生产一种产品的时候,你的抽象工厂模式其实已经退化为工厂方法模式了。反过来说,当生产多种产品时,工厂方法模式就进化为抽象工厂模式。
3.4 小结
抽象工厂模式在创建大量系列对象时可以大大提高开发效率,就是为生产产品族而生的,而对于生产单一产品却无能为力。
如果需要添加一个新的产品族,那就简单了,比如新增一个保时捷汽车,那就只需要添加一个保时捷汽车的工厂实现类就好了,并不会对原有的代码造成任何影响。
但是,如果假设在汽车中,我需要再加一个组件,比如倒车影像,怎么操作?你需要在CarFactory
接口中添加返回倒车影像对象的接口。这一加不得了了......所有品牌汽车实现类全部需要修改并追加该方法的实现,违反了开闭原则。
- 抽象工厂模式优点:
创建大量系列对象时可以大大提高开发效率,降低维护成本。
- 抽象工厂模式缺点:
- 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口;
- 增加了系统的抽象性和理解难度。
四、总结
4.1 如何选择
工厂模式的三种形式都介绍完了,那我们实际开发中该如何去选择呢?
- 从设计原则来说,简单工厂模式不符合开闭原则。但是很神奇,在实际场景中,简单工厂模式确实用的最多的。
- 工厂方法模式是专门用于解决单个对象创建工作,本身模式没问题,也符合开闭原则。但是存在工厂类数量膨胀的问题。如果需要创建的工厂类不是很多,是一种不错的选择。
- 抽象工厂模式天生就是为生产产品族而生的。所以如果你需要创建的对象非常之多,但是对象之间存在明显产品族特征,那么这个时候用抽象工厂模式非常合适。
4.2 示例源码
4.3 技术交流
从BWM生产学习工厂模式的更多相关文章
- 基于go语言学习工厂模式
工厂模式 简单工厂模式(Simple Factory) 定义 优点 缺点 适用范围 代码实现 工厂方法模式(Factory Method) 定义 优点 缺点 适用范围 代码实现 抽象工厂模式(Abst ...
- IOS之Objective-C学习 工厂模式
工厂模式在父类里声明(可实现)创建对象的一个接口,让子类决定实例化哪个类,也就是说让一个类的实例化延迟到子类中生产. 工厂模式一般用于在不同地方创建对象和项目部署依赖多个数据库的时候. 工厂模式有三种 ...
- [javaSE] 看知乎学习工厂模式
factory的“本质”就是根据不同的输入创建出不同类型的对象. 引入factory的原因就是你需要根据不同的输入创建不同类型的对象. 简单工厂模式相当于是一个工厂中有各种产品,创建在一个类中,客户无 ...
- C++模式学习------工厂模式
工厂模式属于创建型模式,大致可以分为简单工厂模式.抽象工厂模式. 简单工厂模式,它的主要特点是需要在工厂类中做判断,从而创造相应的产品. enum PTYPE { ProdA = , ProdB = ...
- 设计模式学习——工厂模式(Factory Pattern)
1.有一个工厂,专门生产不同品牌的汽车.当有人需要从此工厂提货的时候,只需要告诉他,要什么品牌的,就可以了,并不关心这些车是怎么生产出来的. 2.以上方式,如果增加品牌的时候,也要修改工厂,有点麻烦. ...
- 由XML解析学习工厂模式
代码段1: startupData = new StartupData(); /* 设定自定义的MyHandler给XMLReader */ StartupXMLHandler startupData ...
- java模式-工厂模式
今天在学习工厂模式,从最简单的简单工厂模式开始. 我们现在需要通过工厂Factory生产A,B两款产品(都是产品,实现了接口Product). 产品A: public class A implemen ...
- 工厂模式(Factory Patter)
1.工厂模式简介 工厂模式属于创建型模式,是专门用来创建对象的模式,抽象了实例化的过程.工厂模式分为 : 工厂方法模式.抽象工厂模式. 在学习工厂方法模式.抽象工厂之前,首先先要了解一下简单工厂模式, ...
- 设计模式(C#)——02简单工厂模式
推荐阅读: 我的CSDN 我的博客园 QQ群:704621321 工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来.通俗来说,你只关心怎么用,不用关心怎么做 ...
随机推荐
- Tkinter 之Entry输入框标签
一.参数说明 语法 作用 Entry(root,width=20) 组件的宽度(所占字符个数) Entry(root,fg='blue') 前景字体颜色 Entry(root,bg='blue') 背 ...
- 腾讯云上面部署PHP运行环境
现在云服务器已经很普及了,其价格.安全优势等成为不少开发者的首选.本人由于兴趣爱好,从朋友那边借了一个过来玩了两天,下面就分享整个部署流程吧. 1. 先到腾讯云官网购买服务器,这边就不演示.很简单,跟 ...
- 【XSY2131】【BZOJ1857】【SCOI2010】传送带
Description 题目描述: 在一个二维平面上有两条传送带,每一条传送带可以看成是一条线段.两条传送带分别为线段AB和线段CD.小y在AB上的移动速度为P,在CD上的移动速度为Q,在平面上的移动 ...
- [开源]基于goapp+xterm实现webssh-网页上的SSH终端(golang)
简析 基于goapp+xterm实现webssh-网页上的SSH终端. 开源地址见文末. 特性 在网页上实现一个SSH终端.从而无需Xshell之类的模拟终端工具进行SSH连接. 可以对交互命令进行审 ...
- 在线WEB开发编辑器,edt.df5d.com
在线WEB开发编辑器,http://edt.df5d.com 本地服务端下载 : https://pan.baidu.com/s/11SlcoU_D-KbzGFbs-_9Dpg 即可加载本地磁盘,也可 ...
- 2018年7月份JAVA开源软件TOP3
微信开发 Java SDK Weixin Java Tools 评分: 9.6 介绍: 信开发 Java 开发工具包(SDK),支持包括微信支付.微信开放平台.小程序.企业号/企业微信.公众号(包括服 ...
- SAP HCM 评估路径
一.评估路径的配置方法: 1)IMG菜单路径:人事管理-〉组织管理-〉基本设置-〉维护评估路径: 2)首先定义评估路径的名称和描述,客户自定义的评估路径的名称编码可以采用字母数字编码,最大长度是八 ...
- 使用 Scrapy 爬取去哪儿网景区信息
Scrapy 是一个使用 Python 语言开发,为了爬取网站数据,提取结构性数据而编写的应用框架,它用途广泛,比如:数据挖掘.监测和自动化测试.安装使用终端命令 pip install Scrapy ...
- Redis实战--使用Jedis实现百万数据秒级插入
echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!!! 当我们 ...
- MathType转Word公式(OMML)
背景 由于之前个人喜欢在Word里做笔记,而有很多笔记里存在着大量的公式.在早期,由于对Word自身的公式的不理解,所以便使用了MathType这个工具来编写公式.但是现在本人已经转战到LatTeX了 ...