软件设计模式之工厂模式(JAVA)

什么是工厂模式?

工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。

简单点来说,工厂模式就是提供一个产生实例化对象的制造厂,每一个工厂都会提供类似的对象(实现共同接口),当我们需要某个类的实例化对象时,我们不需要关心服务端是通过什么方式来获取对象的,我们直接向对应的工厂提出我们的需求即可(实现了客户端和服务端的分离)。

 

工厂模式分成三类:

1、简单工厂模式(Simple Factory)

2、工厂方法模式(Factory Method)

3、抽象工厂模式(Abstract Factory)

一般我们把简单工厂模式也归为工厂方法模式,然后抽象工厂模式是工厂方法模式的扩展。

 

1、工厂方法模式

先来看看工厂方法模式的组成:

工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现。 

抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。 

具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。 

 

 

写个小例子,模拟上帝造人事件,上帝根据不同地理位置气候的不同,造了三种不同肤色的人类:黄种人,白种人,黑种人。

 

这是一个肤色的接口(这里我们粗略先认为他们只有肤色之差)

 1 package com.lcw.factory.test1;
 2 
 3 /**
 4  * 皮肤接口
 5  */
 6 public interface SkinInterface {
 7     
 8     public void skin();
 9 
10 }

三种肤色的人类,分别是三个实现类(实现SkinInterface接口),由于代码结构一致,这里只给出一个

 1 package com.lcw.factory.test1;
 2 
 3 public class YellowRace implements SkinInterface {
 4 
 5     @Override
 6     public void skin() {
 7         System.out.println("我是黄种人");
 8     }
 9 
10 }

然后,如果按照我们以往的惯例,要拿到对应实现类的对象,我们需要SkinInterface si=new YellowRace();这样子,表面上看虽然没有问题,但一类实现类爆发呢?比如有上百千个实现类,那么除了要添加对应的实现类不说,还需要在客户端去"显示调用"暴露出实现类的类名,后期维护起来也很麻烦。

这时我们的工厂类就派上用场了

 1 package com.lcw.factory.test1;
 2 
 3 /**
 4  * 工厂类(返回对象实例)
 5  * 
 6  */
 7 public class SkinFactory {
 8 
 9     // key为关键字,决定实例化哪个类对象
10     public SkinInterface getSkin(String key) {
11         if ("black".equals(key)) {
12             return new BlackRace();
13         } else if ("white".equals(key)) {
14             return new WhiteRace();
15         } else if ("yellow".equals(key)) {
16             return new YellowRace();
17         }
18         return null;
19     }
20 }

写个实现类试试吧

 1 package com.lcw.factory.test1;
 2 
 3 import java.util.Map;
 4 
 5 public class FactoryTest {
 6 
 7     /**
 8      * @param args
 9      */
10     public static void main(String[] args) {
11         //普通调用方法(显示调用)
12         System.out.println("-------------------------普通调用方法-----------------------------");
13         SkinInterface yellowRace=new YellowRace();
14         SkinInterface whiteRace=new WhiteRace();
15         SkinInterface blackRace=new BlackRace();
16         
17         yellowRace.skin();
18         whiteRace.skin();
19         blackRace.skin();
20         
21         
22         //工厂方法模式调用
23         System.out.println("-------------------------工厂方法模式调用(脱离客户端和服务端)-----------------------------");
24         SkinFactory skinFactory=new SkinFactory();
25         SkinInterface skinInterface1=skinFactory.getSkin("black");
26         SkinInterface skinInterface2=skinFactory.getSkin("white");
27         SkinInterface skinInterface3=skinFactory.getSkin("yellow");
28         
29         skinInterface1.skin();
30         skinInterface2.skin();
31         skinInterface3.skin();
32     }
33 }

从上图来看,我们已经达到了基本目的,但之前的问题已经存在,虽然我们解决了客户端的"显示调用",但服务端却多了一堆的if..elseif的判断,如果实现类很多,那么就需要些更多的if..elseif的判断,这效率很明显就低了,因为每次客户端一请求,那么服务端就要做多余的没玩没了的判断。

有没有什么更好的方法的?那必须是有的,Java有个很强大的机制——反射,我们可以利用反射机制,把所有的类名写入到一个配置文件,以后单纯的维护配置文件即可,看下具体实现过程吧。

