又催婚?别怪我亮出 Null Object Pattern

148 阅读4分钟

放假回家,你妈逼你结婚了吗?

单身猿, 每到情人节, 七夕节, 1024 程序猿节,甚至本来是单身狗保护日的双十一, 都会被无情地秀一脸恩爱。

最可怕的是过年回家, 面对七大姑八大姨的质问, 一句“大侄子处对象了吗”, 答“还没呢”,足以让你成为接下来半小时的重点照顾对象(是不是眼光高呀, 我们村有个叫兰兰的不错, 你是不是 gay 呀)😳

父母更是花式催婚, 看到电视上的奶粉广告(你看电视这大胖小子, 多可爱), 还有什么暗语(你前面有 10 条巷子, 9 条巷子有危险, 你要怎么走过去)🤦‍♂️

对于这种情况,我只想和他们聊聊空对象模式(Null Object Pattern)

目的

避免上层代码进行空指针异常检查和保持上层代码的可读性简洁性

例子代码

要想避开各种催婚,那我们需要假装自己有对象, 这样大家就不会对你抛出空指针异常警告

可是我们是老老实实的程序猿, 并不会编造一个女朋友(那这就是 mock 啦),我们只是根据实际情况作答。

我们需要一个女朋友的定义, 大致如下:

/** * 女朋友属性 */public abstract class AbstractGirlFriend {        /**     * 查询姓名     * @return     */    protected abstract String queryName();        /**     * 查询年龄     * @return     */    protected abstract int queryAge();        /**     * 查询 cup     * @return     */    protected abstract Character queryCup();        /**     * 是不是女性     * @return     */    protected abstract boolean isFemale();}

我们定义一个抽象男人类, 返回女朋友类.

/** * 男人 */public abstract class Man {        /**     * 得到女朋友     * @return     */    public abstract AbstractGirlFriend                                   queryGirlFriend();}

定义了二个实现类:

别人:

/** * 其他人 */public class Others extends Man {        @Override    public AbstractGirlFriend queryGirlFriend() {                return new NormalGrilFriend();    }}

我自己:

/** * 我自己 */public class Me extends Man {        @Override    public AbstractGirlFriend queryGirlFriend() {                return null;    }}

其中 NormalGrilFriend 大致如下:

/** * 正常的女朋友 */public class NormalGrilFriend extends         AbstractGirlFriend implements        PlayWithGirlFriend{        @Override    protected String queryName() {                return "兰兰";    }        @Override    protected int queryAge() {                return 18;    }        @Override    protected Character queryCup() {            //嗯, 经过我阅片 20 年的经验, 这个类也是 A, 你没有看错        return 'A';    }        @Override    protected boolean isFemale() {                return true;    }        @Override    public void date() {        System.out.println("吃饭, 看电影,                                    游乐园, 小树林");    }        @Override    public void shopping() {        System.out.println("买买买, 刷刷刷");    }        @Override    public void mkLove() {        System.out.println("嗯, 这个方法我也不知道要干啥,    请女读者(男的也行)联系我现场教学, 微信是 13820802870");    }}

这时候我们有个需求, 需要打印一组男人所有女朋友的名字, 我们的代码大致如下:

    List<Man> mansWithGrilFriend =        Arrays.asList(new Others(), new Me());        for (Man man : mansWithGrilFriend) {        AbstractGirlFriend girlFriend =                            man.queryGirlFriend();          if(girlFriend == null) {                        continue;         }        System.out.println(girlFriend.queryName());    }

问题分析

我们觉得有两点问题

第一点是,我们需要做 girlFriend == null 这种判断, 别人阅读代码时候本来只要看输出姓名这一行就知道这个 for 循环做了什么, 现在还需要多看二行。

另外,如果不是这个严谨的上层使用者, 没有做非空判断, 随时可能报出空指针异常。

空对象模式

我们定义一个空的女朋友对象:

/** * 空女朋友 */public final class NullGrilFriend extends     AbstractGirlFriend implements    PlayWithGirlFriend{            private static final        NullGrilFriend NULL_GRIL_FRIEND =        new NullGrilFriend();        private NullGrilFriend() {    }        public static NullGrilFriend getInstance() {          return NULL_GRIL_FRIEND;    }        @Override    protected String queryName() {                 return "";                 //return "五指姑娘";    }        @Override    protected int queryAge() {                //没出生呢        return 0;    }        @Override    protected Character queryCup() {                // A 也找不着呀, 所以不能乱说, 需要减 1        return 'A' - 1;    }        @Override    protected boolean isFemale() {                //这个是 IDEA 自动生成的代码        return false;    }        @Override    public void date() {        System.out.println(        "打开电脑 D 盘下面的 '学习视频' 目录下的波老师教学");    }        @Override    public void shopping() {        System.out.println("买些女装自己穿,                            买到让淘宝广告以为我是暖男");    }        @Override    public void mkLove() {        System.out.println("哎, 手上的茧");    }}

在我自己的类中返回这个空的女朋友:

/** * 我自己 */public class Me extends Man {    @Override    public AbstractGirlFriend queryGirlFriend() {        return NullGrilFriend.getInstance();    }}

这样我们就是去掉空指针判断了, 代码的就可以像下面这样可读性变的好了很多

    List<Man> mansWithGrilFriend =            Arrays.asList(new Others(), new Me());    for (Man man : mansWithGrilFriend) {        System.out.println(man.queryGirlFriend()                                    .queryName());    }

Tips: 还可以使用 Nullable 接口和是 Null 标记接口来提高代码的可读性, 在实现空对象模式的时候需要注意保持上层代码的行为要可控。

空对象模式课后作业

1. 了解使用 Nullable 接口和 Null 标记接口的显式空对象模式, 并比较二种方式的优劣

2. 思考空对象模式的缺点

3. 作为一个银行开发人员, 比如我们有一系列的还款计划列表, 我们要构造一个还款汇总类, 传入还款计划列表, 这个类能分别计算未还本金,未还利息,未还手续费三个项, 使用空对象模式实现

@Getterpublic class RepayPlanItem {     //本期未还本金   private BigDecimal unpayedPrice;     //本期未还利率   private BigDecimal unpayedRate;     //本期未还手续费   private BigDecimal unpayedFee;      }

汇总类如下:

public class TotalRepayPlan {        public BigDecimal getTotalUnpayedPrice(){                //等待实现    }        public BigDecimal getTotalUnpayedRate(){                //等待实现    }        public BigDecimal getTotalUnpayedFee(){                //等待实现    }}

codog代码狗 微信: