《大话设计模式》之--第8章 雷锋依然在人间----工厂方法模式

来源:互联网 发布:windows获得system 编辑:程序博客网 时间:2024/05/18 23:26

http://blog.csdn.net/monkey_d_meng/article/details/5694353


第8章 雷锋依然在人间----工厂方法模式

8.1再现活雷锋

小菜来找大鸟,说:“今天我们见到活雷锋了。”

“哦,”大鸟感兴趣道,“现在已经很少提这个人名了,说说看。”

“我们班有个同学叫薛磊风,昨天,他出了车祸,被车撞断了腿,医生说没大碍,可以恢复。”小菜说,“四年来,他做人很低调,只听说他时常去勤工俭学,周末一般就看不到他,别的也没感觉到什么。”

“他不太走运,不过所谓‘常在路上走,哪有不碰车。’被车撞也是没办法的。”

“是呀,我们今天白天去医院看望他,却听到了一个非常感人的故事,应该说,像是在电视里才能看到的真人真事。”

“他做了感人的好事了!”

“你怎么知道,哦,我告诉你见到活雷锋的。你别打岔。”小菜不满地说,“是这样的,他这几年,都一直在帮助一个孤寡老人,每周都去老人家里,为老人洗衣扫地、买米买油,三年多,一直这样,因为他家住在广西很偏远的地方,到了南宁还要再坐几天的汽车,所以他四年来没有回过老家,也正因为此,他对这位老人家的帮助从没有停止过。”

“可是现在出了意外,他住进医院,不能帮助他人了。”大鸟还是打岔道,“这种事你骗谁呀,这么多孤寡老人,他为什么只帮助这一位?”

“你再打岔我就不说了,你不信也听听,接受一下再教育,我今天是受教育了。”小菜接着说。“他在大一的时候,从收音机里听到说,有位孤老早年丧妻,而唯一拉扯大的儿子在八十年代中越战争时牺牲了,而他正是从小生活在离战争不远的边防小镇,目睹过战争的残酷和解放军的无私奉献。所以他一听到这个消息就主动去为老人提供帮助。老人身体不好,尽管有些社会的救济,但在生活上还是不太方便,于是他帮助老人就成了每周末的功课。”

“哦,奉献精神代代传呀。”

“现在他住院了,至少近几个月都没办法去老人家,所以他委托我们去帮他继续做这件好事,此时我们大家才知道,雷锋原来就在我们身边。”

“不容易,在21世纪,我还以为这样的人越来越少了呢,原来乐于助人的事迹还是比比皆是。”

“明天我和几名同学就去老人家。不过老人不认识我们,薛磊风也交待我们不要提任何人的名字,就像他一样说是学雷锋做好事就行了。”

“做了好事不留名,坚持不懈三年多,真让人佩服呀,受教育了。”大鸟感慨地说。

8.2简单工厂模式实现

“好了,我来是为了请教你一个问题,这几天一直在研究工厂方法模式,但还是不太理解它和简单工厂的区别,感觉还不如简单工厂方便,为什么要用这个模式,到底这个模式的精翻在哪里?”

“哈,你刚才讲的故事不就是最好的工厂方法的样例吗?”

“薛磊风的事?怎么讲?”

“那你先把简单工厂模式和工厂方法模式的典型实现说给我听听。”

“哦,简单工厂模式实现是这样的。”

“首先简单工厂模式,若以我写的计算器为例,结构图如下。”

 

[java] view plaincopy
  1. //简单运算工厂类  
  2. public class OperationFactory  
  3. {  
  4.     public static Operation createOperation(String operate)  
  5.     {  
  6.         Operation oper = null;  
  7.   
  8.         if ("+".equals(operate))  
  9.             oper = new OperationAdd();  
  10.         else if ("-".equals(operate))  
  11.             oper = new OperationSub();  
  12.         else if ("*".equals(operate))  
  13.             oper = new OperationMul();  
  14.         else if ("/".equals(operate))  
  15.             oper = new OperationDiv();  
  16.   
  17.         return oper;  
  18.     }  
  19. }  
  20.   
  21. //客户端类  
  22. Operation oper = OperationFactory.createOperation(operator);  
  23. oper.setNumberA(numberA);  
  24. oper.setNumberB(numberB);  
  25. double result = oper.getResult();  

8.3工厂方法模式实现

“那如果换成工厂方法模式来写这个计算器,你能写吗?”

“当然可以,就是因为我写出来了,才感觉好像工厂方法没什么好处!我写给你看。”

“计算器的工厂方法模式实现的结构图是这样的。”