首先,我们需要一个配置文件skin.properties

1 black=com.lcw.factory.test1.BlackRace
2 yellow=com.lcw.factory.test1.YellowRace
3 white=com.lcw.factory.test1.WhiteRace

然后写一个读取properties文件的类,把关键字(key)=》值(value)封装到一个map集合。这样我们在客户端只需要输入关键字就可以让服务端自动映射到对应的类。

关于Java读取配置文件,不熟悉的小伙伴们可以轻戳《Java读写配置文件——Properties类的简要使用笔记》,帮你们总结好了。

 1 package com.lcw.factory.test1;
 2 
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 import java.util.Enumeration;
 6 import java.util.HashMap;
 7 import java.util.Map;
 8 import java.util.Properties;
 9 
10 /**
11  * 读取配置文件类(读取配置文件信息并把键值对封装成一个Map集合)
12  * 
13  */
14 public class PropertiesReader {
15 
16     public Map<String,String> getSkinMap(){
17         
18         Map<String,String> map=new HashMap<String, String>();
19         
20         Properties properties=new Properties();
21         InputStream inStream=getClass().getResourceAsStream("skin.properties");
22         try {
23             properties.load(inStream);
24             Enumeration enumeration=properties.propertyNames();//取得配置文件里的所有key值
25             while(enumeration.hasMoreElements()){
26                 String key=(String) enumeration.nextElement();
27                 map.put(key, properties.getProperty(key));
28             }
29             return map;
30             
31         } catch (IOException e) {
32             e.printStackTrace();
33         }
34         
35         return null;
36         
37     }
38 }

当然,此时的工厂类也需要修改了,看看完整代码

 1 package com.lcw.factory.test1;
 2 
 3 /**
 4  * 工厂类(返回对象实例)
 5  * 
 6  */
 7 public class SkinFactory {
 8 
 9     // key为关键字,决定实例化哪个类对象
10     public SkinInterface getSkin(String key) {
11         if ("black".equals(key)) {
12             return new BlackRace();
13         } else if ("white".equals(key)) {
14             return new WhiteRace();
15         } else if ("yellow".equals(key)) {
16             return new YellowRace();
17         }
18         return null;
19     }
20 
21     // 优化版(避免使用if..elseif繁杂判断)
22     public SkinInterface getSkinByClassName(String className) {
23         try {
24             
25             return (SkinInterface) Class.forName(className).newInstance();
26             
27         } catch (InstantiationException e) {
28             e.printStackTrace();
29         } catch (IllegalAccessException e) {
30             e.printStackTrace();
31         } catch (ClassNotFoundException e) {
32             e.printStackTrace();
33         }
34         return null;
35     }
36 
37 }

完整测试类:

 1 package com.lcw.factory.test1;
 2 
 3 import java.util.Map;
 4 
 5 public class FactoryTest {
 6 
 7     /**
 8      * @param args
 9      */
10     public static void main(String[] args) {
11         //普通调用方法(显示调用)
12         System.out.println("-------------------------普通调用方法-----------------------------");
13         SkinInterface yellowRace=new YellowRace();
14         SkinInterface whiteRace=new WhiteRace();
15         SkinInterface blackRace=new BlackRace();
16         
17         yellowRace.skin();
18         whiteRace.skin();
19         blackRace.skin();
20         
21         
22         //工厂方法模式调用
23         System.out.println("-------------------------工厂方法模式调用(脱离客户端和服务端)-----------------------------");
24         SkinFactory skinFactory=new SkinFactory();
25         SkinInterface skinInterface1=skinFactory.getSkin("black");
26         SkinInterface skinInterface2=skinFactory.getSkin("white");
27         SkinInterface skinInterface3=skinFactory.getSkin("yellow");
28         
29         skinInterface1.skin();
30         skinInterface2.skin();
31         skinInterface3.skin();
32         
33         
34         //工厂方法模式调用(优化版)
35         System.out.println("-------------------------工厂方法模式调用(优化版)-----------------------------");
36         SkinFactory factory=new SkinFactory();
37         
38         //SkinInterface skinInterface=factory.getSkinByClassName("com.lcw.factory.test.YellowRace");//过于繁杂的包名而且显示调用了,我们可以用配置文件来映射简化
39         //skinInterface.skin();
40         
41         PropertiesReader propertiesReader=new PropertiesReader();
42         Map<String,String> map=propertiesReader.getSkinMap();//获取配置文件里的map集合
43         SkinInterface sf1=factory.getSkinByClassName(map.get("black"));
44         SkinInterface sf2=factory.getSkinByClassName(map.get("white"));
45         SkinInterface sf3=factory.getSkinByClassName(map.get("yellow"));
46         
47         sf1.skin();
48         sf2.skin();
49         sf3.skin();
50         
51         
52         
53     }
54 
55 }

