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 }
重构前的应用代码:
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 }
在这里的重构要解决的问题是,应用代码中会不断的查询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 }
重构后的测试代码:
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 }
重构后的测试代码中,减少了对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 保证程序能处理“极端”情况。