[java] view plaincopy
  1. //工厂接口  
  2. public interface IFactory  
  3. {  
  4.     Operation createOperation();  
  5. }  
  6. //加减乘除各建立一个具体工厂去实现这个接口  
  7. public class AddFactory implements IFactory  
  8. {  
  9.     public Operation createOperation()  
  10.     {  
  11.         return new OperationAdd();  
  12.     }  
  13. }  
  14. public class SubFactory implements IFactory  
  15. {  
  16.     public Operation createOperation()  
  17.     {  
  18.         return new OperationSub();  
  19.     }  
  20. }  
  21. public class MulFactory implements IFactory  
  22. {  
  23.     public Operation createOperation()  
  24.     {  
  25.         return new OperationMul();  
  26.     }  
  27. }  
  28. public class DivFactory implements IFactory  
  29. {  
  30.     public Operation createOperation()  
  31.     {  
  32.         return new OperationDiv();  
  33.     }  
  34. }  
  35. //客户端的代码  
  36. public class Main  
  37. {  
  38.     public static void main(String[] args)  
  39.     {  
  40.         IFactory operFactory = new AddFactory();  
  41.         Operation oper = operFactory.createOperation();  
  42.   
  43.         oper.setNumberA(1);  
  44.         oper.setNumberB(2);  
  45.   
  46.         try  
  47.         {  
  48.             double result = oper.getResult();  
  49.             System.out.println("结果是:" + result);  
  50.         }  
  51.         catch (Exception e)  
  52.         {  
  53.             e.printStackTrace();  
  54.         }  
  55.     }  
  56. }  

“对啊,写的很好。”大鸟说,“工厂方法模式就是这样写的,你有什么问题?”

8.4简单工厂VS工厂方法

“怪就怪在这里啊,以前我们不是说过嘛,如果我现在需要增加其他运算,比如求M数的N次方,或者求M数的N次方根,这些功能的增加,在简单工厂里,我是先去加‘求M数的N次方’功能类,然后去更改工厂方法,当中加Case或if语句来做判断,现在用了工厂方法,加功能类没问题,再加相关的工厂类,这也没问题,但要我再去更改客户端,这不等于不但没有简化难度,反而增加了很多类和方法,把复杂性增加了嘛?为什么要这样?”

“问的好,这其实就是工厂方法模式和简单工厂的区别所在。简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。就像你的计算器,让客户端不用管该用哪个类的实例,只需要把‘+’给工厂,工厂自动就给出了相应的实例,客户端只要去做运算就可以了,不同的实例会实现不同的运算。但问题也就在这里,如你所说,如果要加一个‘求M数的N次方’的功能,我们是一定需要给运算工厂类的方法里加Case或if的分支条件的,修改原有的类?这可不是好办法,这就等于说,我们不但对扩展开放了,对修改也开放了,这样就违背了什么原则?”

“哦,是的,违背的是开放-封闭原则。”

“对,于是工厂方法就来了。”

工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。工厂方法模式结构图。

“我们讲过,既然这个工厂类与分支耦合,那么我就对它下手,根据依赖倒转原则,我们把工厂类抽象出一个接口,这个接口只有一个方法,就是创建抽象产品的工厂方法。然后,所有的要生产具体类的工厂,就去实现这个接口,这样,一个简单工厂模式的工厂类,变成一个工厂抽象接口和多个具体生成对象的工厂,于是我们要增加‘求M数的N次方’的功能时,就不需要更改原有的工厂类了,只需要增加此功能的运算类和相应的工厂类就可以了。”

“这样整个工厂和产品体系其实都没有修改的变化,而只是扩展的变化,这就完全符合了开放-封闭原则的精神。”

“哦,工厂方法从这个角度来讲,的确要比简单工厂模式来得强。”

“其实你仔细观察就会发现,工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂内部的逻辑判断移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在要改客户端了!”

“这也是我困惑的地方。”

8.4雷锋工厂

“这个我们过会再讲,以你刚才讲的故事来说吧,雷锋是人人皆知的做好人好事的模范,而班级的这位薛磊风同学以学习雷锋的名义做好事,而你们现在需要去代替他做好事,这其实就是典型的工厂方法模式应用了。”

“哦,你说来听听。”

“首先,薛磊风作为一个大学生,以学雷锋做好事的名义去帮助老人做事,这里如何设计?”

“我的想法是这样的:雷锋类拥扫地、洗衣、买米等方法。”

[java] view plaincopy
  1. //雷锋  
  2. public class LeiFeng  
  3. {  
  4.     public void sweep()  
  5.     {  
  6.         System.out.println("扫地");  
  7.     }  
  8.   
  9.     public void wash()  
  10.     {  
  11.         System.out.println("洗衣");  
  12.     }  
  13.   
  14.     public void buyRice()  
  15.     {  
  16.         System.out.println("买米");  
  17.     }  
  18. }  
  19. //学雷锋的大学生类,继承雷锋  
  20. public class Undergraduate extends LeiFeng  
  21. {  
  22.   
  23. }  
  24. //客户端代码  
  25. public class Main  
  26. {  
  27.     public static void main(String[] args)  
  28.     {  
  29.         LeiFeng xueleifeng = new Undergraduate();  
  30.   
  31.         xueleifeng.buyRice();  
  32.         xueleifeng.sweep();  
  33.         xueleifeng.wash();  
  34.     }  
  35. }  

