设计模式 -- 简单工厂模式
写在前面的话:读书破万卷,编码如有神
--------------------------------------------------------------------
主要内容包括:
- 初识简单工厂模式,包括: 定义、结构
- 体会简单工厂模式,包括: 场景问题、不用模式的解决方案、使用模式的解决方案
- 理解简单工厂模式,包括: 典型疑问、认识简单工厂、简单工厂中方法的写法、可配置的简单工厂、简单工厂的优缺点
- 思考简单工厂模式,包括: 简单工厂的本质、何时选用
参考内容:
1、《研磨设计模式》 一书,作者:陈臣、王斌
--------------------------------------------------------------------
1、初识简单工厂
1.1、定义
提供一个创建对象实例的功能,而无须关心具体的实现。被创建的实例的类型可以是接口、抽象类,也可以是具体的类。
1.2、结构和说明
说明:
- Api : 定义客户端所需要的功能接口
- Impl : 具体实现Api的实现类,可能会有多个
- Factory : 工厂,选择合适的实现类来创建Api接口对象
- Client: 客户端,通过Factory来获取Api接口对象,然后面向Api接口编程
--------------------------------------------------------------------
2、体会简单工厂模式
2.1、接口回顾
(1)Java中接口的概念
在Java中接口是一种特殊的抽象类,跟一般的抽象类相比,接口里面的所有方法都是抽象的,接口里面的所属属性都是常量。也就是说,接口里面只有方法定义而没有方法的实现。
(2)接口用来干什么
通常用接口来定义实现类的外观,也就是实现类的行为定义,用来约束实现类的行为,接口相当于一份契约,根据外部应用需要的功能,约定了实现类应该要实现的功能。
(3)接口的思想
根据接口的作用和用途,可以总结为 “封装隔离”。 通常提到的封装是指对数据的封装,但是这里的封装是指"对被隔离体的行为的封装",或者是"对被隔离体的职责的封装",而隔离指的是外部调用和内部实现,外部调用只能通过接口进行调用,外部调用是不知道内部具体实现的,也就是说外部调用和内部实现是被接口隔离开的。
(4)使用接口的好处
只要接口不变,内部实现的变化就不会影响到外部应用,从而使得系统更灵活,具有更好的扩展性和可维护性。
(5)接口和抽象类的选择
a. 优先选用接口
b. 在既要定义子类的行为,又要为子类提供公共的功能时应该选用抽象类
2.2、面向接口编程
面向接口编程是Java编程中一个重要规则,在Java程序设计里面,非常讲究层的划分和模块的划分。比如场景的三层结构:
在一个层内部的各个模块交互也要通过接口
既然在Java中需要面向接口编程,那么在程序中到底该如何使用接口,来做到真正的面向接口编程呢?
比如目前有接口: Api , 实现类: ImplA、ImplB 客户端:Client
画一个简单的类图:
2.3、不用模式的解决方案
1 (1)首先是接口的定义Api,如下: 2 /** 3 * 某个接口(通用的、抽象的、非具体的功能) 4 * @author Administrator 5 * 6 */ 7 public interface Api { 8 /** 9 * 某个具体的方法定义 10 * @param msg 11 */ 12 public void operation(String msg); 13 } 14 (2)具体的实现类ImplA,如下: 15 /** 16 * 具体的实现类A 17 * @author Administrator 18 * 19 */ 20 public class ImplA implements Api { 21 22 @Override 23 public void operation(String msg) { 24 System.out.println("ImplA , msg = " + msg); 25 } 26 } 27 (3)具体的实现类ImplB,如下: 28 /** 29 * 具体的实现类B 30 * @author Administrator 31 * 32 */ 33 public class ImplB implements Api { 34 35 @Override 36 public void operation(String msg) { 37 System.out.println("ImplB , msg = " + msg); 38 } 39 } 40 (4)客户端,如下: 41 /** 42 * 客户端 43 * @author Administrator 44 * 45 */ 46 public class Client { 47 public static void main(String[] args) { 48 Api api = new ImplA(); 49 api.operation("哈哈,嘻嘻"); 50 51 System.out.println("------------------"); 52 api = new ImplB(); 53 api.operation("嘿嘿,嘎嘎"); 54 } 55 } 56 57 运行结果: 58 ImplA , msg = 哈哈,嘻嘻 59 ------------------ 60 ImplB , msg = 嘿嘿,嘎嘎
2.4、有何问题
重点看看客户端代码中的下面两行:
1 Api api = new ImplA(); 2 api = new ImplB();
我们会发现在客户端调用的时候,客户端不但知道了接口,而且还知道具体的实现类:ImplA、ImplB。接口的思想是"封装隔离",而实现类ImplA、ImplB应该是被接口Api封装并同客户端隔离开的,换句话说, 客户端根本就不应该知道具体的实现是ImplA、ImplB。
把这个问题描述一下:在Java编程中,出现只知接口而不知实现,该肿么办?
2.5、使用简单工厂来解决问题
1 /** 2 * 某个接口(通用的、抽象的、非具体的功能) 3 * @author Administrator 4 * 5 */ 6 public interface Api { 7 /** 8 * 某个具体的方法定义 9 * @param msg 10 */ 11 public void operation(String msg); 12 } 13 14 /** 15 * 具体的实现类A 16 * @author Administrator 17 * 18 */ 19 public class ImplA implements Api { 20 21 @Override 22 public void operation(String msg) { 23 System.out.println("ImplA , msg = " + msg); 24 } 25 } 26 27 /** 28 * 具体的实现类B 29 * @author Administrator 30 * 31 */ 32 public class ImplB implements Api { 33 34 @Override 35 public void operation(String msg) { 36 System.out.println("ImplB , msg = " + msg); 37 } 38 } 39 40 /** 41 * 工厂类,用来创建Api对象 42 */ 43 public class Factory { 44 /** 45 * 具体创建Api对象的方法 46 * @param condition 选择条件 47 * @return 创建好的Api对象 48 */ 49 public static Api createApi(int condition){ 50 Api api = null; 51 if(condition == 1){ 52 api = new ImplA(); 53 }else if(condition == 2){ 54 api = new ImplB(); 55 } 56 return api; 57 } 58 } 59 60 /** 61 * 客户端 62 * @author Administrator 63 * 64 */ 65 public class Client { 66 public static void main(String[] args) { 67 //重要改变,没有new Impl()了,取而代之Factory.createApi(); 68 Api api = Factory.createApi(1); 69 api.operation("哈哈,嘻嘻"); 70 71 System.out.println("------------------"); 72 api = Factory.createApi(2); 73 api.operation("嘿嘿,嘎嘎"); 74 } 75 } 76 77 运行结果: 78 ImplA , msg = 哈哈,嘻嘻 79 ------------------ 80 ImplB , msg = 嘿嘿,嘎嘎
--------------------------------------------------------------------
3、理解简单工厂
3.1、典型疑问
可能会有人认为,上面示例中的简单工厂看起来不就是把客户端里面的"new ImplA()、new ImplB()"移动到简单工厂里面吗? 不还是一样通过new一个实现类来得到接口吗? 把"new ImplA()、new ImplB()"放到客户端和放到简单工厂里面有什么不同吗?
ps: 理解这个问题的重点就在于理解简单工厂所处的位置。
简单工厂的位置是位于封装体内的,也就是简单工厂是跟接口和具体的实现在一起的,算是封装体内部的一类,所以简单工厂知道具体的实现类是没有关系的。重新整理一下简单工厂的结构图,如下图所示:
3.2、认识简单工厂
(1)简单工厂模式的功能
可以用来创建的接口、抽象类或者是普通类的实例。
(2)静态工厂
通常把简单工厂类实现成一个工具类,直接使用静态方法就可以了,也就是说简单工厂的方法通常都是静态的,所以也称为静态工厂。
(3)万能工厂
一个简单工厂可以包含很多用来构造东西的方法,这些方法可以创建不同的接口、抽象类或者是类实例。一个简单工厂理论上可以构造任何东西。所以又称之为“万能工厂”
(4)简单工厂创建对象的范围
虽然从理论上将,简单工厂什么都能创建,但对于简单工厂可创建对象的范围,通常不要太大,最好是控制在一个独立的组件级别或者一个模块级别。
(5)简单工厂的调用顺序示意图
3.3、简单工厂中方法的写法
仔细分析会发现,简单工厂方法的内部主要实现的功能是"选择合适的实现类"来创建实例对象,既然要实现选择,那么就需要选择的条件或者是选择的参数,选择条件或者是参数的来源通常有以下几种:
- 来源于客户端,由Client来传入参数
- 来源于配置文件,从配置文件获取用于判断的值
- 来源于程序运行期间的某个值,比如从缓存中获取某个运行期的值
3.4、可配置的简单工厂
通过配置文件的方式来实现简单工厂
1 /** 2 * 某个接口(通用的、抽象的、非具体的功能) 3 * @author Administrator 4 * 5 */ 6 public interface Api { 7 /** 8 * 某个具体的方法定义 9 * @param msg 10 */ 11 public void operation(String msg); 12 } 13 14 15 /** 16 * 具体的实现类A 17 * @author Administrator 18 * 19 */ 20 public class ImplA implements Api { 21 22 @Override 23 public void operation(String msg) { 24 System.out.println("ImplA , msg = " + msg); 25 } 26 } 27 28 /** 29 * 具体的实现类B 30 * @author Administrator 31 * 32 */ 33 public class ImplB implements Api { 34 35 @Override 36 public void operation(String msg) { 37 System.out.println("ImplB , msg = " + msg); 38 } 39 } 40 41 42 import java.io.IOException; 43 import java.io.InputStream; 44 import java.util.Properties; 45 46 /** 47 * 工厂类,用来创建Api对象 48 */ 49 public class Factory { 50 51 /** 52 * 具体创建Api对象的方法,根据配置文件的参数来创建接口 53 * @return 54 */ 55 public static Api createApi(){ 56 //直接读取配置文件来获取需要创建实例的类 57 Properties p = new Properties(); 58 InputStream in = null; 59 try{ 60 in = Factory.class.getResourceAsStream("factory.properties"); 61 p.load(in); 62 }catch(IOException e){ 63 System.out.println("找不到配置文件!"); 64 e.printStackTrace(); 65 }finally{ 66 try{ 67 in.close(); 68 }catch(IOException e){ 69 e.printStackTrace(); 70 } 71 } 72 73 //利用反射区创建 74 Api api = null; 75 76 try { 77 api = (Api)Class.forName(p.getProperty("ImplClass")).newInstance(); 78 } catch (InstantiationException e) { 79 e.printStackTrace(); 80 } catch (IllegalAccessException e) { 81 e.printStackTrace(); 82 } catch (ClassNotFoundException e) { 83 e.printStackTrace(); 84 } 85 return api; 86 } 87 } 88 89 /** 90 * 客户端 91 * @author Administrator 92 * 93 */ 94 public class Client { 95 public static void main(String[] args) { 96 //重要改变,没有new Impl()了,取而代之Factory.createApi(); 97 Api api = Factory.createApi(); 98 api.operation("哈哈,嘻嘻"); 99 } 100 } 101 102 配置文件factory.properties 103 ImplClass=Factory.ImplA 104 105 106 运行结果: 107 ImplA , msg = 哈哈,嘻嘻
--------------------------------------------------------------------
4、思考简单工厂模式
4.1、简单工厂模式的本质:选择实现
4.2、简单工厂的优缺点
优点:
- 帮助封装
- 解耦
缺点:
- 可能增加客户端使用的复杂度
- 不方便扩展子工厂
4.3、何时选用简单工厂
- 如果想要完全封装隔离具体的实现,让让外部只能通过接口来操作封装体,那么可以选用简单工厂,让客户端通过工厂来获取相应的接口,而无须关系具体的实现
- 如果想要把对外创建对象的职责集合管理和控制,可以选用简单工厂,一个简单工厂可以创建很多的、不相关的对象,可以把对外创建对象的职责集中到一个工厂来,从而实现集中管理和控制