【Java重构系列】重构31式之封装集合

转载自:http://blog.csdn.net/lovelion/article/details/17970147 java

2009年,Sean Chambers在其博客中发表了31 Days of Refactoring: Useful refactoring techniques you have to know系列文章,天天发布一篇,介绍一种重构手段,连续发文31篇,故得名“重构三十一天:你应该掌握的重构手段”。此外,Sean Chambers还将这31篇文章【即31种重构手段】整理成一本电子书, 如下是博客原文连接和电子书下载地址:编程

       博客原文:http://lostechies.com/seanchambers/2009/07/31/31-days-of-refactoring/
编程语言

       电子书下载地址:http://lostechies.com/wp-content/uploads/2011/03/31DaysRefactoring.pdfide


      本系列博客将基于Sean Chambers的工做,可是更换了编程语言(C# --> Java),更重要的是增长了不少新的内容,融入了大量Sunny对这些重构手段的理解,实例更加完整,分析也更为深刻,此外,还引伸出一些新的讨论话题,但愿可以帮助你们写出更高质量的程序代码!学习


      这三十一种重构手段罗列以下【注:原文是重构第N天,即Refactoring Day N,Sunny我的以为有些重构很是简单,一天一种太少,不过瘾,因而重命名(Rename)为重构第N式微笑,计划对这31种重构手段用Java语言从新介绍一遍,介绍次序与原文次序并不彻底一致,补充了不少新的内容,大笑】:网站

    Refactoring 1: Encapsulate Collection【重构第一式:封装集合】this

    Refactoring 2: Move Method【重构第二式:搬移方法】spa

    Refactoring 3: Pull Up Method【重构第三式:上移方法】.net

    Refactoring 4: Pull Up Field【重构第四式:上移字段】翻译

    Refactoring 5: Push Down Method【重构第五式:下移方法】

    Refactoring 6: Push Down Field【重构第六式:下移字段】

    Refactoring 7: Rename(method,class,parameter)【重构第七式:重命名(方法,类,参数)】

    Refactoring 8: Replace Inheritance with Delegation【重构第八式:用委托取代继承】

    Refactoring 9: Extract Interface【重构第九式:提取接口】

    Refactoring 10: Extract Method【重构第十式:提取方法】

    Refactoring 11: Switch to Strategy【重构第十一式:重构条件语句为策略模式】

    Refactoring 12: Break Dependencies【重构第十二式:消除依赖】

    Refactoring 13: Extract Method Object【重构第十三式:提取方法对象】

    Refactoring 14: Break Responsibilities【重构第十四式:分离职责】

    Refactoring 15: Remove Duplication【重构第十五式:去除重复代码】

    Refactoring 16: Encapsulate Conditional【重构第十六式:封装条件表达式】

    Refactoring 17: Extract Superclass【重构第十七式:提取父类】

    Refactoring 18: Replace exception with conditional【重构第十八式:用条件语句取代异常】

    Refactoring 19: Extract Factory Class【重构第十九式:提取工厂类】

    Refactoring 20: Extract Subclass【重构第二十式:提取子类】

    Refactoring 21: Collapse Hierarchy【重构第二十一式:合并继承层次结构】

    Refactoring 22: Break Method【重构第二十二式:分解方法】

    Refactoring 23: Introduce Parameter Object【重构第二十三式:引入参数对象】

    Refactoring 24: Remove Arrowhead Antipattern【重构第二十四式:去除复杂的嵌套条件判断】

    Refactoring 25: Introduce Design By Contract checks【重构第二十五式:引入契约式设计验证】

    Refactoring 26: Remove Double Negative【重构第二十六式:消除双重否认】

    Refactoring 27: Remove God Classes【重构第二十七式:去除上帝类】

    Refactoring 28: Rename boolean method【重构第二十八式:重命名布尔方法】

    Refactoring 29: Remove Middle Man【重构第二十九式:去除中间人】

    Refactoring 30: Return ASAP【重构第三十式:尽快返回】

    Refactoring 31: Replace conditional with Polymorphism【重构第三十一式:用多态取代条件语句】


      在英文原文中提供了C#版的重构实例,对重构手段的描述较为精简,Sunny将这些实例都改成了Java版本,并结合我的理解对实例代码和重构描述进行了适当的补充和完善。在本系列文章写做过程当中,参考了麒麟.NET的翻译版本《31天重构速成 :你必须知道的重构技巧》以及圣殿骑士(Knights Warrior)《31天重构学习笔记》,在此表示感谢!微笑


----------------------------------------------------------------------------------------------------------------------------------------


重构第一式:封装集合 (Refactoring 1: Encapsulate Collection)


       咱们知道,对属性和方法的封装能够经过设置它们的可见性来实现,可是对于集合,如何进行封装呢?疑问

       本重构提供了一种向类的使用者(客户端)隐藏类中集合的方法,既可让客户类可以访问到集合中的元素,可是又不让客户类直接修改集合的内容,尤为是在原有类的addXXX()方法和removeXXX()方法中还包含一些其余代码逻辑时,若是将集合暴露给其余因此类,且容许这些类来直接修改集合,将致使在addXXX()方法和removeXXX()方法中新增业务逻辑失效。

       下面举一个例子来加以说明:

【重构实例】

       电子商务网站一般会有订单管理功能,用户能够查看每张订单(Order)的详情,也能够根据须要添加和删除订单中的订单项(OrderItem)。所以在Order类中定义了一个集合用于存储多个OrderItem。

       重构以前的代码片断以下:

[java]  view plain  copy
  在CODE上查看代码片 派生到个人代码片
  1. package sunny.refactoring.one.before;  
  2.   
  3. import java.util.Collection;  
  4. import java.util.ArrayList;  
  5.   
  6. //订单类  
  7. class Order {  
  8.     private double orderTotal; //订单总金额  
  9.     private Collection<OrderItem> orderItems; //集合对象,存储一个订单中的全部订单项  
  10.       
  11.     public Order() {  
  12.         this.orderItems = new ArrayList<OrderItem>();  
  13.     }  
  14.       
  15.     //返回订单项集合  
  16.     public Collection<OrderItem> getOrderItems() {  
  17.         return this.orderItems;  
  18.     }  
  19.       
  20.     //返回订单总金额  
  21.     public double getOrderTotal() {  
  22.         return this.orderTotal;  
  23.     }  
  24.       
  25.     //增长订单项,同时增长订单总金额  
  26.     public void addOrderItem(OrderItem orderItem) {  
  27.         this.orderTotal += orderItem.getTotalPrice();  
  28.         orderItems.add(orderItem);  
  29.     }  
  30.       
  31.     //删除订单项,同时减小订单总金额  
  32.     public void removeOrderItem(OrderItem orderItem) {  
  33.         this.orderTotal -= orderItem.getTotalPrice();  
  34.         orderItems.remove(orderItem);  
  35.     }  
  36. }  
  37.   
  38. //订单项类,省略了不少属性  
  39. class OrderItem {  
  40.     private double totalPrice; //订单项商品总价格  
  41.       
  42.     public OrderItem() {  
  43.     }  
  44.       
  45.     public OrderItem(double totalPrice) {  
  46.         this.totalPrice = totalPrice;  
  47.     }  
  48.       
  49.     public void setTotalPrice(double totalPrice) {  
  50.         this.totalPrice = totalPrice;  
  51.     }  
  52.       
  53.     public double getTotalPrice() {  
  54.         return this.totalPrice;  
  55.     }  
  56. }  
  57.   
  58. class Client {  
  59.     public static void main(String args[]) {  
  60.         OrderItem orderItem1 = new OrderItem(116.00);  
  61.         OrderItem orderItem2 = new OrderItem(234.00);  
  62.         OrderItem orderItem3 = new OrderItem(58.00);  
  63.           
  64.         Order order = new Order();  
  65.         order.addOrderItem(orderItem1);  
  66.         order.addOrderItem(orderItem2);  
  67.         order.addOrderItem(orderItem3);  
  68.           
  69.         //获取订单类中的订单项集合  
  70.         Collection<OrderItem> orderItems = order.getOrderItems();  
  71.           
  72.         System.out.print("订单中各订单项的价格分别为:");  
  73.         for (Object obj : orderItems) {  
  74.             System.out.print(((OrderItem)obj).getTotalPrice() + ",");  
  75.         }  
  76.         System.out.println("订单总金额为" + order.getOrderTotal());  
  77.   
  78.         //经过订单项集合对象的add()方法增长新订单  
  79.         orderItems.add(new OrderItem(100.00));  
  80.           
  81.         System.out.print("订单中各订单项的价格分别为:");  
  82.         for (Object obj : orderItems) {  
  83.             System.out.print(((OrderItem)obj).getTotalPrice() + ",");  
  84.         }  
  85.         System.out.println("增长新项后订单总金额为" + order.getOrderTotal());  
  86.     }  
  87. }  

       输出结果以下:

订单中各订单项的价格分别为:116.0,234.0,58.0,订单总金额为408.0

订单中各订单项的价格分别为:116.0,234.0,58.0,100.0,增长新项后订单总金额为408.0

       不难发现,第二句输出结果是有问题的,在增长了新项后订单的总金额竟然没有发生改变,仍是408.0,可是新项却又可以增长成功。缘由很简单,由于返回了Collection类型的订单项集合对象,能够直接使用在Collection接口中声明的add()方法来增长元素,而绕过了在Order类的addOrderItem()方法中统计订单总金额的代码,致使订单项增长成功,可是总金额并无变化。

       在此,客户端只须要遍历访问一个集合对象中的元素,而此时却提供了一个Collection集合对象,它具备对集合的全部操做,这将给程序带来不少隐患,由于使用Order类的用户并不知道在Order类的addOrderItem()方法中还有一些额外的代码,而会习惯性地使用Collection提供的add()方法增长元素。面对这种状况,最好的作法固然是重构。

       如何重构?

       咱们须要对存储订单项的集合对象orderItems进行封装,只容许客户端遍历该集合中的元素,而不容许客户端修改集合中的元素,全部的修改都只能经过Order类统一进行。


       下面提供两种重构方案:

      重构方案一将Collection改为Iterable,由于在java.lang. Iterable接口中只提供了一个返回迭代器Iterator对象的iterator()方法,没有提供add()、remove()等修改为员的方法。所以,只能遍历集合中的元素,而不能对集合进行修改,这不正是咱们想看到的吗?

       代码片断以下(考虑到篇幅,省略了一些相同的代码):

[java]  view plain  copy
  在CODE上查看代码片 派生到个人代码片
  1. package sunny.refactoring.one.after;  
  2. ……  
  3. class Order {  
  4.     ……  
  5.     //将getOrderItems()的返回类型改成Iterable  
  6.     public Iterable<OrderItem> getOrderItems() {  
  7.         return this.orderItems;  
  8.     }  
  9.     ……  
  10. }  
  11.   
  12. class OrderItem {  
  13.     ……  
  14. }  
  15.   
  16. class Client {  
  17.     public static void main(String args[]) {  
  18.         OrderItem orderItem1 = new OrderItem(116.00);  
  19.         OrderItem orderItem2 = new OrderItem(234.00);  
  20.         OrderItem orderItem3 = new OrderItem(58.00);  
  21.           
  22.         Order order = new Order();  
  23.         order.addOrderItem(orderItem1);  
  24.         order.addOrderItem(orderItem2);  
  25.         order.addOrderItem(orderItem3);  
  26.           
  27.         //获取Iterable<OrderItem>类型的订单项集合对象  
  28.         Iterable<OrderItem> orderItems = order.getOrderItems();  
  29.           
  30.         Iterator<OrderItem> iterator = orderItems.iterator();  
  31.         System.out.print("订单中各订单项的价格分别为:");  
  32.         while(iterator.hasNext()) {  
  33.             System.out.print(((OrderItem)iterator.next()).getTotalPrice() + ",");  
  34.         }  
  35.         System.out.println("订单总金额为" + order.getOrderTotal());  
  36.   
  37.         //没法访问Order中的集合,Iterable没有提供add()方法,只能经过Order的addOrderItem()方法增长新元素  
  38.         order.addOrderItem(new OrderItem(100.00));  
  39.           
  40.         Iterator<OrderItem> iteratorNew = orderItems.iterator();  
  41.         System.out.print("订单中各订单项的价格分别为:");  
  42.         while(iteratorNew.hasNext()) {  
  43.             System.out.print(((OrderItem)iteratorNew.next()).getTotalPrice() + ",");  
  44.         }  
  45.         System.out.println("增长新项后订单总金额为" + order.getOrderTotal());  
  46.     }  
  47. }  

       输出结果以下:

订单中各订单项的价格分别为:116.0,234.0,58.0,订单总金额为408.0

订单中各订单项的价格分别为:116.0,234.0,58.0,100.0,增长新项后订单总金额为508.0

       

       重构方法二:将getOrderItemsIterator()方法的返回类型改成Iterator<OrderItem>,不直接返回集合对象,而是返回遍历集合对象的迭代器,客户端使用迭代器来遍历集合而不能直接操做集合中的元素。

       代码片断以下:

[java]  view plain  copy
  在CODE上查看代码片 派生到个人代码片
  1. package sunny.refactoring.one.after;  
  2. ……  
  3. import java.util.Iterator;  
  4. ……  
  5. class Order {  
  6.     ……  
  7.     //返回遍历orderItems对象的迭代器  
  8.     public Iterator<OrderItem> getOrderItemsIterator() {  
  9.         return orderItems.iterator();  
  10.     }  
  11.     ……  
  12. }  
  13.   
  14. class OrderItem {  
  15.     ……  
  16. }  
  17.   
  18. class Client {  
  19.     public static void main(String args[]) {  
  20.         OrderItem orderItem1 = new OrderItem(116.00);  
  21.         OrderItem orderItem2 = new OrderItem(234.00);  
  22.         OrderItem orderItem3 = new OrderItem(58.00);  
  23.           
  24.         Order order = new Order();  
  25.         order.addOrderItem(orderItem1);  
  26.         order.addOrderItem(orderItem2);  
  27.         order.addOrderItem(orderItem3);  
  28.   
  29.         //获取遍历订单项集合对象的迭代器  
  30.         Iterator<OrderItem> iterator = order.getOrderItemsIterator();  
  31.         System.out.print("订单中各订单项的价格分别为:");  
  32.         while(iterator.hasNext()) {  
  33.             System.out.print(((OrderItem)iterator.next()).getTotalPrice() + ",");  
  34.         }  
  35.         System.out.println("订单总金额为" + order.getOrderTotal());  
  36.   
  37.         //没法访问Order中的集合,只能经过Order的addOrderItem()方法增长新元素  
  38.         order.addOrderItem(new OrderItem(100.00));  
  39.           
  40.         Iterator<OrderItem> iteratorNew = order.getOrderItemsIterator();  
  41.         System.out.print("订单中各订单项的价格分别为:");  
  42.         while(iteratorNew.hasNext()) {  
  43.             System.out.print(((OrderItem)iteratorNew.next()).getTotalPrice() + ",");  
  44.         }  
  45.         System.out.println("订单总金额为" + order.getOrderTotal());  
  46.     }  
  47. }  

       输出结果以下:

订单中各订单项的价格分别为:116.0,234.0,58.0,订单总金额为408.0

订单中各订单项的价格分别为:116.0,234.0,58.0,100.0,订单总金额为508.0

      

       上述两种重构手段均可以防止客户端直接调用集合类的那些修改集合对象的方法(如add()和remove()等等),不管是返回Iterable类型的对象仍是返回Iterator类型的对象,客户端都只能遍历集合,而不能改变集合,从而达到了封装集合的目的。

 

重构心得

       说实话,Sunny以为该重构用得并非特别普遍(与其余使用更为频繁的重构手段相比),可是这种封装的思想很重要,让客户端“可以看到该看到的,不应看的必定看不到”。咱们在设计和实现类时,必定要多思考每个属性和方法以及类自己的封装性,这样能够减小一些未来使用上的不便。实质上,迭代器模式引入的目的之一就是为了更好地实现聚合对象的封装性,聚合对象负责存储数据,而遍历数据的职责交给迭代器来完成,增长和修改遍历方法无须修改原有聚合对象,用户也不能经过迭代器来修改聚合对象中的元素,而仅仅只是使用这些元素。封装自己就是一种很重要的编程技巧。微笑