看下效果图:

这样一来,虽然我们多做了很多代码工厂,但对于日后维护起来就很方便了,不管添加多少的新的实现类,我们都不需要去改动客户端代码和工厂类代码,只需要在配置文件里添加上对应的关键字(key),和value(完整类名)即可。

 

 

 

2、抽象工厂模式

 在抽象工厂模式中,抽象产品 (AbstractProduct) 可能是一个或多个,从而构成一个或多个产品族(Product Family)。 

还是一样举个例子,人有男女之别,那么我们以国家来划分,在中国,有中国男孩,女孩,在美国,有美国男孩,女孩。本质上人只有男、女,那么只是一个基础的接口,那么不同的国家,就可以看做是不同的工厂,他们分别可以生产出"属于他们国家的男孩、女孩(产品)"。

来看下具体代码吧

 

这是产品的基础接口:

1 package com.lcw.factory.test2;
2 
3 /**
4  *男孩接口(产品接口)
5  */
6 public interface Boy {
7     public void boy();
8 }
1 package com.lcw.factory.test2;
2 /**
3  * 女孩接口(产品接口)
4  *
5  */
6 public interface Girl {
7     public void girl();
8 }

产品的具体实现:(由于代码结构一致,这里只给出美国)

 1 package com.lcw.factory.test2;
 2 /**
 3  * 
 4  *美国男孩(产品)
 5  */
 6 public class AmericaBoy implements Boy {
 7 
 8     @Override
 9     public void boy() {
10         System.out.println("我是男孩,我来自美国。");
11     }
12 
13 }
 1 package com.lcw.factory.test2;
 2 
 3 /**
 4  * 美国女孩(产品)
 5  */
 6 public class AmericaGirl implements Girl {
 7 
 8     @Override
 9     public void girl() {
10         System.out.println("我是女孩,我来自美国。");
11     }
12 
13 }

抽象工厂接口:

 1 package com.lcw.factory.test2;
 2 /**
 3  * 
 4  *抽象工厂
 5  */
 6 public interface PersonFactory {
 7     
 8     public Boy getBoy();
 9 
10     public Girl getGirl();
11 
12 }

具体工厂实现:(同样的,中国也有相同的具体工厂去实现这个抽象接口,并提供对应的中国男孩、女孩实例,由于代码结构一直,这里就不贴了)

 1 package com.lcw.factory.test2;
 2 /**
 3  * 
 4  *具体工厂类(产生具体实例)
 5  */
 6 public class AmericaPersonFactory implements PersonFactory{
 7 
 8     @Override
 9     public Boy getBoy() {
10         return new AmericaBoy();
11     }
12 
13     @Override
14     public Girl getGirl() {
15         return new AmericaGirl();
16     }
17 
18 }

来个测试类:

 1 package com.lcw.factory.test2;
 2 
 3 public class PersonFactoryTest {
 4     
 5     public static void main(String[] args) {
 6         PersonFactory factory1=new ChinesePersonFactory();//取得工厂对象
 7         //获取所需实例
 8         Boy boy1=factory1.getBoy();
 9         Girl girl1=factory1.getGirl();
10         
11         boy1.boy();
12         girl1.girl();
13         
14         
15         
16         
17         PersonFactory factory2=new AmericaPersonFactory();
18         Boy boy2=factory2.getBoy();
19         Girl girl2=factory2.getGirl();
20         
21         
22         boy2.boy();
23         girl2.girl();
24     }
25 }

看下实现效果:

 

总结:

(1)工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。 (或者由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的。)
(2)抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。它针对的是有多个产品的等级结构。而工厂方法模式针对的是一个产品的等级结构。

 

作者:Balla_兔子
出处:http://www.cnblogs.com/lichenwei/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我!

posted @ 2015-01-09 17:12  李晨玮  阅读(2055)  评论(3编辑  收藏  举报