http://blog.csdn.net/monkey_d_meng/article/details/5694353
第8章 雷锋依然在人间----工厂方法模式
8.1再现活雷锋
小菜来找大鸟,说:“今天我们见到活雷锋了。”
“哦,”大鸟感兴趣道,“现在已经很少提这个人名了,说说看。”
“我们班有个同学叫薛磊风,昨天,他出了车祸,被车撞断了腿,医生说没大碍,可以恢复。”小菜说,“四年来,他做人很低调,只听说他时常去勤工俭学,周末一般就看不到他,别的也没感觉到什么。”
“他不太走运,不过所谓‘常在路上走,哪有不碰车。’被车撞也是没办法的。”
“是呀,我们今天白天去医院看望他,却听到了一个非常感人的故事,应该说,像是在电视里才能看到的真人真事。”
“他做了感人的好事了!”
“你怎么知道,哦,我告诉你见到活雷锋的。你别打岔。”小菜不满地说,“是这样的,他这几年,都一直在帮助一个孤寡老人,每周都去老人家里,为老人洗衣扫地、买米买油,三年多,一直这样,因为他家住在广西很偏远的地方,到了南宁还要再坐几天的汽车,所以他四年来没有回过老家,也正因为此,他对这位老人家的帮助从没有停止过。”
“可是现在出了意外,他住进医院,不能帮助他人了。”大鸟还是打岔道,“这种事你骗谁呀,这么多孤寡老人,他为什么只帮助这一位?”
“你再打岔我就不说了,你不信也听听,接受一下再教育,我今天是受教育了。”小菜接着说。“他在大一的时候,从收音机里听到说,有位孤老早年丧妻,而唯一拉扯大的儿子在八十年代中越战争时牺牲了,而他正是从小生活在离战争不远的边防小镇,目睹过战争的残酷和解放军的无私奉献。所以他一听到这个消息就主动去为老人提供帮助。老人身体不好,尽管有些社会的救济,但在生活上还是不太方便,于是他帮助老人就成了每周末的功课。”
“哦,奉献精神代代传呀。”
“现在他住院了,至少近几个月都没办法去老人家,所以他委托我们去帮他继续做这件好事,此时我们大家才知道,雷锋原来就在我们身边。”
“不容易,在21世纪,我还以为这样的人越来越少了呢,原来乐于助人的事迹还是比比皆是。”
“明天我和几名同学就去老人家。不过老人不认识我们,薛磊风也交待我们不要提任何人的名字,就像他一样说是学雷锋做好事就行了。”
“做了好事不留名,坚持不懈三年多,真让人佩服呀,受教育了。”大鸟感慨地说。
8.2简单工厂模式实现
“好了,我来是为了请教你一个问题,这几天一直在研究工厂方法模式,但还是不太理解它和简单工厂的区别,感觉还不如简单工厂方便,为什么要用这个模式,到底这个模式的精翻在哪里?”
“哈,你刚才讲的故事不就是最好的工厂方法的样例吗?”
“薛磊风的事?怎么讲?”
“那你先把简单工厂模式和工厂方法模式的典型实现说给我听听。”
“哦,简单工厂模式实现是这样的。”
“首先简单工厂模式,若以我写的计算器为例,结构图如下。”
- //简单运算工厂类
- public class OperationFactory
- {
- public static Operation createOperation(String operate)
- {
- Operation oper = null;
- if ("+".equals(operate))
- oper = new OperationAdd();
- else if ("-".equals(operate))
- oper = new OperationSub();
- else if ("*".equals(operate))
- oper = new OperationMul();
- else if ("/".equals(operate))
- oper = new OperationDiv();
- return oper;
- }
- }
- //客户端类
- Operation oper = OperationFactory.createOperation(operator);
- oper.setNumberA(numberA);
- oper.setNumberB(numberB);
- double result = oper.getResult();
8.3工厂方法模式实现
“那如果换成工厂方法模式来写这个计算器,你能写吗?”
“当然可以,就是因为我写出来了,才感觉好像工厂方法没什么好处!我写给你看。”
“计算器的工厂方法模式实现的结构图是这样的。”
- //工厂接口
- public interface IFactory
- {
- Operation createOperation();
- }
- //加减乘除各建立一个具体工厂去实现这个接口
- public class AddFactory implements IFactory
- {
- public Operation createOperation()
- {
- return new OperationAdd();
- }
- }
- public class SubFactory implements IFactory
- {
- public Operation createOperation()
- {
- return new OperationSub();
- }
- }
- public class MulFactory implements IFactory
- {
- public Operation createOperation()
- {
- return new OperationMul();
- }
- }
- public class DivFactory implements IFactory
- {
- public Operation createOperation()
- {
- return new OperationDiv();
- }
- }
- //客户端的代码
- public class Main
- {
- public static void main(String[] args)
- {
- IFactory operFactory = new AddFactory();
- Operation oper = operFactory.createOperation();
- oper.setNumberA(1);
- oper.setNumberB(2);
- try
- {
- double result = oper.getResult();
- System.out.println("结果是:" + result);
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- }
- }
“对啊,写的很好。”大鸟说,“工厂方法模式就是这样写的,你有什么问题?”
8.4简单工厂VS工厂方法
“怪就怪在这里啊,以前我们不是说过嘛,如果我现在需要增加其他运算,比如求M数的N次方,或者求M数的N次方根,这些功能的增加,在简单工厂里,我是先去加‘求M数的N次方’功能类,然后去更改工厂方法,当中加Case或if语句来做判断,现在用了工厂方法,加功能类没问题,再加相关的工厂类,这也没问题,但要我再去更改客户端,这不等于不但没有简化难度,反而增加了很多类和方法,把复杂性增加了嘛?为什么要这样?”
“问的好,这其实就是工厂方法模式和简单工厂的区别所在。简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。就像你的计算器,让客户端不用管该用哪个类的实例,只需要把‘+’给工厂,工厂自动就给出了相应的实例,客户端只要去做运算就可以了,不同的实例会实现不同的运算。但问题也就在这里,如你所说,如果要加一个‘求M数的N次方’的功能,我们是一定需要给运算工厂类的方法里加Case或if的分支条件的,修改原有的类?这可不是好办法,这就等于说,我们不但对扩展开放了,对修改也开放了,这样就违背了什么原则?”
“哦,是的,违背的是开放-封闭原则。”
“对,于是工厂方法就来了。”
工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。工厂方法模式结构图。
“我们讲过,既然这个工厂类与分支耦合,那么我就对它下手,根据依赖倒转原则,我们把工厂类抽象出一个接口,这个接口只有一个方法,就是创建抽象产品的工厂方法。然后,所有的要生产具体类的工厂,就去实现这个接口,这样,一个简单工厂模式的工厂类,变成一个工厂抽象接口和多个具体生成对象的工厂,于是我们要增加‘求M数的N次方’的功能时,就不需要更改原有的工厂类了,只需要增加此功能的运算类和相应的工厂类就可以了。”
“这样整个工厂和产品体系其实都没有修改的变化,而只是扩展的变化,这就完全符合了开放-封闭原则的精神。”
“哦,工厂方法从这个角度来讲,的确要比简单工厂模式来得强。”
“其实你仔细观察就会发现,工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂内部的逻辑判断移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在要改客户端了!”
“这也是我困惑的地方。”
8.4雷锋工厂
“这个我们过会再讲,以你刚才讲的故事来说吧,雷锋是人人皆知的做好人好事的模范,而班级的这位薛磊风同学以学习雷锋的名义做好事,而你们现在需要去代替他做好事,这其实就是典型的工厂方法模式应用了。”
“哦,你说来听听。”
“首先,薛磊风作为一个大学生,以学雷锋做好事的名义去帮助老人做事,这里如何设计?”
“我的想法是这样的:雷锋类拥扫地、洗衣、买米等方法。”
- //雷锋
- public class LeiFeng
- {
- public void sweep()
- {
- System.out.println("扫地");
- }
- public void wash()
- {
- System.out.println("洗衣");
- }
- public void buyRice()
- {
- System.out.println("买米");
- }
- }
- //学雷锋的大学生类,继承雷锋
- public class Undergraduate extends LeiFeng
- {
- }
- //客户端代码
- public class Main
- {
- public static void main(String[] args)
- {
- LeiFeng xueleifeng = new Undergraduate();
- xueleifeng.buyRice();
- xueleifeng.sweep();
- xueleifeng.wash();
- }
- }
“小菜写的不错,现在假设你们有三个人要去代替他做这些事,那应该怎样写?”
“那就应该实例化三个学雷锋的大学生对象了。”
- LeiFeng student1 = new Undergraduate();
- student1.buyRice();
- LeiFeng student2 = new Undergraduate();
- student2.sweep();
- LeiFeng student3 = new Undergraduate();
- student3.wash();
“你们都是要毕业的,而帮助老人却是长期工作,所以‘社区志愿者’更合适,此时这样的写法就非常的不合适了,因为我们需要更改多个实例化的地方。”
“是啊,其实老人不需要知道是谁来做好事,他只需要知道是学雷锋的人来帮忙就可以了。所以还需要增加一个继承‘雷锋’类的‘社区志愿者’类。”
- //社区志愿者
- public class Volunteer extends LeiFeng
- {
- }
- //简单工厂类
- public class SimpleFactory
- {
- public static LeiFeng createLeiFeng(String type)
- {
- LeiFeng result = null;
- if ("学雷锋的大学生".equals(type))
- {
- result = new Undergraduate();
- }
- else if ("社区志愿者".equals(type))
- {
- result = new Volunteer();
- }
- return result;
- }
- }
- //客户端代码,如果要换,就只需要换“学雷锋的大学生”为“社区志愿者”
- LeiFeng studentA = SimpleFactory.createLeiFeng("学雷锋的大学生");
- studentA.buyRice();
- LeiFeng studentB = SimpleFactory.createLeiFeng("学雷锋的大学生");
- studentB.sweep();
- LeiFeng studentC = SimpleFactory.createLeiFeng("学雷锋的大学生");
- studentC.wash();
“好,此时你就发现了,你需要在任何实例化的时候写出这个工厂的代码。这里有重复,也就有了坏味道。你再用工厂方法模式写一遍。”
- //雷锋工厂
- public interface IFactory
- {
- LeiFeng createLeiFeng();
- }
- //学雷锋的大学生工厂
- public class UndergraduateFactory implements IFactory
- {
- public LeiFeng createLeiFeng()
- {
- return new Undergraduate();
- }
- }
- //社区志愿者工厂
- public class VolunteerFactory implements IFactory
- {
- public LeiFeng createLeiFeng()
- {
- return new Volunteer();
- }
- }
- //客户端代码
- public class Main
- {
- public static void main(String[] args)
- {
- IFactory factory = new UndergraduateFactory();
- LeiFeng student = factory.createLeiFeng();
- student.buyRice();
- student.sweep();
- student.wash();
- }
- }
“我明白了,尽管如果要换成‘社区志愿者’也还是要修改代码,但是只需要修改一处就可以了,这是最佳的做法。”
“最佳做法?NOP,NOP,NOP,这不是最好的,不过应该算是比较好的了。你现在明白什么时候用简单工厂模式,什么时候用工厂方法模式了没?”
“我感觉啊,工厂方法模式克服了简单工厂违背开放-封闭原则的缺点,又保持了封闭对象创建过程的优点。”
“说的好,它们都是集中封闭了对象的创建,使得要更换对象时,不需要做大的改动就可以实现,降低客户程序与产品对象的耦合。工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。但缺点是由于每加一个产品,就需要加一个产品工厂的类,增加了额外的开发量。”
“你说这不是最佳做法?难道说还有更好的?还有就是这样还是没有避免修改客户端的代码啊!”
“哈,之前我就提到过,利用‘反射’可以解决避免分支判断的问题。不过今天还是不急,等以后再谈。你明天不是还要去看望老人家吗?洗洗睡吧,这年头,活雷锋太少了,你们班那个薛磊风,实在是太不容易了,要是有雷锋工厂该有多好啊。”
“雷锋工厂!雷锋工厂!是啊,如果每个人都有雷锋精神,这世界该有多么美好啊…”小菜念叨着,不觉中唱到:“学习雷锋,好榜样,忠于革命忠于党,爱憎分明不忘本,立场坚定斗志强…”