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】P1681最大正方形2(异或运算,DP)
题目链接 不得不说attack是个天才.读入使用异或运算,令que[i][j]^=(i^j)&1,于是原题目变成了求que数组的最大相同值. 然而我还是不理解为啥,而且就算简化成这样我也不会做 ...
- POJ——3159Candies(差分约束SPFA+前向星+各种优化)
Candies Time Limit: 1500MS Memory Limit: 131072K Total Submissions: 28071 Accepted: 7751 Descrip ...
- 刷题总结——教主的魔法(bzoj3343)
题目: Description 教主最近学会了一种神奇的魔法,能够使人长高.于是他准备演示给XMYZ信息组每个英雄看.于是N个英雄们又一次聚集在了一起,这次他们排成了一列,被编号为1.2.…….N. ...
- 刷题总结——小凸玩矩阵(scoi)
题目: 题目背景 SCOI2015 DAY1 T1 题目描述 小凸和小方是好朋友,小方给了小凸一个 n×m(n≤m)的矩阵 A,并且要求小凸从矩阵中选出 n 个数,其中任意两个数都不能在同一行或者同一 ...
- Numpy 花式索引
记住:花式索引跟切片不一样,它总是将数据复制到新数组中. 一 给定一个列表,返回索引为1,3,4,5,6的数组 2 针对二维数组 需要注意的一点是,对于花式索引.对照下后面的两种方式,查询结果的不同.
- 洛谷 [P3812] 线性基
异或空间下的线性基模版 异或空间下求线性基,本质还是高斯消元,参见 http://www.cnblogs.com/Mr-WolframsMgcBox/p/8562924.html 求最大值是一个贪心的 ...
- windows创建任务计划(周期执行bat脚本)
https://jingyan.baidu.com/article/ca00d56c767cfae99febcf73.html windows找到任务计划程序: 这台电脑->管理
- UTF-8 编码的文件在处理时要注意 BOM 文件头问题
最近在给项目团队开发一个基于 Java 的通用的 XML 分析器时,设计了一个方法,能够读取现成的 XML 文件进行分析处理,当然 XML 都是采用 UTF-8 进行编码的.但是在用 UltraEdi ...
- getpixel 取色
HWND hwnd = ::FindWindow(NULL,"<天龙八部OL>"); CRect rect; CString strTmp; if (hwnd != N ...
- 使用Python写的第一个网络爬虫程序
今天尝试使用python写一个网络爬虫代码,主要是想訪问某个站点,从中选取感兴趣的信息,并将信息依照一定的格式保存早Excel中. 此代码中主要使用到了python的以下几个功能,因为对python不 ...