文章

空对象模式 (Null Object Pattern) 深度解析

空对象模式 (Null Object Pattern) 深度解析

📜 设计模式:空对象模式 (Null Object Pattern)

概念与动机

定义

空对象模式 (Null Object Pattern) 是一种行为型设计模式,它用一个**不执行任何操作(no-op)**的对象来代替程序中的 null 引用。

这个“空对象”实现了与实际对象相同的接口或继承相同的抽象类,从而可以在客户端代码中透明地使用

动机

在传统的编程中,为了防止访问空引用时抛出 NullPointerException (Java) 或类似错误,我们经常需要编写大量的条件判断语句(如 if (object != null))来进行检查。

空对象模式的核心价值在于:消除条件检查,简化客户端代码。

  • 消除条件逻辑: 客户端代码可以直接调用方法,而无需关心它拿到的是一个真实对象还是一个空对象。
  • 提供默认行为: 空对象可以提供默认的、安全且“无害”的行为,例如返回一个空字符串,或者执行一个空操作。

结构

在空对象模式中,主要角色包括:

  1. 抽象实体 (Abstract Entity): 定义了真实对象和空对象都必须实现或继承的接口或抽象类。
  2. 真实实体 (Real Entity): 实现了抽象实体,并提供实际的业务逻辑。
  3. 空对象 (Null Object): 实现了抽象实体,但其方法实现为空操作或返回默认值。
  4. 工厂/客户端 (Factory/Client): 根据条件判断,返回真实实体或空对象。

💻 实现示例:客户查找服务

我们将创建一个客户查找服务。如果客户存在,返回 $RealCustomer$;如果客户不存在,返回 $NullCustomer$。

1. Java 实现

步骤 1: 抽象类 $AbstractCustomer$ (修正原笔记中的类名错误)

定义了客户的通用接口。

1
2
3
4
5
6
7
8
9
10
// AbstractCustomer.java
public abstract class AbstractCustomer {
    protected String name;

    // 检查对象是否为空对象
    public abstract boolean isNil();

    // 获取客户名称
    public abstract String getName();
}

步骤 2: 实体类 $RealCustomer$ 和 $NullCustomer$

  • RealCustomer: 包含真实的客户信息。
  • NullCustomer: 不包含信息,返回默认的“不可用”字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// RealCustomer.java
public class RealCustomer extends AbstractCustomer {
    public RealCustomer(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public boolean isNil() {
        return false;
    }
}

// NullCustomer.java
public class NullCustomer extends AbstractCustomer {
    @Override
    public String getName() {
        return "Not Available in Customer Database"; // 默认行为
    }

    @Override
    public boolean isNil() {
        return true;
    }
}

步骤 3: 工厂类 $CustomerFactory$

负责根据名字决定返回 $RealCustomer$ 还是 $NullCustomer$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// CustomerFactory.java
public class CustomerFactory {
    public static final String[] names = {"Rob", "Joe", "Julie"};

    public static AbstractCustomer getCustomer(String name) {
        for (int i = 0; i < names.length; i++) {
            if (names[i].equalsIgnoreCase(name)) {
                return new RealCustomer(name);
            }
        }
        // 如果未找到,返回 NullCustomer
        return new NullCustomer();
    }
}

4. 客户端演示 $NullPatternDemo$

注意: 客户端代码无需任何 if (customer != null) 检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// NullPatternDemo.java
public class NullPatternDemo {
    public static void main(String[] args) {
        AbstractCustomer customer1 = CustomerFactory.getCustomer("Rob");
        AbstractCustomer customer2 = CustomerFactory.getCustomer("Bob");
        AbstractCustomer customer3 = CustomerFactory.getCustomer("Julie");
        AbstractCustomer customer4 = CustomerFactory.getCustomer("Laura");

        System.out.println("Customers");
        System.out.println(customer1.getName()); // Rob
        System.out.println(customer2.getName()); // Not Available...
        System.out.println(customer3.getName()); // Julie
        System.out.println(customer4.getName()); // Not Available...
    }
}
执行结果:
1
2
3
4
5
Customers
Rob
Not Available in Customer Database
Julie
Not Available in Customer Database

2. Python 实现

在 Python 中,我们使用 abc 模块来定义抽象基类。

步骤 1: 抽象基类 $AbstractCustomer$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# abstract_customer.py
from abc import ABC, abstractmethod


class AbstractCustomer(ABC):
    def __init__(self, name: str = None):
        self._name = name

    @abstractmethod
    def is_nil(self) -> bool:
        """检查是否为空对象"""
        pass

    @abstractmethod
    def get_name(self) -> str:
        """获取客户名称"""
        pass

步骤 2: 实体类 $RealCustomer$ 和 $NullCustomer$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# real_customer.py
class RealCustomer(AbstractCustomer):
    def is_nil(self) -> bool:
        return False

    def get_name(self) -> str:
        return self._name


# null_customer.py
class NullCustomer(AbstractCustomer):
    def is_nil(self) -> bool:
        return True

    def get_name(self) -> str:
        # 提供默认的、无害的返回
        return "Not Available in Customer Database"

步骤 3: 工厂类 $CustomerFactory$

1
2
3
4
5
6
7
8
9
10
# customer_factory.py
class CustomerFactory:
    NAMES = ["Rob", "Joe", "Julie"]

    @staticmethod
    def get_customer(name: str) -> AbstractCustomer:
        if name in CustomerFactory.NAMES:
            return RealCustomer(name)
        # 返回 Null Object
        return NullCustomer()

4. 客户端演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# null_pattern_demo.py
if __name__ == '__main__':
    customer1 = CustomerFactory.get_customer("Rob")
    customer2 = CustomerFactory.get_customer("Bob")
    customer3 = CustomerFactory.get_customer("Julie")
    customer4 = CustomerFactory.get_customer("Laura")

    print("Customers")
    print(customer1.get_name())
    print(customer2.get_name())
    print(customer3.get_name())
    print(customer4.get_name())

    # 客户端代码始终可以安全地调用 .get_name()
执行结果:
1
2
3
4
5
Customers
Rob
Not Available in Customer Database
Julie
Not Available in Customer Database

🌟 优缺点分析

方面优点 (Pros)缺点 (Cons)
代码可读性极大地简化了客户端代码,消除了大量的 $if (obj != null)$ 检查。增加了新的类(Null Object),可能导致类数量增多,增加维护负担。
错误处理将检查 $null$ 的逻辑集中到工厂,客户端无需关心。如果业务逻辑要求区分“不存在”和“存在但为空”,可能会引入额外的复杂性(例如 $isNil()$ 方法)。
健壮性保证了程序执行的流程,消除了潜在的 $NullPointerException$ 风险。该模式必须基于一个稳定的、被所有真实对象和空对象实现的接口。
耦合性客户端对真实对象和空对象解耦,只依赖于抽象接口。只能用于可以预知 $null$ 检查的场景。如果 $null$ 出现的位置不确定,该模式无效。
本文由作者按照 CC BY 4.0 进行授权