zxbyhcsdn

 

关于工厂方法模式与开闭原则

这儿有两个关键点:

1>工厂方法满足开闭原则么?
2>工厂方法到底用在什么场合?

好像模式的书上都写着工厂方法满足开闭原则...

但是我认为它并不满足开闭, 不知道是不是我的认识有错误
故再此发文一篇,权当讨论与提高.

哪么先从简单工厂说起,好像所有的讲设计模式的书籍里面工厂模式都是从简单工厂开始的.
简单工厂是公认的部分支持开闭原则的.

众所周知工厂模式就是为了解决New这个玩意儿的,先来一个没有用到工厂的例子

import java.io.*;
interface ITest{
    String testFun();
}
class TestA implements ITest{
    public String testFun(){
        return "A;testFun";
    }
}
class TestB implements ITest{
    public String testFun(){
        return "B;testFun";
    }
}

public class TestFactory{
    public static void main(String args[]) throws IOException{
     //声明一个变量引用
     ITest test = null;

     //根据用户输入决定实例化具体的对象
     BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
     String line = sin.readLine();
      if(line.equals("A")){
        test = new TestA();
     }
     else if(line.equals("B")){
        test = new TestB();
     }
     //使用对象
     if (test != null){
     System.out.println(test.testFun());
     }
   }
}


这儿使用了 new 具体的类,不满足开闭原则

因为修改或者增加,new这个地方都要改动
比如说我们现在增加了一个类C,那么客户端这儿就不可避免要改动.
如果这样的逻辑在多个客户端用到,就必须都做改动,显然是不合理的.

基于 "对可变性的封装" 的指导思想.
把new这个地方的工作封装到一个类里面,于是就有了简单工厂.

class SampleFactory{
    public static ITest create(String ar){
        if(ar.equals("A")){
            return new TestA();
        }
        else if(ar.equals("B")){
            return new TestB();
        }
        return null;
    }
}

然后客户端调用改为:

public static void main(String args[]) throws IOException{
    //声明一个变量引用
    ITest test = null;

    //根据用户输入决定实例化具体的对象
    BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
    String line = sin.readLine();
  
    //调用简单工厂创建对象
    test = SampleFactory.create(line);

    //使用对象
    if (test != null){
        System.out.println(test.testFun());
    }
}

于是这儿就能适应变化了,如果新加入了C类型,就不用去修改所有使用的客户端了.客户端满足开闭了,
但是工厂又必须修改,而且吧其他类型的创建逻辑影响到了.那咋办捏?

又基于"对可变性的封装" 的指导思想,对每一个类型的New 都弄一个工厂,这样有扩展就不得影响
道其他工厂了, 这个就是工厂方法模式, 好像满足了 开闭原则了,

interface IFactoryMathod{
    ITest create();
}

class FactoryA implements IFactoryMathod{
 public ITest create(){
        return new TestA();
 }
}

class FactoryB implements IFactoryMathod{
 public ITest create(){
        return new TestB();
 }
}

然后 客户端调用变成:

public static void main(String args[]) throws IOException{
    //声明一个变量引用
    ITest test = null;
    //声明一个工厂
    IFactoryMathod factory = null;

    //根据用户输入决定实例化具体的对象
    BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
    String line = sin.readLine();
  
    //调用工厂方法创建对象
    if(line.equals("A")){
        factory = new FactoryA();
    }
    else if(line.equals("B")){
        factory = new FactoryB();
    }
    else{
        factory = new FactoryB();
    }
    test = factory.create();
    //使用对象
    if (test != null){
        System.out.println(test.testFun());
    }
}

但是现在问题有来了,客户端的这段代码又瓜了.对客户端来说,又不满足开闭了.
我们转了一圈结果发现又回到开始的地方了...晕??

哪么我认为:工厂方法仍然不满足开闭, 因为只要在代码里面用了New ,就不可能满足开闭,如果有添加
总要修改到现有代码的某一个部分.

这儿就需要引入反射,消除了New,从配置文件里面读取需要创建的对象.比如说Spring就是这么干的.

然后说了半天,从上面的这个例子好像发现,那个工厂方法没什么用处得,完全就是用一个方法把new
包起来,大大的有脱裤子放屁的嫌疑..
到底怎么回事情啦,不可能大名顶顶的工厂方法就这样子??

哪么就进入下面的一个论点: 工厂方法的使用场合

好像有了反射.工厂方法就可以下岗了...
的确,我认为仅仅在工厂方法里面写一个New XXXX 的话,哪么这样用工厂方法的确快下岗了..

