面向对象设计模式之创建型模式(二):工厂模式
李青原(liqingyuan1986@aliyun.com)创作于博客园个人博客(http://www.cnblogs.com/liqingyuan/),转载请标明出处。
工厂模式是最常见的设计模式之一,它的主要目的,就是让对象使用者和对象本身的创建细节分离。
实际代码中,工厂模式往往又分为几种实现方式,分别是简单工厂模式、工厂方法模式、抽象工厂模式、反射工厂模式。
1.简单工厂模式:
A.UML:

B.代码实现:
简单工厂模式将对象的创建工作交给工厂类,由简单工厂类在方法内部,根据参数选择创建不同的具体对象。
简单工厂类代码:
public class SimpleFactory { public static Product create(String productFlag){ if("A".equals(productFlag)){ return new ProductA(); }else if("B".equals(productFlag)){ return new ProductB(); }else{ return new ProductC(); } } }
对象使用代码:
Product a = SimpleFactory.create("A");
a.work();
原本对象使用者需要自己决定调用哪个具体产品对象的构造方法,现在它只给简单工厂类发出指令,而由工厂类来调用对应构造方法,使用者不再和具体对象发生关联,只需要知道这些对象的抽象身份Product接口。
C.优缺点:
简单工厂优势:模型简单,实现方便;使用者不再和具体产品耦合,只需要修改一个简单工厂类,就可以修改所有使用者获得的具体产品。
简单工厂缺点:当出现增量需求时,新增的产品,必须在简单工厂类方法内的选择代码中,添加一个 if else 选择语块。这种情景,违背了我们通常要求的“对添加开放,对修改关闭”的原则。
2.工厂方法模式:
A.UML:

B.代码实现:
抽象工厂模式中,工厂被继续抽象为一个接口,每个工厂的具体实现分别对应一个产品类的具体实现,使用者通过选择不同的工厂来获得不同的产品。
工厂A的代码:
public class FactoryA implements Factory{ public Product create(){ return new ProductA(); } }
使用代码:
Factory factory = new FactoryA(); Product a = factory.create();
和简单工厂模式相比,工厂方法模式中的工厂不再是“万能工厂”,每个具体工厂只能负责一种产品;产品的选择,不再依托工厂内部的代码,而是再次转交给使用者,让它通过选择不同的工厂来获得不同的对象。
有人会觉得这和直接调用具体产品类的构造方法似乎没有什么区别,因为使用者必然会“知道”一个具体类的构造方法,不是具体产品的构造方法,就是具体工厂的构造方法。
问题的关键在于,具体产品类的构造方法和具体工厂类的构造方法,哪一个更“稳定”?
从上面的代码可以看出,具体工厂本质上只是一个封装层,内部不具有任何逻辑代码;而具体产品则不同,它和业务逻辑往往紧密关联,产品的功能实现来源于具体业务。很明显,相比较之下,具体工厂类和业务的相关性几乎为0,也就是说它们“被改动的风险”几乎为0,在这种情况下,使用者依赖具体工厂类,要远好过直接依赖具体产品类。
C.优缺点:
工厂方法模式优点:符合“对添加开放,对修改关闭”的原则,增量代码更新是此模式的常态;让使用者依赖更“稳定”的具体工厂类,而不是和业务紧密关联的具体产品类。
工厂方法模式缺点:逻辑上来说,产品的选择又回到了使用者手中,尽管具体工厂类比较“稳定”,但是修改的风险依然存在,并不是完美的解决方案。
3.抽象工厂模式:
1.UML:

B.代码实现:
在实际工作中,使用的往往不是一个对象,而是一个对象系列,比如汽车+轮胎、飞机+螺旋桨,在这种情况下,一个对象系列中会存在多个抽象产品接口,刚才2个例子的抽象就是交通工具接口+运动装置接口。
解决这种情况,通常使用抽象工厂模式。抽象工厂模式是对工厂方法模式的扩展,工厂接口中不再只定义一个方法,而是针对每个产品接口都定义一个方法。
具体工厂1的代码:
public class Factory1 implements Factory{ public Product createA(){ return new ProductA1(); } public Product createB(){ return new ProductB1(); } }
使用代码:
Factory factory = new Factory1(); ProductA a1 = factory.createA(); ProductB b1 = factory.createB();
很明显,在抽象工厂模式中,具体产品类的分步不再是一维的,而是二维的——决定具体产品类的条件有2个,哪一种产品接口和该接口下的哪一种具体实现。
而每个具体工厂类,则不再直接针对具体产品,而是针对一种产品搭配,对应一个产品系列。当使用者在挑选具体工厂类时,决定的是一个系列的具体产品,比如挑选了Factory1,就等于选择了ProductA1和ProductB1。
C.优缺点:
抽象工厂模式是对工厂方法模式的扩展,具有工厂方法模式所有的优缺点。
同时,因为抽象工厂模式针对是更复杂的二维产品类分步,所以每个具体工厂都针对一个产品系列,更换产品系列在这种情况下变得非常简单(如果一个系列有5个产品,使用者只需要更换一个工厂类就能把5个产品同时更换)。
当然,抽象工厂模式同时带来的则是类数量的大量增加,会增加代码管理的难度。
4.反射工厂模式:
前面的三种工厂模式,都是传统的工厂模式,在实际代码中往往利弊参半。
而反射工厂模式,实际上是利用编程语言提供的反射功能,对上述三种模式的优化。
A.利用反射优化简单工厂:
public class SimpleFactory { public static Product create(String productClassName){ Product p = null; try{ p = (Product)Class.forName(productClassName).newInstance(); }catch(ClassNotFoundException e){ e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return p; } }
和原有的简单工厂模式不一样的是,工厂内部不再通过标记变量来做if else的分支判断,而是直接根据类路径,反射出一个实例交给使用者。
这样做最大的好处,就是简单工厂变得“一劳永逸”,无论新增什么具体产品,只要还是Product接口的实现类,原有的简单工厂类无需任何修改就能应对;而使用者依然只“知道”一个字符串变量,没有任何多余耦合。
B.利用反射优化抽象工厂模式:
public class ComplexFactory { public static ProductA createA(String productClassName){ return (ProductA)reflecting(productClassName); } public static ProductB createB(String productClassName){ return (ProductB)reflecting(productClassName); } public static Object reflecting(String productClassName){ Object object = null; try{ object = Class.forName(productClassName).newInstance(); }catch(ClassNotFoundException e){ e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return object; } }
和原有的抽象工厂模式相比,我们做了巨大改变,取消了工厂接口和实现类,变成了类似简单工厂加强版的静态工厂类。每个静态方法都针对一个产品接口,这样一个工厂类可以应对任意组合的产品系列。
但是必须注意:抽象工厂模式中,使用者只需要“知道”一个具体工厂,修改一个工厂就修改了一系列的产品;但使用反射后,使用者改为“知道”一系列的类路径字符串,这样造成修改的难度大大增加。
为了解决这个问题,最通常的做法就是封装一个产品系列类,工厂反射的不再是具体产品,而是具体产品系列,通过不同的产品系列实现类,来控制产品之间的搭配关系。(实质上是进一步抽象,将二维的产品分步还原成了一维的产品分步)
C.反射工厂的优缺点:
反射+工厂模式提供了一种全新的拥抱变化的代码思路。
反射功能使得我们能够写出一种稳定的、通用的工厂,以不变应万变的应对新增产品类,这是反射工厂模式提供的巨大优势。很多开源框架都采用了这种设计思路,最经典的就是Spring的IOC容器设计。
5.对工厂模式的思索和总结:
耦合无法解除,耦合只能被削弱和转移。
耦合由逻辑关系决定,只要业务逻辑没变,耦合一定还会存在。
所谓“解耦”,实际上是对耦合的削弱和转移。
耦合的削弱靠抽象:更精准的定位使用者的需求,使用最完全的抽象(接口或者抽象类),能够把耦合削弱到最低程度。
无论是何种设计模式,或者新的代码技术,都只能转移削弱后的耦合。比如当需要“对产品进行选择时”,简单工厂的选择在工厂内部,工厂方法和抽象工厂的选择在使用者,而反射工厂的选择在JVM——无论那种模式,你都必须进行选择,因为这是由业务逻辑决定的。
无论是使用注解,还是XML配置,所做的也只是转移耦合到注解和配置文件中,方便进行耦合管理。


浙公网安备 33010602011771号