In older projects, it’s common to see classes with nullation logic in some method body for the same variable. If there are only a few methods that need to be nulled in the body of the method, that’s fine, but if most methods have to be nulled for a variable, the code is often difficult to maintain.

Such as:

The disadvantages of this method of shorting the same variable in large numbers are as follows (a careful taste of the disadvantages is an important basis for understanding subsequent improvements) :

  • Repeating this null logic in multiple places will produce a lot of duplicate code, which is not easy to maintain.
  • If most of the code in a class is this kind of code, colleagues will usually spend more time understanding them and think long and hard about extending them
  • Null-free logic cannot null-protect a newly introduced method, and a NULL error can occur if you write a new method and forget to write null logic.

Let’s take a specific example. In the e-commerce project, there is always a unified entrance and exit for us to process payment. There are many kinds of payment behaviors, such as using coupons and not using coupons.

public class PayProcess {


    private CouponInfo couponInfo;

    public void setCouponInfo(CouponInfo couponInfo) { this.couponInfo = couponInfo; } // Check the validity of the payment public BooleancheckLegitimate() {
        if(null ! = couponInfo) {// Check whether the coupon has expiredreturncouponInfo.checkLeg(); } // If there is no coupon, it means legalreturn true; Public int getPayValue(int totalValue) {public int getPayValue(int totalValue) {if(null ! = couponInfo) {// The total amount paid minus the coupon amount is naturally the amount that needs to be paidreturn totalValue - couponInfo.getCouponValue();
        }
        returntotalValue; } // Get the coupon type public intgetCouponType() {
        if(null ! = couponInfo) {returncouponInfo.getCouponType(); } // Return 0 if there is no coupon at all. 0 means there is no couponreturn 0;
    }


}

class CouponInfo {


    public boolean checkLeg() {// In practice we will check the time of the coupons and so on, but now I will return one for demonstration purposesfalse// Just get the ideareturn false;
    }

    public int getCouponValue() {// Return the actual value of the coupon, also for demonstration purposes I directly return a fixed valuereturn 3;
    }

    public int getCouponType() {// Return the type of coupon, see if it is invincible? // I'll just write an int for the sake of illustration, but make sure you write it as a constantreturn2; }}Copy the code

When we use this payment system, there will definitely be multiple uses, some scenarios using coupons, some scenarios not. Such as:

Public static void main(String[] args) {PayProcess p1 = new PayProcess(); p1.setCouponInfo(new CouponInfo()); PayProcess p2 = new PayProcess(); p1.setCouponInfo(null); }Copy the code

As you can see, our PayProcess does a lot of null-checking. Defensive programming never hurts. This is also an important point mentioned in the Java development manual of Ali, the null to null, the array out of bounds to array out of bounds. The idea is good, but with code like this, it’s easy to go into null hell. What shortcomings we mentioned at the beginning of the article.

How do I refactor this old code? So he doesn’t look so bad?

Let’s add a new class (the main purpose of which is to uniformly handle null cases here) :

Public class NullCouponInfo extends CouponInfo {public Boolean extends CouponInfo {public BooleancheckLeg() {
        return true; } // If there is no coupon, the value of the coupon is 0 public intgetCouponValue() {
        return0; } // No coupon naturetype0 public intgetCouponType() {
        return0; }}Copy the code

Then our payment class can be much cleaner:

public class PayProcess {


    private CouponInfo couponInfo;

    public void setCouponInfo(CouponInfo couponInfo) { this.couponInfo = couponInfo; } // Check the validity of the payment public BooleancheckLegitimate() {// Check whether the coupon has expiredreturncouponInfo.checkLeg(); } public int getPayValue(int totalValue) {public int getPayValue(int totalValue) {public int getPayValue(int totalValue) {public int getPayValue(int totalValue)returntotalValue - couponInfo.getCouponValue(); } // Get the coupon type public intgetCouponType() {
        returncouponInfo.getCouponType(); }}Copy the code

When finally called, do not pass null as an argument when the ticket is empty

   PayProcess p3 = new PayProcess();
   p1.setCouponInfo(new NullCouponInfo());
Copy the code

As you can see, the whole logic is much clearer and readable. There’s not as much duplicate code. Of course, there is a catch: when we need to add new methods, we need to change NullCouponInfo as well as CouponInfo, and if we miss it, we will have a catch in the PayProcess class. There are no null-pointer exceptions, but they often don’t get the results we want.

For this scenario, we can simply abstract out an interface. Let both our CouponInfo and NullCouponInfo inherit an interface: ICoupon (stop making CouponInfo the parent of NullCouponInfo), so all new methods need to be added to the interface, which will prompt us to implement them in both subclasses at compile time. Thus avoid the hidden dangers mentioned above. (This code is relatively simple and will not be demonstrated)

Conclusion: Replacing all null logic with a single NULL object solves the problem we raised at the beginning of this article. Some of the main points are as follows:

  • If the business logic is simple, introducing the null object pattern will increase the code. So users need to make their own judgments about the complexity of the whole business.
  • When writing null object, you need to comment it out, especially during refactoring, and notify the caller of the end of the refactoring, because if you introduce this pattern and your colleagues don’t know about it, they probably won’t write the logic for null. Of course, using interfaces can circumvent this situation.
  • Even with interfaces, the overall code complexity increases slightly.
  • For null scenarios that are not strongly verified, blindly imitating null Objects will increase the design complexity.
  • Don’t get stuck in the void void scenario, but think about it a lot of times we can use this notation to determine if a list is empty.