Introduce Null Object

时间:2022-01-30 23:45:26

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

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

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

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

  重构前的主体代码:

 package com.nelson.io;

 public class Site {

     private Customer _customer;

     public Customer getCustomer()
{
return _customer;
} public void setCustomer(Customer cus)
{
_customer = cus;
} } class Customer
{
String _name;
BillingPlan _billingplan;
PaymentHistory _paymentHistory; public String getName() {return _name;} public BillingPlan getPlan() {
return _billingplan;
} public PaymentHistory getHistroy(){
return _paymentHistory;
} public Customer(){
} public Customer(String name,BillingPlan bill,PaymentHistory pay){
_name = name;
_billingplan = bill;
_paymentHistory = pay;
}
} class BillingPlan
{
private int basicpay;
private int extrapay; public BillingPlan(){
setBasicpay(0);
setExtrapay(0);
} public BillingPlan(int pay)
{
setBasicpay(pay);
setExtrapay(0);
} public BillingPlan(int basic,int extra)
{
basicpay = basic;
extrapay = extra;
} static BillingPlan basic()
{
return new BillingPlan(100); //最低消费100元
} public int getTotalExpand()
{
return basicpay+extrapay;
} int getExtrapay() {
return extrapay;
} void setExtrapay(int extrapay) {
this.extrapay = extrapay;
} int getBasicpay() {
return basicpay;
} void setBasicpay(int basicpay) {
this.basicpay = basicpay;
}
} class PaymentHistory
{
public int getWeeksDelinquentInLastYear; public PaymentHistory()
{
getWeeksDelinquentInLastYear = 0;
}
}

  重构前的应用代码:

 package com.nelson.io;

 public class HelloWorld {

     public static void main(String[] args) {

         System.out.println("Hello Java!");

         //不正常的客户
Site site = new Site(); //这样默认Site中的_customer = null
Customer cus1 = site.getCustomer(); String strName;
if(cus1 == null) strName = "occupant"; //顾客名字暂时叫做occupant
else strName = cus1.getName(); //获取用户的名字
System.out.println("Current Customer1: "+strName); BillingPlan plan1;
if(cus1 == null) plan1 = BillingPlan.basic();
else plan1 = cus1.getPlan();
System.out.println("Total Expand:"+plan1.getTotalExpand()); //////////////////////////////////////////////////////////
//正常的客户
BillingPlan plan2 = new BillingPlan(100,19);
PaymentHistory history2 = new PaymentHistory();
Customer cus= new Customer("xiaoming",plan2,history2);
site.setCustomer(cus); Customer cus2 = site.getCustomer();
if(cus2 == null) strName = "occupant"; //顾客名字暂时叫做occupant
else strName = cus2.getName(); //获取用户的名字
System.out.println("Current Customer2: "+strName); if(cus2 == null) plan1 = BillingPlan.basic();
else plan1 = cus2.getPlan();
System.out.println("Total Expand:"+plan1.getTotalExpand()); } }

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

  重构后的主体代码:

 package com.nelson.io;

 public class Site {

     private Customer _customer;

     public Customer getCustomer()
{
return (_customer==null)?Customer.newNull():_customer;
} public void setCustomer(Customer cus)
{
_customer = cus;
} } class Customer implements Nullable
{
String _name;
BillingPlan _billingplan;
PaymentHistory _paymentHistory; public String getName() {return _name;} public BillingPlan getPlan() {
return _billingplan;
} public PaymentHistory getHistroy(){
return _paymentHistory;
} protected Customer(){
} public Customer(String name,BillingPlan bill,PaymentHistory pay){
_name = name;
_billingplan = bill;
_paymentHistory = pay;
} public static Customer newNull()
{
return new NullCustomer();
} public boolean isNull() {
return false;
}
} class NullCustomer extends Customer
{
public String getName() {return "occupant";} public BillingPlan getPlan() {
return BillingPlan.basic();
} public boolean isNull() {
return true;
}
} class BillingPlan
{
private int basicpay;
private int extrapay; public BillingPlan(){
setBasicpay(0);
setExtrapay(0);
} public BillingPlan(int pay)
{
setBasicpay(pay);
setExtrapay(0);
} public BillingPlan(int basic,int extra)
{
basicpay = basic;
extrapay = extra;
} static BillingPlan basic()
{
return new BillingPlan(100); //最低消费100元
} public int getTotalExpand()
{
return basicpay+extrapay;
} int getExtrapay() {
return extrapay;
} void setExtrapay(int extrapay) {
this.extrapay = extrapay;
} int getBasicpay() {
return basicpay;
} void setBasicpay(int basicpay) {
this.basicpay = basicpay;
}
} class PaymentHistory
{
public int getWeeksDelinquentInLastYear; public PaymentHistory()
{
getWeeksDelinquentInLastYear = 0;
}
} interface Nullable
{
boolean isNull();
}

  重构后的测试代码:

 package com.nelson.io;

 public class HelloWorld {

     public static void main(String[] args) {

         System.out.println("Hello Java!");

         //////////////////////////////////////////////////////////////////
//不正常的客户
Site site = new Site(); //这样默认Site中的_customer = null
Customer cus1 = site.getCustomer();
String strName = cus1.getName(); //获取用户的名字
System.out.println("Current Customer1: "+strName);
BillingPlan plan1 = cus1.getPlan();
System.out.println("Total Expand:"+plan1.getTotalExpand()); if(!cus1.isNull())
System.out.println("Customer1 is not null customer");
else
System.out.println("Customer1 is null customer"); System.out.println(); //////////////////////////////////////////////////////////
//正常的客户
BillingPlan plan2 = new BillingPlan(100,19);
PaymentHistory history2 = new PaymentHistory();
Customer cus= new Customer("xiaoming",plan2,history2);
site.setCustomer(cus); Customer cus2 = site.getCustomer();
strName = cus2.getName(); //获取用户的名字
System.out.println("Current Customer2: "+strName);
cus2.getPlan();
System.out.println("Total Expand:"+plan1.getTotalExpand()); if(!cus2.isNull())
System.out.println("Customer2 is not null customer");
else
System.out.println("Customer2 is null customer");
} }

  重构后的测试代码中,减少了对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 保证程序能处理“极端”情况。