【设计模式 - 1】之工厂模式(Factory)
1、模式简介
工厂模式的作用:
- 工厂模式解决的是“使用new关键字获取对象造成松耦合”的问题。
- 工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
工厂模式可以分为三类:
- 简单工厂模式(SimpleFactory)
- 工厂方法模式(FactoryMethod)
- 抽象工厂方法(AbstractFactory)
- 注:这里将简单工厂模式看作工厂方法模式的一个特例
工厂方法模式:
- 一个抽象产品类,可以派生出多个具体产品类;
- 一个抽象工厂类,可以派生出多个具体工厂类;
- 每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
- 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类;
- 一个抽象工厂类,可以派生出多个具体工厂类;
- 每个具体工厂类可以创建多个具体产品类的实例。
模式区别:
- 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个;
- 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
2、简单工厂模式
2.1、简介
简单工厂模式的工作流程如上图所示:汽车工厂中有创建汽车的方法,用户只需要将汽车的型号作为参数传到工厂中(告诉工厂自己想要造哪种型号的汽车),工厂会根据参数创建不同的汽车对象后返回给用户。
可见,在工厂模式中,用户不需要知道汽车具体是如何制造的,他们只需要告诉工厂自己想要哪个型号的汽车,其他的工作交给工厂去做即可。
简单工厂模式主要包括三部分:
- 工厂角色:相当于上图中的FactoryBMW,具有一定的判断逻辑;
- 抽象产品角色:相当于上图中的BMW,一般是具体商品继承的父类后实现的接口;
- 具体产品角色:相当于上图中的BMW532和BMW320,是抽象产品类的子类或实现类。
2.2、分析
设计模式的最终目标:让程序“对扩展开放,对修改关闭(开闭原则)”。
当有新的车型加入工厂生产线后,不仅需要新加一个新的车型类,还需要在工厂中改变车型的判断逻辑,这是不符合开闭原则的。
要解决这个问题,需要工厂方法模式。
2.3、代码
程序目录架构:
抽象汽车产品类BMW.java类中的代码:
public abstract class BMW {
public abstract void introduce();
}
具体汽车产品类BMW523.java和BMW320.java中的代码(以BMW523.java为例):
public class BMW523 extends BMW {
public BMW523() {
}
@Override
public void introduce() {
System.out.println("这是一辆BMW523型号车");
}
}
汽车工厂类FactoryBMW.java中的代码:
public class FactoryBMW {
public static final int TYPE_BMW523 = 1;
public static final int TYPE_BMW320 = 2;
public static BMW createBMW(int type) {
switch (type) {
case TYPE_BMW523:
return new BMW523();
case TYPE_BMW320:
return new BMW320();
}
return null;
}
}
测试类Test.java中的代码:
public class Test {
private static BMW bmw;
public static void main(String[] args) {
Scannerscanner = new Scanner(System.in);
System.out.println("请选择要生产的汽车的型号:");
System.out.println("1、BMW 523");
System.out.println("2、BMW 320");
int type = scanner.nextInt();
bmw = FactoryBMW.createBMW(type);
if (bmw != null) {
bmw.introduce();
}else {
System.out.println("没有这个车型!");
}
scanner.close();
}
}
运行结果:
3、工厂方法模式
3.1、简介
工厂方法模式的工作流程如上图所示:将简单工厂模式中的工厂类FactoryBMW定义成接口,每种车型都定义一个新的工厂类,用来专门生产BMW类的某个车型子类。这样,工厂可以扩展,就不需要修改原来的代码了。
工厂方法模式主要包括四部分:
- 抽象工厂角色:相当于上图中的FactoryBMW,是所有具体工厂必须实现或继承的接口或父类;
- 具体工厂角色:相当于上图中的FactoryBMW320和FactoryBMW523,它们含有具体的与业务逻辑有关的代码,用来创建具体的产品对象;
- 抽象产品角色:相当于上图中的BMW,是具体产品的接口或父类;
- 具体产品角色:相当于上图中的BMW523和BMW320,是具体的产品类。
3.2、分析
相比于简单工厂模式用一个工厂来创建所有车型,工厂方法模式将工厂生产汽车的方法下放到其子类中,分担了工厂类承担的压力。
当有新的车型加入工厂生产线时,只需要创建具体的工厂类和车型类即可,不需要修改现有的代码,让程序变得更加灵活,可见,工厂方法模式是符合开闭原则的。
但是工厂方法模式也有一定的弊端:这种模式使得类的数量成倍增长,当有很多车型时,就会有很多工厂类来生产这些车型,这不是我们所希望的。这个问题的一个解决方案是将工厂方法模式与简单工厂模式结合使用,创建方法比较相似的车型合并到一个简单工厂中创建,创建方法差别较大的车型使用工厂方法模式分到不同的工厂中创建。
工厂方法模式已经比较完美的对对象进行了封装,但也不是意味着我们要对程序中的所有对象都采用工厂方法模式。
在以下情况下可以考虑使用工厂方法模式:
- 当客户不需要知道要使用的对象的创建过程时;
- 当客户要使用的对象存在变动的可能,或者根本就不知道要使用哪个对象时。
3.3、代码
程序目录框架:
抽象的工厂类FactoryBMW.java中的代码:
public abstract class FactoryBMW {
public abstract BMW createBMW();
}
具体的工厂类FactoryBMW320.java和FactoryBMW523.java中的代码(这里以FactoryBMW523.java为例):
public class FactoryBMW523 extendsFactoryBMW {
@Override
public BMW createBMW() {
return new BMW523();
}
}
测试类Test.java中的代码:
public class Test {
private static BMW bmw;
private static FactoryBMW factory;
public static void main(String[] args) {
Scannerscanner = new Scanner(System.in);
System.out.println("请选择要生产的汽车的型号:");
System.out.println("1、BMW 523");
System.out.println("2、BMW 320");
int type = scanner.nextInt();
switch (type) {
case 1:
factory = new FactoryBMW523();
break;
case 2:
factory = new FactoryBMW320();
break;
default:
factory = null;
break;
}
if (factory != null) {
bmw = factory.createBMW();
bmw.introduce();
}else {
System.out.println("没有这个车型!");
}
scanner.close();
}
}
运行结果如下图:
3.4、扩展
从上面的代码我们可以看到:工厂方法模式其实并没有做到避免代码的修改,而是将原来需要在工厂中进行的逻辑判断拿到了测试类中进行,这是因为我们仍然无法判断具体需要调用哪个工厂。面对这种情况,我们可以使用反射机制来在一定程度上进行解决。具体代码如下:
public class Test {
private static BMW bmw;
private static final String[] classes = {
"com.itgungnir.designpattern.factory.factorymethod.FactoryBMW523",
"com.itgungnir.designpattern.factory.factorymethod.FactoryBMW320" };
public static void main(String[] args) {
Scannerscanner = new Scanner(System.in);
System.out.println("请选择要生产的汽车的型号:");
System.out.println("1、BMW 523");
System.out.println("2、BMW 320");
int type = scanner.nextInt();
try {
Class<?>factory = Class.forName(classes[type - 1]);
MethodcreateBMW = factory.getMethod("createBMW");
bmw = (BMW) createBMW.invoke(factory.newInstance());
}catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (NoSuchMethodException e) {
e.printStackTrace();
}catch (SecurityException e) {
e.printStackTrace();
}catch (IllegalAccessException e) {
e.printStackTrace();
}catch (IllegalArgumentException e) {
e.printStackTrace();
}catch (InvocationTargetException e) {
e.printStackTrace();
}catch (InstantiationException e) {
e.printStackTrace();
}
if (bmw != null) {
bmw.introduce();
}else {
System.out.println("没有这个车型!");
}
scanner.close();
}
}
这样,当有新的车型和工厂加入到生产线之后,除了创建具体工厂类和具体车型类,只需要在字符串数组classes中加入新创建的具体工厂类的包地址即可。这样可以将代码的改动降到最小。
注:关于反射机制的介绍可以参考:【JAVA - 基础】之反射的原理与应用
4、抽象工厂模式
4.1、简介
抽象工厂模式的工作流程和工厂方法模式的工作流程基本相似,唯一的不同是一个工厂生产的是一个产品的不同组件。下图中,一个具体工厂除了可以生产汽车之外,还可以生产与该型号汽车对应的空调。(本DEMO的前提是每个车型的汽车都有一款对应的空调,一个车型的汽车不能搭载另一个车型的汽车的空调)
抽象工厂模式的组成部分和工厂方法模式的组成部分相同。
4.2、分析
从上图中可以看到,抽象工厂模式的适用场景是系统中存在多个系列的产品(如汽车和空调)。实际上,抽象工厂模式和工厂方法模式的区别就在于需要创建的对象的复杂程度。用这个例子来说,就是当我们只需要生产汽车的时候,就可以选择工厂方法模式;当我们既需要生产汽车又需要生产空调时,就可以选择抽象工厂模式。
用一句话概括抽象工厂方法:给客户端提供一个接口,可以创建多个产品族中的产品对象。
4.3、代码
程序目录框架:
空调抽象类AC.java中的代码:
public abstract class AC {
public abstract void introduce();
}
具体的空调类AC523.java和AC320.java中的代码(这里以AC523.java为例):
public class AC523 extends AC {
@Override
public void introduce() {
System.out.println("BMW523型号的汽车成功搭载了AC523型号的空调");
}
}
抽象的工厂接口Factory.java中的代码:
public interface Factory {
BMWcreateBMW();
ACcreateAC();
}
具体的工厂类Factory523.java和Factory320.java中的代码(这里以Factory523.java为例):
public class Factory523 implements Factory {
@Override
public BMW createBMW() {
return new BMW523();
}
@Override
public AC createAC() {
return new AC523();
}
}
测试类Test.java中的代码:
public class Test {
private static BMW bmw;
private static AC ac;
private static final String[] classes = {
"com.itgungnir.designpattern.factory.abstractfactory.Factory523",
"com.itgungnir.designpattern.factory.abstractfactory.Factory320" };
public static void main(String[] args) {
Scannerscanner = new Scanner(System.in);
System.out.println("请选择要生产的产品的型号:");
System.out.println("1、523型号");
System.out.println("2、320型号");
int type = scanner.nextInt();
try {
Class<?>factory = Class.forName(classes[type - 1]);
MethodcreateBMW = factory.getMethod("createBMW");
MethodcreateAC = factory.getMethod("createAC");
bmw = (BMW) createBMW.invoke(factory.newInstance());
ac = (AC) createAC.invoke(factory.newInstance());
}catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (NoSuchMethodException e) {
e.printStackTrace();
}catch (SecurityException e) {
e.printStackTrace();
}catch (IllegalAccessException e) {
e.printStackTrace();
}catch (IllegalArgumentException e) {
e.printStackTrace();
}catch (InvocationTargetException e) {
e.printStackTrace();
}catch (InstantiationException e) {
e.printStackTrace();
}
if (bmw != null && ac!=null) {
bmw.introduce();
ac.introduce();
}else {
System.out.println("没有这个型号的产品!");
}
scanner.close();
}
}
运行结果如下图所示:
5、后记
这个帖子中的所有例子都很简单,都是直接创建的,这样还不能把工厂模式淋漓尽致的体现出来。设想,如果一个产品的创建需要很多步骤,中间会产生很多很多的对象,而这些对象最终都没有返回(即这些对象对我们最终想要获得的对象只是辅助作用,我们不需要获取这些对象)。在这种情况下,我们如果每获取一个对象都调用这一套方法的话无疑是非常耗时耗力的。
因此,工厂模式不仅对“new”进行了封装,也在一定程度上对对象的创建过程进行了封装。使用工厂模式,可以得到更松耦合、更有弹性的设计。
最后总结一下工厂模式不同类别的定义:
- 工厂方法模式:定义一个创建对象的接口或抽象类,由实现类或子类决定要实例化的类是哪一个。工厂方法模式让类把实例化的代码推迟到子类中进行;
- 抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
最后贴出工厂模式的GitHub地址:【GitHub - Factory】。
【设计模式 - 1】之工厂模式(Factory)的更多相关文章
- 设计模式(一)工厂模式Factory(创建型)
设计模式一 工厂模式Factory 在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的.可是在一些情况下, new操作符直接生成对象会带来一些问题. ...
- 设计模式(一)工厂模式Factory(创建类型)
设计模式一 工厂模式Factory 在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的.可是在一些情况下, new操作符直接生成对象会带来一些问题. ...
- 乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern)
原文:乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factor ...
- 设计模式(一)工厂模式Factory(创建型)(转)
原文链接:http://blog.csdn.net/hguisu/article/details/7505909 设计模式一 工厂模式Factory 在面向对象编程中, 最通常的方法是一个new操作符 ...
- 设计模式之简单工厂模式(Simple Factory)
原文地址:http://www.cnblogs.com/BeyondAnyTime/archive/2012/07/06/2579100.html 今天呢,要学习的设计模式是“简单工厂模式”,这是一个 ...
- 设计模式系列之工厂模式三兄弟(Factory Pattern)
说明:设计模式系列文章是读刘伟所著<设计模式的艺术之道(软件开发人员内功修炼之道)>一书的阅读笔记.个人感觉这本书讲的不错,有兴趣推荐读一读.详细内容也可以看看此书作者的博客https:/ ...
- 设计模式 - 工厂模式(factory pattern) 具体解释
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u012515223/article/details/27081511 工厂模式(factory pa ...
- 桥接模式及C++实现 C++设计模式-AbstractFactory抽象工厂模式
桥接模式及C++实现 桥接模式 先说说桥接模式的定义:将抽象化(Abstraction)与实现化(Implementation)分离,使得二者可以独立地变化. 桥接模式号称设计模式中最难理解的模式之一 ...
- java设计模式---三种工厂模式
工厂模式提供创建对象的接口. 工厂模式分为三类:简单工厂模式(Simple Factory), 工厂方法模式(Factory Method)和抽象工厂模式(Abstract Factory).GOF在 ...
- Jquery如何序列化form表单数据为JSON对象 C# ADO.NET中设置Like模糊查询的参数 从客户端出现小于等于公式符号引发检测到有潜在危险的Request.Form 值 jquery调用iframe里面的方法 Js根据Ip地址自动判断是哪个城市 【我们一起写框架】MVVM的WPF框架(三)—数据控件 设计模式之简单工厂模式(C#语言描述)
jquery提供的serialize方法能够实现. $("#searchForm").serialize();但是,观察输出的信息,发现serialize()方法做的是将表单中的数 ...
随机推荐
- JavaScript 实现触点式弹出菜单插件
之前做项目时经常用到一种触点式弹出菜单或者导航的功能,这个功能的主要应用场景是:web页面中多层分级导航或者子功能目录,但又考虑到页面区域有限,于是就考虑到在鼠标移动到某导航按钮上或者点击时,系统将在 ...
- 哈希表(hashtable)的javascript简单实现
javascript中没有像c#,java那样的哈希表(hashtable)的实现.在js中,object属性的实现就是hash表,因此只要在object上封装点方法,简单的使用obejct管理属性的 ...
- Visual c++ 2012 软件错误
vs2012 未能正确加载"Visual C++ Language Manager Package"包 解决办法 如下图所示: 到官网下载更新即可. http://www.micr ...
- BootStrap Progressbar 实现大文件上传的进度条
1.首先实现大文件上传,如果是几兆或者几十兆的文件就用基本的上传方式就可以了,但是如果是大文件上传的话最好是用分片上传的方式.我这里主要是使用在客户端进行分片读取到服务器段,然后保存,到了服务器段读取 ...
- 写个自动下载安装Ant的shell脚本【一】
#!/bin/bash ###################################################### # file name: install_ant.sh # # f ...
- Servlet 中使用POI生成Excel
使用的是poi3.13 http://mvnrepository.com/artifact/org.apache.poi/poi/3.13 import java.io.IOException; im ...
- Nodejs异步
http://cnodejs.org/topic/4f16442ccae1f4aa2700113b http://cnodejs.org/topic/4f16442ccae1f4aa27001123 ...
- node c#
blogs.msdn.com/b/brunoterkaly/archive/2012/02/22/node-js-socket-programming-with-c-and-javascript.as ...
- OTG线与普通USB线的区别
转自OTG线与普通USB线的区别 USB数据线是我们常见的设备,OTG线作为近年来随着手机行业的快速发展,逐步进入了我们的日常使用范围.OTG线与普通USB线的有什么区别? USB数据线用 ...
- I2C读写时序
1. I2C写时序图: 注意:最后一个byte后,结束标志在第十个CLK上升沿之后: 2. I2C读时序图: 注意:restart信号格式:读操作结束前最后一组clk的最后一个上升沿,主机应发送NAC ...