Introduce Null Object
今天继续总结《重构》这本书中的一个重构手法,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 保证程序能处理“极端”情况。
Introduce Null Object的更多相关文章
- 【设计模式 - 21】之空对象模式(Null Object)
		
1 模式简介 在空对象模式中,一个空对象取代NULL对象的实例的检查.NULL对象不是检查空值,而是反映一个不做任何动作的关系.这样的NULL对象也可以在数据不可用的时候提供默认的行为. 在 ...
 - 设计模式之美:Null Object(空对象)
		
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Null Object 的示例实现. 意图 通过对缺失对象的封装,以提供默认无任何行为的对象替代品. Encapsulate t ...
 - 使用“Empty 模式”改进 Null Object
		
概述 Null Object 是Martin 大师提出的一种重构手段,其思想就是通过多态(派生一个Null对象)来减少逻辑(if … then …else)的判断. 而.NET中已经有Null Obj ...
 - 设计模式之空对象(Null Object)模式
		
通过引用Null Object,用于取消业务逻辑中对对象的为空推断 类图: Nullable: package com.demo.user; public interface Nullable { b ...
 - 设计模式:空对象模式(Null Object Pattern)
		
设计模式:空对象模式(Null Object Pattern) 背景 群里聊到<ASP.NET设计模式>,这本书里有一个“Null Object Pattern”,大家就闲聊了一下这个模式 ...
 - Attempt to write to field 'android.support.v4.app.FragmentManagerImpl android.support.v4.app.Fragment.mFragmentManager' on a null object reference
		
E/AndroidRuntime﹕ FATAL EXCEPTION: mainProcess: org.example.magnusluca.drawertestapp, PID: 3624java. ...
 - Null Object Design Pattern (Python recipe)
		
Null Object 个人感觉非常有用.也是在review公司其他同事写代码的时候看到. 当时使用了flask的request全局请求变量g,然后使用了g.x保存了一个东西. 当时在view代码读取 ...
 - Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.app.ActionBar.setDisplayShowHomeEnabled(boolean)' on a null object reference
		
/********************************************************************************* * Caused by: java ...
 - java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List com.yunweather.app.db.YunWeatherDB.loadProvinces()' on a null object reference
		
NullPointerException:查看自己的什么地方是否对空指针进行了操作 Attempt to invoke virtual method 'java.util.List com.yunwe ...
 
随机推荐
- 【Luogu】P2617Dynamic Ranking(树状数组套主席树)
			
题目链接 树状数组套主席树有点难懂qwq 不好理解 树状数组套主席树的直观理解应该是:树状数组的每一个节点是一棵主席树. 普通区间修改我们是创建1个线段树,树状数组套主席树的时候我们就创建log个线段 ...
 - 【THUSC2016】成绩单(bzoj4897)
			
$f(i,j,x,y)$ 表示区间 $[i,j]$中,第 $j$ 个数在最后一次操作中才消去,最后一次操作的最大值为 $x$,最小值为 $y$ 时的最小代价: $g(i,j)$ 表示区间 $[i,j] ...
 - 2014湘潭全国邀请赛I题 Intervals /POJ 3680 / 在限制次数下取有权区间使权最大/小问题(费用流)
			
先说POJ3680:给n个有权(权<10w)开区间(n<200),(区间最多数到10w)保证数轴上所有数最多被覆盖k次的情况下要求总权最大,输出最大权. 思路: 限制的处理:s ...
 - webStorm汉化
			
http://www.sdbeta.com/xiazai/2015/0603/35070.html 注册时选择“License server”输入“http://idea.imsxm.com/”点击“ ...
 - CMDB资产管理系统的数据表设计
			
Server表: asset = models.OneToOneField('Asset') 主机名(hostname) sn号(sn) 制造商(manufacture) 系统(os_platform ...
 - Spring Boot 2 快速教程:WebFlux 集成 Mongodb(四)
			
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第104篇原创 文章工程:* JDK 1.8* M ...
 - 下载安装webstrom及激活
			
太久没在新电脑上安装websrtom,又有点忘了咋激活. 一.安装 1.直接在浏览器搜索webstrom,打开官网,直接点击download.如下图 2.打开安装包,开始安装,直接点击 next 3. ...
 - BZOJ——1202: [HNOI2005]狡猾的商人
			
http://www.lydsy.com/JudgeOnline/problem.php?id=1202 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: ...
 - luogu P1260 工程规划
			
题目描述 造一幢大楼是一项艰巨的工程,它是由n个子任务构成的,给它们分别编号1,2,…,n(5≤n≤1000).由于对一些任务的起始条件有着严格的限制,所以每个任务的起始时间T1,T2,…,Tn并不是 ...
 - Wannafly练习赛14
			
B(倍增) 题意: 分析: 先可以用two point预处理出以每个位置为起点的连续段<=k的下一个终点 然后对于每个询问,倍增跳就行了 时间复杂度O(nlogn) C(扫描线处理区间询问) ...