一天一模式之2简单工厂
初识简单工厂
提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。
结构和说明
- Api:定义客户所需要的功能接口
- Impl:具体实现Api的实现类,可能会有多个
- Factory:工厂,选择合适的实现类来创建Api接口对象
- Client:客户端,通过Factory去获取Api接口对象,然后面向Api接口编程
接口回顾
1.Java中接口的概念在Java中接口是一种特殊的抽象类
2. 接口用来干什么通常用接口来定义实现类的外观,就相当于一份契约,根据外部应用需要的功能,约定了实现类应该要实现的功能
3. 接口的思想——“封装隔离”
4. 使用接口的好处只要接口不变,内部实现的变化就不会影响到外部应用,从而使得系统更灵活,具有更好的扩展性和可维护性
5. 接口和抽象类的选择(1)优先选用接口(2)在如下情况选抽象类:既要定义子类的行为,又要为子类提供公共的功能
java实例
定义接口api
package cn.javass.dp.simplefactory.example2;
/**
* 接口的定义,该接口可以通过简单工厂来创建
*/
public interface Api {
/**
* 示意,具体的功能方法的定义
* @param s 示意,需要的参数
*/
public void operation(String s);
}
实现接口实例A
package cn.javass.dp.simplefactory.example2;
/**
* 接口的具体实现对象A
*/
public class ImplA implements Api{
public void operation(String s) {
//实现功能的代码,示意一下
System.out.println("ImplA s=="+s);
}
}
实现接口实例B
package cn.javass.dp.simplefactory.example2;
/**
* 接口的具体实现对象B
*/
public class ImplB implements Api{
public void operation(String s) {
//实现功能的代码,示意一下
System.out.println("ImplB s=="+s);
}
}
定义工厂
package cn.javass.dp.simplefactory.example2;
/**
* 工厂类,用来创造Api对象
*/
public class Factory {
/**
* 具体的创造Api对象的方法
* @param condition 示意,从外部传入的选择条件
* @return 创造好的Api对象
*/
public static Api createApi(int condition){
//应该根据某些条件去选择究竟创建哪一个具体的实现对象
//这些条件可以从外部传入,也可以从其它途径获取
//如果只有一个实现,可以省略条件,因为没有选择的必要
//示意使用条件
Api api = null;
if(condition == 1){
api = new ImplA();
}else if(condition == 2){
api = new ImplB();
}
return api;
}
}
客户端
package cn.javass.dp.simplefactory.example2;
/**
* 客户端,使用Api接口
*/
public class Client {
public static void main(String[] args) {
//通过简单工厂来获取接口对象
Api api = Factory.createApi(1);
api.operation("正在使用简单工厂");
}
}
面向接口编程
面向接口编程是Java编程中的一个重要原则。在Java 程序设计里面,非常讲究层的划分和模块的划分。比如常见的三层结构:
在一个层内部的各个模块交互也要通过接口
不管是一层还是一个模块或者一个组件,都是一个被接口隔离的整体
问题
既然在Java中需要面向接口编程,那么在程序中到底如何使用接口,来做到真正的面向接口编程呢?
不用模式的解决方案
java实例
接口api
package cn.javass.dp.simplefactory.example1;
/**
* 某个接口(通用的、抽象的、非具体的功能的)
*/
public interface Api {
/**
* 某个具体的功能方法的定义,用test1来演示一下。
* 这里的功能很简单,把传入的s打印输出即可
* @param s 任意想要打印输出的字符串
*/
public void test1(String s);
}
接口实现类
package cn.javass.dp.simplefactory.example1;
/**
* 对接口的实现
*/
public class Impl implements Api{
public void test1(String s) {
System.out.println("Now In Impl. The input s=="+s);
}
}
客户端
package cn.javass.dp.simplefactory.example1;
/**
* 客户端:测试使用Api接口
*/
public class Client {
public static void main(String[] args) {
Api api = new Impl();
api.test1("哈哈,不要紧张,只是个测试而已!");
}
}
这是真的面向接口编程吗?
这既没有没有把实现和接口封装,也没有实现隔离。
有何问题?
把这个问题描述一下:在Java编程中,出现只知接口而不知实现,该怎么办?
使用模式的解决方案
java实例
接口api
package cn.javass.dp.simplefactory.example3;
/**
* 某个接口(通用的、抽象的、非具体的功能的)
*/
public interface Api {
/**
* 某个具体的功能方法的定义,用test1来演示一下。
* 这里的功能很简单,把传入的s打印输出即可
* @param s 任意想要打印输出的字符串
*/
public void test1(String s);
}
接口实现
package cn.javass.dp.simplefactory.example3;
/**
* 对接口的实现
*/
public class Impl implements Api{
public void test1(String s) {
System.out.println("Now In Impl. The input s=="+s);
}
}
工厂
package cn.javass.dp.simplefactory.example3;
/**
* 工厂类,用来创造Api对象
*/
public class Factory {
private Factory(){
}
/**
* 具体的创造Api对象的方法
* @return 创造好的Api对象
*/
public static Api createApi(){
//主要用来实现 选择合适的实现类 来创建实例对象
//由于只有一个实现,就不用条件判断了
return new Impl();
}
}
客户端
package cn.javass.dp.simplefactory.example3;
/**
* 客户端:测试使用Api接口
*/
public class Client {
public static void main(String[] args) {
//重要改变,没有new Impl()了,取而代之Factory.createApi()
Api api = Factory.createApi();
api.test1("哈哈,不要紧张,只是个测试而已!");
}
}
使用简单工厂,就可以实现接口和实现的封装和隔离,客户端只能操作接口的api。
理解简单工厂
一个典型的疑问
首先来解决一个常见的疑问:可能有朋友会认为,上面示例中的简单工厂看起来不就是把客户端里面的“new Impl()”移动到简单工厂里面吗?不还是一样通过new一个实现类来得到接口吗?把“new Impl()”这句话放到客户端和放到简单工厂里面有什么不同吗?
理解这个问题的重点就在于理解简单工厂所处的位置。
认识简单工厂
简单工厂的功能
可以用来创建的接口、抽象类或者是普通类的实例静态工厂
通常把简单工厂类实现成一个工具类,直接使用静态方法就可以了,也就是说简单工厂的方法通常都是静态的,所以也被称为静态工厂万能工厂
一个简单工厂理论上可以用来构造任何对象,所以又称之为“万能工厂”简单工厂创建对象的范围
建议控制在一个独立的组件级别或者一个模块级别简单工厂的调用顺序示意图
-
简单工厂命名的建议
- (1)类名建议为“模块名称+Factory”,比如:用户模块的工厂就称为:UserFactory
- (2)方法名称通常为“get+接口名称”或者是“create+接口名称”
- (3)不建议把方法名称命名为“new+接口名称”
简单工厂中方法的写法
简单工厂方法的内部主要实现的功能是“选择合适的实现类”来创建实例对象。
注意:如果是从客户端在调用工厂的时候,传入选择的参数,这就说明客户端必须知道每个参数的含义,也需要理解每个参数对应的功能处理。这就要求必须在一定程度上,向客户暴露一定的内部实现细节。
可配置的简单工厂
使用反射加上配置文件,来实现添加新的实现类过后,无须修改代码,就能把这个新的实现类加入应用中
选择
1:参数来源于 client
接口
package cn.javass.dp.simplefactory.example4;
/**
* 某个接口(通用的、抽象的、非具体的功能的)
*/
public interface Api {
/**
* 某个具体的功能方法的定义,用test1来演示一下。
* 这里的功能很简单,把传入的s打印输出即可
* @param s 任意想要打印输出的字符串
*/
public void test1(String s);
}
实现1
package cn.javass.dp.simplefactory.example4;
/**
* 对某个接口的一种实现
*/
public class Impl implements Api{
public void test1(String s) {
System.out.println("Now In Impl. The input s=="+s);
}
}
实现2
package cn.javass.dp.simplefactory.example4;
/**
* 对接口的一种实现
*/
public class Impl2 implements Api{
public void test1(String s) {
System.out.println("Now In Impl2. The input s=="+s);
}
}
工厂
package cn.javass.dp.simplefactory.example4;
/**
* 工厂类,用来创造Api的
*/
public class Factory {
/**
* 具体的创造Api的方法,根据客户端的参数来创建接口
* @param type 客户端传入的选择创造接口的条件
* @return 创造好的Api对象
*/
public static Api createApi(int type){
//这里的type也可以不由外部传入,而是直接读取配置文件来获取
//为了把注意力放在模式本身上,这里就不去写读取配置文件的代码了
//根据type来进行选择,当然这里的1和2应该做成常量
//选择===〉如何选?====〉选择的参数===〉参数从何而来?
//1:参数来源于 client
//2:参数来源于配置文件
//3:参数来源于系统自身,比如运行期间的某个值
Api api = null;
if(type==1){
api = new Impl();
}else if(type==2){
api = new Impl2();
}
return api;
}
}
2:参数来源于配置文件
接口
package cn.javass.dp.simplefactory.example5;
/**
* 某个接口(通用的、抽象的、非具体的功能的)
*/
public interface Api {
/**
* 某个具体的功能方法的定义,用test1来演示一下。
* 这里的功能很简单,把传入的s打印输出即可
* @param s 任意想要打印输出的字符串
*/
public void test1(String s);
}
实现1
package cn.javass.dp.simplefactory.example5;
/**
* 对某个接口的一种实现
*/
public class Impl implements Api{
public void test1(String s) {
System.out.println("Now In Impl. The input s=="+s);
}
}
实现2
package cn.javass.dp.simplefactory.example5;
/**
* 对某个接口的一种实现
*/
public class Impl2 implements Api{
public void test1(String s) {
System.out.println("Now In Impl222222. The input s=="+s);
}
}
实现3
package cn.javass.dp.simplefactory.example5;
public class Impl3 implements Api{
public void test1(String s) {
System.out.println("Now In Impl33333. The input s=="+s);
}
}
配置文件FactoryTest.properties
ImplClass=cn.javass.dp.simplefactory.example5.Impl3
工厂
package cn.javass.dp.simplefactory.example5;
import java.util.*;
import java.io.*;
/**
* 工厂类,用来创造Api对象
*/
public class Factory {
/**
* 具体的创造Api的方法,根据配置文件的参数来创建接口
* @return 创造好的Api对象
*/
public static Api createApi(){
//直接读取配置文件来获取需要创建实例的类
//至于如何读取Properties还有如何反射这里就不解释了
Properties p = new Properties();
InputStream in = null;
try {
in = Factory.class.getResourceAsStream("FactoryTest.properties");
p.load(in);
} catch (IOException e) {
System.out.println("装载工厂配置文件出错了,具体的堆栈信息如下:");
e.printStackTrace();
}finally{
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//用反射去创建,那些例外处理等完善的工作这里就不做了
Api api = null;
try {
api = (Api)Class.forName(p.getProperty("ImplClass")).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return api;
}
}
客户端
package cn.javass.dp.simplefactory.example5;
/**
* 客户端:测试使用Api接口
*/
public class Client {
public static void main(String[] args) {
//重要改变,没有new Impl()了,取而代之Factory.createApi()
Api api = Factory.createApi();
api.test1("哈哈,不要紧张,只是个测试而已!");
}
}
3:参数来源于系统自身,比如运行期间的某个值
接口
package cn.javass.dp.simplefactory.example6;
/**
* 某个接口(通用的、抽象的、非具体的功能的)
*/
public interface Api {
/**
* 某个具体的功能方法的定义,用test1来演示一下。
* 这里的功能很简单,把传入的s打印输出即可
* @param s 任意想要打印输出的字符串
*/
public void test1(String s);
}
实现1
package cn.javass.dp.simplefactory.example6;
/**
* 对某个接口的一种实现
*/
public class Impl implements Api{
public void test1(String s) {
System.out.println("Now In Impl. The input s=="+s);
}
}
实现2
package cn.javass.dp.simplefactory.example6;
/**
* 对接口的一种实现
*/
public class Impl2 implements Api{
public void test1(String s) {
System.out.println("Now In Impl2. The input s=="+s);
}
}
工厂
package cn.javass.dp.simplefactory.example6;
/**
* 工厂类,用来创造Api的
*/
public class Factory {
private static int count = 0;
/**
* 具体的创造Api的方法,根据客户端的参数来创建接口
* @param type 客户端传入的选择创造接口的条件
* @return 创造好的Api对象
*/
public static Api createApi(){
//这里的type也可以不由外部传入,而是直接读取配置文件来获取
//为了把注意力放在模式本身上,这里就不去写读取配置文件的代码了
//根据type来进行选择,当然这里的1和2应该做成常量
//选择===〉如何选?====〉选择的参数===〉参数从何而来?
//1:参数来源于 client
//2:参数来源于配置文件
//3:参数来源于系统自身,比如运行期间的某个值
Api api = null;
if(count<3){
api = new Impl();
count++;
}else{
api = new Impl2();
count++;
}
return api;
}
}
客户端
package cn.javass.dp.simplefactory.example6;
/**
* 客户端:测试使用Api接口
*/
public class Client {
public static void main(String[] args) {
//重要改变,没有new Impl()了,取而代之Factory.createApi()
//注意这里传递的参数,修改参数就可以修改行为,试试看吧
for(int i=0;i<5;i++){
Api api = Factory.createApi();
api.test1("哈哈,不要紧张,只是个测试而已!");
}
}
}
简单工厂的优缺点
- 1:帮助封装
- 2:解耦
- 3:可能增加客户端的复杂度
- 4:不方便扩展子工厂
思考简单工厂
简单工厂的本质
简单工厂的本质是:选择实现
何时选用简单工厂
- 如果想要完全封装隔离具体实现,让外部只能通过接口来操作封装体,那么可以选用简单工厂,让客户端通过工厂来获取相应的接口,而无需关心具体实现
- 如果想要把对外创建对象的职责集中管理和控制,可以选用简单工厂,一个简单工厂可以创建很多的、不相关的对象,可以把对外创建对象的职责集中到一个简单工厂来,从而实现集中管理和控制