“小菜写的不错,现在假设你们有三个人要去代替他做这些事,那应该怎样写?”

“那就应该实例化三个学雷锋的大学生对象了。”

[java] view plaincopy
  1. LeiFeng student1 = new Undergraduate();  
  2. student1.buyRice();  
  3.   
  4. LeiFeng student2 = new Undergraduate();  
  5. student2.sweep();  
  6.   
  7. LeiFeng student3 = new Undergraduate();  
  8. student3.wash();  

“你们都是要毕业的,而帮助老人却是长期工作,所以‘社区志愿者’更合适,此时这样的写法就非常的不合适了,因为我们需要更改多个实例化的地方。”

“是啊,其实老人不需要知道是谁来做好事,他只需要知道是学雷锋的人来帮忙就可以了。所以还需要增加一个继承‘雷锋’类的‘社区志愿者’类。”

[java] view plaincopy
  1. //社区志愿者  
  2. public class Volunteer extends LeiFeng  
  3. {  
  4.   
  5. }  
  6. //简单工厂类  
  7. public class SimpleFactory  
  8. {  
  9.     public static LeiFeng createLeiFeng(String type)  
  10.     {  
  11.         LeiFeng result = null;  
  12.   
  13.         if ("学雷锋的大学生".equals(type))  
  14.         {  
  15.             result = new Undergraduate();  
  16.         }  
  17.         else if ("社区志愿者".equals(type))  
  18.         {  
  19.             result = new Volunteer();  
  20.         }  
  21.   
  22.         return result;  
  23.     }  
  24. }  
  25. //客户端代码,如果要换,就只需要换“学雷锋的大学生”为“社区志愿者”  
  26. LeiFeng studentA = SimpleFactory.createLeiFeng("学雷锋的大学生");  
  27. studentA.buyRice();  
  28.   
  29. LeiFeng studentB = SimpleFactory.createLeiFeng("学雷锋的大学生");  
  30. studentB.sweep();  
  31.           
  32. LeiFeng studentC = SimpleFactory.createLeiFeng("学雷锋的大学生");  
  33. studentC.wash();  

“好,此时你就发现了,你需要在任何实例化的时候写出这个工厂的代码。这里有重复,也就有了坏味道。你再用工厂方法模式写一遍。”

 

[java] view plaincopy
  1. //雷锋工厂  
  2. public interface IFactory  
  3. {  
  4.     LeiFeng createLeiFeng();  
  5. }  
  6. //学雷锋的大学生工厂  
  7. public class UndergraduateFactory implements IFactory  
  8. {  
  9.     public LeiFeng createLeiFeng()  
  10.     {  
  11.         return new Undergraduate();  
  12.     }  
  13. }  
  14. //社区志愿者工厂  
  15. public class VolunteerFactory implements IFactory  
  16. {  
  17.     public LeiFeng createLeiFeng()  
  18.     {  
  19.         return new Volunteer();  
  20.     }  
  21. }  
  22. //客户端代码  
  23. public class Main  
  24. {  
  25.     public static void main(String[] args)  
  26.     {  
  27.         IFactory factory = new UndergraduateFactory();  
  28.         LeiFeng student = factory.createLeiFeng();  
  29.   
  30.         student.buyRice();  
  31.         student.sweep();  
  32.         student.wash();  
  33.     }  
  34. }  

“我明白了,尽管如果要换成‘社区志愿者’也还是要修改代码,但是只需要修改一处就可以了,这是最佳的做法。”

“最佳做法?NOP,NOP,NOP,这不是最好的,不过应该算是比较好的了。你现在明白什么时候用简单工厂模式,什么时候用工厂方法模式了没?”

“我感觉啊,工厂方法模式克服了简单工厂违背开放-封闭原则的缺点,又保持了封闭对象创建过程的优点。”

“说的好,它们都是集中封闭了对象的创建,使得要更换对象时,不需要做大的改动就可以实现,降低客户程序与产品对象的耦合。工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。但缺点是由于每加一个产品,就需要加一个产品工厂的类,增加了额外的开发量。”

“你说这不是最佳做法?难道说还有更好的?还有就是这样还是没有避免修改客户端的代码啊!”

“哈,之前我就提到过,利用‘反射’可以解决避免分支判断的问题。不过今天还是不急,等以后再谈。你明天不是还要去看望老人家吗?洗洗睡吧,这年头,活雷锋太少了,你们班那个薛磊风,实在是太不容易了,要是有雷锋工厂该有多好啊。”

“雷锋工厂!雷锋工厂!是啊,如果每个人都有雷锋精神,这世界该有多么美好啊…”小菜念叨着,不觉中唱到:“学习雷锋,好榜样,忠于革命忠于党,爱憎分明不忘本,立场坚定斗志强…”


原创粉丝点击