简单工厂不属于标准的设计模式,但是它非常的实用和简单,而且在一些思想上与标准的设计模式切合,所以暂且归为设计模式中。
一、场景问题
1.1接口回顾
1.2面向接口编程
1.3不用模式的解决方案
package com.chenxyt.java.practice;
/*
* 某个接口(不具有业务实际意义)
*/
public interface Api {
/*
* 一个测试方法 功能将传入的s打出
* @param s
*/
public void test1(String s);
}
然后是这个接口的实现类Impl.java
package com.chenxyt.java.practice;
public class Impl implements Api{
@Override
public void test1(String s) {
// TODO Auto-generated method stub
System.out.println(s);
}
}
最后是一个客户端类Client.java,使用上边定义的接口
package com.chenxyt.java.practice;
public class Client {
public static void main(String[] args) {
Api api = new Impl();
api.test1("Hello API");
}
}
运行结果:
1.4有何问题
Api api = new Impl();
二、解决方案
2.1使用简单工厂来解决
简单工厂的概念:提供一个用来创建对象实例的功能,而不需要去关心具体的实现。被创建实例的类型可以是接口、抽象类也可以是一个具体的类。生活中的例子,大概就是有一个生产衣服的工厂,你只管去拿即可,不需要管衣服是怎么做的,也不需要让你自己做衣服了,衣服有各种各样的款式和大小,肯定会符合你的品位。
分析上边的问题,我们的目的就是不让外部模块知道具体的实现,也就是创建接口的示例的时候,不能使用接口的实现进行创建了。因此我们就把创建实例的方法转交给另一个类,这个类可以定义在模块内部,它可以知道也必须知道实例的具体实现。我们称这个类为Factory类。整理一下实现的思路就是我们创建一个Factory类,在这个类的内部来创建接口的实例,客户端通过Factory获得接口的实例,进而实现接口的方法。
2.2简单工厂结构说明
图中前边示例的实现方式是Client通过创建Api的实例Impl1或者Impl2实现operation功能,使用了设计模式之后的实现方式是创建一个工厂类,客户端调用这个工厂类来实现创建Api接口的实例Impl1或者Impl2,从而实现了真正的面向接口编程,隐藏了接口Api的实现部分Impl1或者Impl2
2.3简单工厂示例代码
首先是我们要实现的接口Api
package com.chenxyt.java.practice;
/**
* 一个要被调用的接口,该接口可以通过简单工厂创建
* @author Crayon
*
*/
public interface Api {
/**
* 接口内定义的方法示例
* @param s
*/
public void operation(String s);
}
接下来是接口Api的两个实现
package com.chenxyt.java.practice;
public class Impl implements Api{
@Override
public void operation(String s) {
// TODO Auto-generated method stub
System.out.println(s);
}
}
接下来是核心部分,也就是简单工厂的工厂Factory实现
package com.chenxyt.java.practice;
public class Factory{
public static Api createApi(int condition){
Api api = null;
if(condition == 1){
api = new Impl();
}else if(condition == 2){
api = new Impl();
}
return api;
}
}
这里只是做了一个示例,有多个实例的时候可以增加condition选择条件,由客户端选择创建哪个。类似在工厂买衣服,我给了你指定的大小尺码标准。
最后是客户端的调用,就是通过简单的静态方法的形式进行调用。
package com.chenxyt.java.practice;
public class Client {
public static void main(String[] args) {
Api api = Factory.createApi(1);
api.operation("Hello");
}
}
2.4使用简单工厂重写示例
这里上边的示例代码即是文章开始的示例部分的简单工厂模式。
三、模式讲解
3.1典型疑问
从上边的示例可以看出,所谓的简单工厂不过是将new Impl()放在了Factory类中,也就是它其实只是换了个位置,不是吗?这个问题的关键在于面向对象的封装特性,我们将Factory封装在了封装体内部,也就是Factory与Client是完全隔离的,因为换了个位置但是本质是发生了变化的。
3.2认识简单工厂
所谓工厂就是用来生产东西的一个场所,这里简单工厂设计模式不仅可以创建接口还可以创建抽象类与普通的实体类,因此也是一种万能工厂。同时我们也把简单工厂可以称作为是一种静态工厂,因为我们没有创建工厂实例的必要,仅仅需要将该类的方法设置为static的即可。为了防止客户端无意中将工厂类实例化,我们也可以像单例模式那样将构造函数私有化。虽然简单工厂理论上可以创建很大的范围,但是实际开发中为了更好的体现软件设计的模块化与结构化,建议工厂只创建本模块内的内容。
3.3简单工厂中方法的写法
继续理解上边的Factory类,发现它虽然能够创建接口、抽象类、实体类这些,但是真正实现这些功能的,还是他们具体的实现。(比如Impl)也就是说,简单工厂实际上就是一个实现了选择的工具,选择要实现什么功能。因此大多数情况下,Facotry内部的方法都是根据指定条件做选择。条件可以用形参传入,也可以从配置文件获取等各种形式。看到这里,可能前边说的衣服加工厂的例子稍微有一点不合适,大概像是一个中介吧,你告诉我你要什么,我给你选择,帮你实现,但是实现这一块不是我中介来做。
3.4可配置的简单工厂
结合配置文件和反射,可以实现简单工厂的配置化,当然使用IOC也可以实现。这里暂时介绍一下使用反射的方式配置。主要思想就是配置文件中配置要在工厂方法里创建的类的全名,然后通过反射的方式动态加载这个类,以达到目的。
示例的配置文件factory.properties如下:
ImplClass=com.chenxyt.practice.ImplClass1
如上配置文件中我们定义了一个全新的类的名称(包含包路径的名称)
接下来重新编写Factory类
package com.chenxyt.java.practice;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class Factory{
public static Api createApi(){
Properties p = new Properties();
InputStream in = null;
try{
//装载配置文件
in = Factory.class.getResourceAsStream("factory.properties");
p.load(in);;
}catch(IOException e){
e.printStackTrace();
}finally{
try{
in.close();
}catch(IOException e){
e.printStackTrace();
}
}
Api api = null;
try{
//使用反射创建配置文件中定义的类
api = (Api)Class.forName(p.getProperty("ImplClass")).newInstance();
}catch(Exception e){
e.printStackTrace();
}
}
}
3.5简单工厂的优缺点
优点:帮助封装,有助于面向接口编程,实现了客户端与接口之间的解耦。
缺点:违背了面向对象思想中的“开放-关闭”规则,一旦新增需要工厂创建的产品,需要对工厂类进行修改。静态方法不能被继承重写,因此无法实现继承结构。工厂类的本质是选择实现,因此当工厂类不可使用时,整个系统将受到影响。
3.6思考简单工厂
简单工厂的实质就是选择实现,当我们需要隔离封装的时候建议使用该模式,当我们想把创建外部对象的方法集中管理的时候,可以选择该模式。
3.7相关设计模式
相关的还有抽象工厂设计模式、工厂方法设计模式。如文章开篇所述,简单工厂与其它模式相比不属于标准的实际模式。相关的设计模式待续。
四、总结
为了便于整理全文以及后续复习,增加一个概括的章节。简单工厂说到底就是一个用来实现创建接口或者抽象的类的类,它的本质思想是选择实现,可以通过配置文件、反射、IOC等方式实现配置化。