简介
工厂模式是Java中最常用的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建时不会对客户端暴露创建逻辑,并且是通过一种公共的接口来指向新的对象。
举例说明
假如你想要买一辆车,你只要知道具体你想要的是什么车就可以去工厂里面提货了,你不需要知道这个车是怎么做出来的,以及其他细节。
优点
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想要增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心接口。高度封装。
引用设计模式之禅中的讲解(包括代码)
女娲补天的故事大家都听说过吧,今天不说这个,说女娲创造人的故事,可不是“造人”的工作,这个词被现代人滥用了。这个故事是说,女娲在补了天后,下到凡间一看,哇塞,风景太优美了,天空是湛蓝的,水是清澈的,空气是清新的,太美丽了,然后就待时间长了就有点寂寞了,没有动物,这些看的到都是静态的东西呀,怎么办?
别忘了是神仙呀,没有办不到的事情,于是女娲就架起了八卦炉(技术术语:建立工厂)开始创建人,具体过程是这样的:先是泥巴捏,然后放八卦炉里烤,再扔到地上成长,但是意外总是会产生的:
第一次烤泥人,兹兹兹兹~~,感觉应该熟了,往地上一扔,biu~,一个白人诞生了,没烤熟!
第二次烤泥人,兹兹兹兹兹兹兹兹~~,上次都没烤熟,这次多烤会儿,往地上一扔,嘿,熟过头了,黑人哪!
第三次烤泥人,兹兹兹~,一边烤一边看着,嘿,正正好,Perfect!优品,黄色人类!【备注:RB 人不属此列】
这个过程还是比较有意思的,先看看类图:(之前在论坛上有兄弟建议加类图和源文件,以后的模式都会加上去,之前的会一个一个的补充,目的是让大家看着舒服,看着愉悦,看着就想要,就像是看色情小说一样,目标,目标而已,能不能实现就看大家给我的信心了)
那这个过程我们就用程序来表现,首先定义一个人类的总称:
package FactoryModel;
public interface Human {
// 定义方法:笑,哭,说话
public void laugh();
public void cry();
public void talk();
}
然后定义具体的人类:
- 白人
package FactoryModel;
/*白人的实现类*/
public class WhiteHuman implements Human {
@Override
public void laugh() {
System.out.println("白色人类会笑!");
}
@Override
public void cry() {
System.out.println("白色人类会哭!");
}
@Override
public void talk() {
System.out.println("白色人类会说话!");
}
}
- 黑人
package FactoryModel;
/*黑人的实现类*/
public class BlackHuman implements Human {
@Override
public void laugh() {
System.out.println("黑人会笑!");
}
@Override
public void cry() {
System.out.println("黑人会哭!");
}
@Override
public void talk() {
System.out.println("黑人会说话!");
}
}
- 黄种人
package FactoryModel;
/*黄种人的实现类*/
public class YellowHuman implements Human {
@Override
public void laugh() {
System.out.println("黄种人会笑,开心的很!");
}
@Override
public void cry() {
System.out.println("黄种人会哭,一般不会哭!");
}
@Override
public void talk() {
System.out.println("黄种人会说话,别欺负我们,要不我们咬你!");
}
}
人类也定义完毕了,那我们把八卦炉定义出来:
package FactoryModel;
/*八卦炉*/
public class HumanFactory {
// 这个炉子只有一个功能就是:把泥巴扔进去,一个人就自动早出来了,但是得提前指定一下你要造的是黄种人还是黑人还是白人
// 静态方法,到时候你直接可以用类名加点调用
// 返回值:Human 类型的对象.
public static Human createHuman(Class c){
Human human = null; // 先定义一个人类类型的,等会赋值给它
try {
human = (Human)Class.forName(c.getName()).newInstance();// 利用反射机制找到对应的类,new一个对象
} catch (InstantiationException e) {
// 实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或者接口时,抛出的异常
System.out.println("你必须指定人类的颜色");
} catch (IllegalAccessException e) {
// 安全权限异常,一般来说,是由于java在反射时调用了private方法导致的。把方法要改成public
System.out.println("人类定义有错误!");
} catch (ClassNotFoundException e) {
System.out.println("混蛋,你指定的人类没有找到???");
}
return human;
}
}
然后我们再把女娲声明出来:
package FactoryModel;
/*女娲类*/
public class NvWa {
public static void main(String[] args) {
// 第一次造人:白人
System.out.println("-----------1------------");
Human human1 = HumanFactory.createHuman(WhiteHuman.class); // 这里的参数一定要与后台的类名相同,要不然反射找不到这个类
human1.laugh();
human1.cry();
human1.talk();
// 第二次造人:黑人
System.out.println("-----------2------------");
Human human2 = HumanFactory.createHuman(BlackHuman.class);// 这里的参数一定要与后台的类名相同,要不然反射找不到这个类
human2.laugh();
human2.cry();
human2.talk();
// 第二次造人:黑人
System.out.println("-----------3------------");
Human human3 = HumanFactory.createHuman(YellowHuman.class);// 这里的参数一定要与后台的类名相同,要不然反射找不到这个类
human3.laugh();
human3.cry();
human3.talk();
}
}
运行结果:
这样这个世界就热闹起来了,人也有了,但是这样创建太累了,神仙也会累的,那怎么办?神仙就想了:我塞进去一团泥巴,随机出来一群人,管他是黑人、白人、黄人,只要是人就成(你看看,神仙都偷懒,何况是我们人),先修改类图:
从类图上我们可以看出来,HumanFactory中新增了一个方法:CreateHuman(),这个方法没有任何参数,只要调它就可以随机的创建一个人出来,什么肤色的都有可能。下面看看代码的修改:
package FactoryModel;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/*八卦炉*/
public class HumanFactory {
// 这个炉子只有一个功能就是:把泥巴扔进去,一个人就自动早出来了,但是得提前指定一下你要造的是黄种人还是黑人还是白人
// 静态方法,到时候你直接可以用类名加点调用
// 返回值:Human 类型的对象.
public static Human createHuman(Class c){
Human human = null; // 先定义一个人类类型的,等会赋值给它
try {
human = (Human)Class.forName(c.getName()).newInstance(); // 利用反射机制找到对应的类,new一个对象
} catch (InstantiationException e) {
// 实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或者接口时,抛出的异常
System.out.println("你必须指定人类的颜色");
} catch (IllegalAccessException e) {
// 安全权限异常,一般来说,是由于java在反射时调用了private方法导致的。把方法要改成public
System.out.println("人类定义有错误!");
} catch (ClassNotFoundException e) {
System.out.println("混蛋,你指定的人类没有找到???");
}
return human;
}
// 女娲生气了,现在不管了,就直接把泥巴扔进去,爱产生啥就产生吧!
public static Human createHuman(){
Human human = null;
// 首先获得所有的Human的实现类,其中用到的ClassUtils是一个工具类,等会展示代码
List<Class> humanList = ClassUtils.getAllClassByInterface(Human.class);
//随机少一个人出来
Random random = new Random();
int rand = random.nextInt(humanList.size()); // 产生一个随机数
human = createHuman(humanList.get(rand)); // 调用上面的创造方法
return human;
}
}
女娲的改变:
package FactoryModel;
/*女娲类*/
public class NvWa {
public static void main(String[] args) {
// 第一次造人:白人
System.out.println("-----------1------------");
Human human1 = HumanFactory.createHuman(WhiteHuman.class); // 这里的参数一定要与后台的类名相同,要不然反射找不到这个类
human1.laugh();
human1.cry();
human1.talk();
// 第二次造人:黑人
System.out.println("-----------2------------");
Human human2 = HumanFactory.createHuman(BlackHuman.class);// 这里的参数一定要与后台的类名相同,要不然反射找不到这个类
human2.laugh();
human2.cry();
human2.talk();
// 第二次造人:黑人
System.out.println("-----------3------------");
Human human3 = HumanFactory.createHuman(YellowHuman.class);// 这里的参数一定要与后台的类名相同,要不然反射找不到这个类
human3.laugh();
human3.cry();
human3.talk();
// 随机产生一百个人
for(int i = 0; i < 100; i++){
System.out.println("-----------" + (i+1) +"------------");
Human human = HumanFactory.createHuman();// 这里的参数一定要与后台的类名相同,要不然反射找不到这个类
human.laugh();
human.cry();
human.talk();
}
}
}
结果图:
工具类:给一个接口,返回这个接口的所有实现类
package FactoryModel;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* 工具类:给一个接口,返回这个接口的所有实现类
*/
@SuppressWarnings("all")
public class ClassUtils {
public static List<Class> getAllClassByInterface(Class c){
List<Class> returnClassList = new ArrayList<Class>(); //返回结果
//如果不是一个接口,则不做处理
if(c.isInterface()){
String packageName = c.getPackage().getName(); //获得当前的包名
try {
List<Class> allClass = getClasses(packageName); //获得当前包下以及子包下的所有类
//判断是否是同一个接口
for(int i=0;i<allClass.size();i++){
if(c.isAssignableFrom(allClass.get(i))){ //判断是不是一个接口
if(!c.equals(allClass.get(i))){ //本身不加进去
returnClassList.add(allClass.get(i));
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return returnClassList;
}
//从一个包中查找出所有的类,在jar包中不能查找
private static List<Class> getClasses(String packageName) throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread() .getContextClassLoader();
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<File>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<Class>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes;
}
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
}
return classes;
}
}
工厂方法模式还有一个非常重要的应用,就是延迟始化(Lazy initialization),什么是延迟始化呢?
一个对象初始化完毕后就不释放,等到再次用到得就不用再次初始化了,直接从内存过中拿到就可以了,怎么实现呢,很简单,看例子:
package FactoryModel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
/*八卦炉*/
public class HumanFactory {
// 加一个map来存放之前创建过的对象
private static HashMap<String,Human> humans = new HashMap<>();
// 这个炉子只有一个功能就是:把泥巴扔进去,一个人就自动早出来了,但是得提前指定一下你要造的是黄种人还是黑人还是白人
// 静态方法,到时候你直接可以用类名加点调用
// 返回值:Human 类型的对象.
public static Human createHuman(Class c){
Human human = null; // 先定义一个人类类型的,等会赋值给它
try {
if(humans.containsKey(c.getSimpleName())){ // c.getSimpleName :返回源代码中给出的底层类的简称。
human = humans.get(c.getSimpleName()); // 如果之前创建过,就直接拿出来用
}else {
human = (Human)Class.forName(c.getName()).newInstance(); // 利用反射机制找到对应的类,new一个对象
humans.put(c.getSimpleName(), human); // 放入map中
}
} catch (InstantiationException e) {
// 实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或者接口时,抛出的异常
System.out.println("你必须指定人类的颜色");
} catch (IllegalAccessException e) {
// 安全权限异常,一般来说,是由于java在反射时调用了private方法导致的。把方法要改成public
System.out.println("人类定义有错误!");
} catch (ClassNotFoundException e) {
System.out.println("混蛋,你指定的人类没有找到???");
}
return human;
}
// 女娲生气了,现在不管了,就直接把泥巴扔进去,爱产生啥就产生吧!
public static Human createHuman(){
Human human = null;
// 首先获得所有的Human的实现类,其中用到的ClassUtils是一个工具类,等会展示代码
List<Class> humanList = ClassUtils.getAllClassByInterface(Human.class);
//随机少一个人出来
Random random = new Random();
int rand = random.nextInt(humanList.size()); // 产生一个随机数
human = createHuman(humanList.get(rand)); // 调用上面的创造方法
return human;
}
}
这个在类初始化很消耗资源的情况比较实用,比如你要连接硬件,或者是为了初始化一个类需要准备比较多条件(参数),通过这种方式可以很好的减少项目的复杂程度。