但是我觉得真正体现工厂方法的意义的在这儿:
还是 拿出 我们的指导思想(马克思列宁思想...哈哈哈) "对可变性的封装"
当对象的创建不仅仅是一个new XXXX,包含复杂的业务逻辑,而且可以面临巨大的变动.
比如说: 有个需求是对象的创建需要是从文件序列化出来,如果没有才New一个,属性设置为默认,
万一那天有要求分布式应用,不能从文件里面反序列,需要改为从数据库里面找到属性并创建一个出来.
总不可能在每个调用这个类的客户端端口都去写上这么一大堆 if else的业务逻辑三.
如果以后再有改动,不就麻烦了..

这个时候就需要工厂方法,把可变的封装到工厂方法里面,
如果以后有变动或者增加,我们就只是需要修改或者扩展具体的工厂方法类,其他的都不会受到干扰.
再结合反射,将工厂方法类放到配置文件,这样就能真正的 满足开闭原则.

可能有同学有提出来问题:我何不在具体类的构造函数里面去做哪么一大堆啦.这样有改动的化,
我修改一下那个构造函数不久得了.反正用了反射,仍然满足开闭原则.

但是如果那个地方的代码变动的可能很大,你修改了构造函数,哪么这个类的其他地方就受到干扰了
站在这个问题的角度说,就是: 当一个类的构造逻辑频繁变动,哪么就需要把他封装,于是把这儿的逻辑放到工厂方法里面了.
这儿有体现了 "对可变性的封装" 的思想

工厂方法模式并不是因为简单工厂方法不满足开闭原则而引入
而是因为:类的构造逻辑复杂且多变,为了将构造逻辑封装而引入


哪么现在得出结论:
1>工厂方法并不真正满足开闭原则,但是结合反射和配置文件能够满足.
2>工厂方法使用场合 : 类的构造逻辑复杂且多变,为了将构造逻辑封装而引入
如果仅仅是在工厂方法里面写一个New,而且也不会发生变化.就没有使用工厂方法的意义了.


然后还有这个 思想我觉得比较重要
"对可变性的封装",或者说 "对不确定性的封装"
OOAD的核心思想啊!!!


欢迎讨论...写这些的目的就是为了共同进步,有什么错误或者不足,欢迎指出....

 

posted on 2008-07-26 21:31 zxbyh 阅读(2361) 评论(3)  编辑  收藏 所属分类: 设计模式

评论

# re: 关于工厂方法模式与开闭原则[未登录] 2008-07-26 22:03 LB

理论结合实际,等你真正做系统时候,没有那么多完美的情况。
还有选择最适合的方法,不是说spring出来了就处处用spring,或者处处都AOP,反射用在需要的地方,你可以去IBM development那里看看专家评测的new一个对象和用反射建立一个对象时间消耗,都不在一个级别上。

另外,你的factory不用还为每一个类型在建立具体的factory,那个和直接建立这个类型有什么区别,就是多了一个中间层环节,可是你只是new,并没有加入任何逻辑型的东西。所以说,你具的例子太不实际了。

写这些思想的人,也是一开始自己摸索的,他提出来了,也是他尽量地总结出来的,怎么现在的程序员脑子这么死性呢!  回复  更多评论   

# re: 关于工厂方法模式与开闭原则 2008-07-26 22:19 zxbyh

@LB

本文是在讨论 工厂模式与开闭原则.
并不是在讨论实际系统是否必须百分百满足开闭原则.

本人也认为,原则是死的,使用的人是灵活的..

还有一点,我举例的factory为每一个类型在建立具体的factory,
这个正是我要论证的论点:如果仅仅是在工厂方法里面写一个New,而且也不会发生变化.就没有使用工厂方法的意义了.
可以认为那是一个反例!!!

  回复  更多评论   

# re: 关于工厂方法模式与开闭原则 2008-07-28 13:30 zhuxing

个人觉得搂主文章里面对“开闭原则”的理解有点狭隘

首先,开闭原则本身重在强调系统真个框架在引入新扩展的时候能够提供比较自然的扩展,外部使用的抽象层面的东西不需要做很大的改动。而且,从本质上将,开闭原则也有一定的成分是愿景,不然就不会将其提高到了OO编程5大原则之一了

其次,客户端的调用代码的需要改动,是不是据此就判断是打破了开闭原则了。个人觉得不是这样的。

估计楼主是在客户端代码里面包含了一定的选择特定工厂的任务,觉得新工厂的进入,需要增加判断语句,以便使用新的工厂实现。进一步延伸讲,这只是一个客户端,真正的系统中可能有很多很多的类似客户端。如果讲工厂的选择操作做一个封装,那多个客户端选择工厂的行为操作本身就可以进行封装了,例如:
getFactory(int factoryID) {//...}


个人意见,仅供参考!

同时赞同楼上@LB评论的观点


  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

常用链接

留言簿(1)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