Introduce Null Object

  今天继续总结《重构》这本书中的一个重构手法,Introduce Null Object。写这个手法是因为它确实很巧妙,在实际编程中经常会遇到这种情况,前人总结出来了这么一个经典的手法,当然还有由此手法扩展更普遍更经典的手法--Special Case。

  刚入行的时候,听“老人”给我讲,书是要越读越薄的。当时没什么感受,觉得“老人”在故弄玄虚。工作4年多来,发现看过不少书,烂熟于心的却是最初入门的那一本--郭天祥C51。买的是一本影印盗版书(在大学里嘛,那年大二),不停的翻,书都翻烂了。现在那本书也不知道去哪了,但C51的所有内容都清清楚楚了。学习是为了什么,无非是为了精进,为以后做准备嘛。

  扯远了,接下来半年的时间里,争取有时间就写写读书笔记吧。近几年看了好几本技术书籍,有些还看了好几遍。写下来的内容都是为了把学过的知识捋一遍。

  开始今天的内容吧,这个重构手法在一定的应用场景下很有用。先来看看其应用场景。

  重构前的主体代码:

  1 package com.nelson.io;
  2 
  3 public class Site {
  4     
  5     private Customer _customer;
  6     
  7     public Customer getCustomer()
  8     {
  9         return _customer;
 10     }
 11     
 12     public void setCustomer(Customer cus)
 13     {
 14         _customer = cus;
 15     }
 16     
 17 }
 18 
 19 class Customer
 20 {
 21     String _name;
 22     BillingPlan _billingplan;
 23     PaymentHistory _paymentHistory;
 24     
 25     public String getName()    {return _name;}
 26     
 27     public BillingPlan getPlan() {
 28         return _billingplan;
 29     }
 30     
 31     public PaymentHistory getHistroy(){
 32         return _paymentHistory;
 33     }
 34     
 35     public Customer(){
 36     }
 37     
 38     public Customer(String name,BillingPlan bill,PaymentHistory pay){
 39         _name = name;
 40         _billingplan = bill;
 41         _paymentHistory = pay;
 42     }
 43 }
 44 
 45 class BillingPlan
 46 {
 47     private int basicpay;
 48     private int extrapay;
 49     
 50     public BillingPlan(){
 51         setBasicpay(0);
 52         setExtrapay(0);
 53     }
 54     
 55     public BillingPlan(int pay)
 56     {
 57         setBasicpay(pay);
 58         setExtrapay(0);
 59     }
 60     
 61     public BillingPlan(int basic,int extra)
 62     {
 63         basicpay = basic;
 64         extrapay = extra;
 65     }
 66     
 67     static BillingPlan basic()
 68     {
 69         return new BillingPlan(100);   //最低消费100元
 70     }
 71     
 72     public int getTotalExpand()
 73     {
 74         return basicpay+extrapay;
 75     }
 76 
 77     int getExtrapay() {
 78         return extrapay;
 79     }
 80 
 81     void setExtrapay(int extrapay) {
 82         this.extrapay = extrapay;
 83     }
 84 
 85     int getBasicpay() {
 86         return basicpay;
 87     }
 88 
 89     void setBasicpay(int basicpay) {
 90         this.basicpay = basicpay;
 91     }
 92 }
 93 
 94 class PaymentHistory
 95 {
 96     public int getWeeksDelinquentInLastYear;
 97     
 98     public PaymentHistory()
 99     {
100         getWeeksDelinquentInLastYear = 0;
101     }
102 }
View Code

  重构前的应用代码:

 1 package com.nelson.io;
 2 
 3 public class HelloWorld {
 4 
 5     public static void main(String[] args) {
 6         
 7         System.out.println("Hello Java!");
 8 
 9         //不正常的客户
10         Site site = new Site();             //这样默认Site中的_customer = null
11         Customer cus1 = site.getCustomer();
12         
13         String strName;
14         if(cus1 == null) strName = "occupant";   //顾客名字暂时叫做occupant
15         else strName = cus1.getName();   //获取用户的名字
16         System.out.println("Current Customer1: "+strName);
17         
18         BillingPlan plan1;
19         if(cus1 == null) plan1 = BillingPlan.basic();
20         else plan1 = cus1.getPlan();
21         System.out.println("Total Expand:"+plan1.getTotalExpand());
22         
23         //////////////////////////////////////////////////////////
24         //正常的客户
25         BillingPlan plan2 = new BillingPlan(100,19);
26         PaymentHistory history2 = new PaymentHistory();
27         Customer cus= new Customer("xiaoming",plan2,history2);
28         site.setCustomer(cus);
29         
30         Customer cus2 = site.getCustomer();
31         if(cus2 == null) strName = "occupant";      //顾客名字暂时叫做occupant
32         else strName = cus2.getName();              //获取用户的名字
33         System.out.println("Current Customer2: "+strName);
34         
35         if(cus2 == null) plan1 = BillingPlan.basic();
36         else plan1 = cus2.getPlan();
37         System.out.println("Total Expand:"+plan1.getTotalExpand());
38         
39     }
40 
41 }
View Code

       在这里的重构要解决的问题是,应用代码中会不断的查询Site中的customer对象是否为空,这个应用的关键也在于允许customer=null的情况(这种情况存在不算错,而且必须应对这种情况)。Introduce Null Object手法就是去掉这里的重复查询是否为空的代码。

   重构后的主体代码:

  1 package com.nelson.io;
  2 
  3 public class Site {
  4     
  5     private Customer _customer;
  6     
  7     public Customer getCustomer()
  8     {
  9         return (_customer==null)?Customer.newNull():_customer;
 10     }
 11     
 12     public void setCustomer(Customer cus)
 13     {
 14         _customer = cus;
 15     }
 16     
 17 }
 18 
 19 class Customer implements Nullable
 20 {
 21     String _name;
 22     BillingPlan _billingplan;
 23     PaymentHistory _paymentHistory;
 24     
 25     public String getName()    {return _name;}
 26     
 27     public BillingPlan getPlan() {
 28         return _billingplan;
 29     }
 30     
 31     public PaymentHistory getHistroy(){
 32         return _paymentHistory;
 33     }
 34     
 35     protected Customer(){
 36     }
 37     
 38     public Customer(String name,BillingPlan bill,PaymentHistory pay){
 39         _name = name;
 40         _billingplan = bill;
 41         _paymentHistory = pay;
 42     }
 43     
 44     public static Customer newNull()
 45     {
 46         return new NullCustomer();
 47     }
 48 
 49     public boolean isNull() {
 50         return false;
 51     }
 52 }
 53 
 54 class NullCustomer extends Customer
 55 {
 56     public String getName()    {return "occupant";}
 57     
 58     public BillingPlan getPlan() {
 59         return BillingPlan.basic();
 60     }
 61     
 62     public boolean isNull() {
 63         return true;
 64     }
 65 }
 66 
 67 class BillingPlan
 68 {
 69     private int basicpay;
 70     private int extrapay;
 71     
 72     public BillingPlan(){
 73         setBasicpay(0);
 74         setExtrapay(0);
 75     }
 76     
 77     public BillingPlan(int pay)
 78     {
 79         setBasicpay(pay);
 80         setExtrapay(0);
 81     }
 82     
 83     public BillingPlan(int basic,int extra)
 84     {
 85         basicpay = basic;
 86         extrapay = extra;
 87     }
 88     
 89     static BillingPlan basic()
 90     {
 91         return new BillingPlan(100);   //最低消费100元
 92     }
 93     
 94     public int getTotalExpand()
 95     {
 96         return basicpay+extrapay;
 97     }
 98 
 99     int getExtrapay() {
100         return extrapay;
101     }
102 
103     void setExtrapay(int extrapay) {
104         this.extrapay = extrapay;
105     }
106 
107     int getBasicpay() {
108         return basicpay;
109     }
110 
111     void setBasicpay(int basicpay) {
112         this.basicpay = basicpay;
113     }
114 }
115 
116 class PaymentHistory
117 {
118     public int getWeeksDelinquentInLastYear;
119     
120     public PaymentHistory()
121     {
122         getWeeksDelinquentInLastYear = 0;
123     }
124 }
125 
126 interface Nullable
127 {
128     boolean isNull();
129 }
View Code

  重构后的测试代码:

 1 package com.nelson.io;
 2 
 3 public class HelloWorld {
 4 
 5     public static void main(String[] args) {
 6         
 7         System.out.println("Hello Java!");
 8 
 9         //////////////////////////////////////////////////////////////////
10         //不正常的客户
11         Site site = new Site();            //这样默认Site中的_customer = null
12         Customer cus1 = site.getCustomer();
13         String strName = cus1.getName();   //获取用户的名字
14         System.out.println("Current Customer1: "+strName);
15         BillingPlan plan1 = cus1.getPlan();
16         System.out.println("Total Expand:"+plan1.getTotalExpand());
17         
18         if(!cus1.isNull()) 
19             System.out.println("Customer1 is not null customer");
20         else
21             System.out.println("Customer1 is null customer");
22         
23         System.out.println();
24         
25         //////////////////////////////////////////////////////////
26         //正常的客户
27         BillingPlan plan2 = new BillingPlan(100,19);
28         PaymentHistory history2 = new PaymentHistory();
29         Customer cus= new Customer("xiaoming",plan2,history2);
30         site.setCustomer(cus);
31         
32         Customer cus2 = site.getCustomer();
33         strName = cus2.getName();              //获取用户的名字
34         System.out.println("Current Customer2: "+strName);
35         cus2.getPlan();
36         System.out.println("Total Expand:"+plan1.getTotalExpand());
37         
38         if(!cus2.isNull()) 
39             System.out.println("Customer2 is not null customer");
40         else
41             System.out.println("Customer2 is null customer");
42     }
43 
44 }
View Code

  重构后的测试代码中,减少了对customer是否为null的判断。当测试代码中出现很多这样的判断时,此项重构价值得以体现。而且这样的好处还有,如果程序中碰到一个对象为null,不加判断去调用对象的函数,将造成程序崩溃。而将null的情况封装为Null Object后,将永不会出现这种异常。程序运行更安全。测试代码中的调用方只管使用方法就好了,保证不出错。注意Customer和NullCustomer中都实现了Nullable接口,意在告诉调用方有NullCustomer类存在。如果调用方实在想知道谁是NullCustomer谁是Customer,通过接口函数就可以知道了。

  引申的Special Case模式讲的就是特例模式,当SIte中的Customer = null时也算一种特例,当Customer中的name = “”时也是一种特例,也可以定义UnknownCustomer。或者种种其他的特殊情况--程序运行过程中大部分情况都是正常的Customer,但偶尔出现NullCustomer或者UnknownCustomer,Null Object和Special Case 保证程序能处理“极端”情况。

posted @ 2017-10-16 22:31  kanite  阅读(480)  评论(1编辑  收藏  举报