面向对象设计模式之创建型模式(二):工厂模式

李青原(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配置,所做的也只是转移耦合到注解和配置文件中,方便进行耦合管理

posted @ 2013-05-20 17:25  李青原  阅读(505)  评论(0)    收藏  举报