空对象模式

简介: 空对象模式

空对象模式
空对象模式(Null Object Pattern)是一种设计模式,用于解决在某些情况下不需要实例化具体的对象,而是返回一个“空”对象,这样可以简化代码、避免 NullPointerException 和提高程序的可读性和维护性。简单来说,”空对象模式“就是本该返回None值或抛出异常时,返回一个符合正常结果接口的特制“空类型对象”来代替,以此免去调用方法的错误处理工作。

来看一个例子,现有多份问卷调查的得分记录,全部为字符串格式,存放在一个列表中:

data = ['bruce_liu 98', 'mike 100', 'invalid-data', 'roland $invalid_points', ...]

正常的得分记录是{username} {points}格式,但你会发现, 有些数据明显不符合规范(比如invalid-data)。现在统计合格(大于等于80)的得分记录总数,代码如下:

QUALIFIED_POINTS = 80


class CreateUserPointError(Exception):
    """创建得分纪录失败时抛出"""


class UserPoint:
    """用户得分记录"""

    def __init__(self, username, points):
        self.username = username
        self.points = points

    def is_qualified(self):
        """返回得分是否合格"""
        return self.points >= QUALIFIED_POINTS


def make_userpoint(point_string):
    """从字符串初始化一条得分记录

    :param point_string: 形如 "piglei 1" 的表示得分记录的字符串
    :return: UserPoint 对象
    :raises: 当输入数据不合法时返回 CreateUserPointError
    """
    try:
        username, points = point_string.split()
        points = int(points)
    except ValueError:
        raise CreateUserPointError('input must follow pattern "{username} {points}"')

    if points < 0:
        raise CreateUserPointError('points can not be negative')
    return UserPoint(username=username, points=points)


def calc_qualified_count(points_data):
    """计算得分合格的总人数

    :param points_data: 字符串格式的用户得分列表
    """
    result = 0
    for point_string in points_data:
        try:
            point_obj = make_userpoint(point_string)
        except CreateUserPointError:
            pass
        else:
            result += point_obj.is_qualified()
    return result


data = [
    'bruce_liu 98',
    'nobody 61',
    'cotton 83',
    'invalid_data',
    'roland $invalid_points',
    'alfred -3',
]
print(calc_qualified_count(data))

#输出结果
# 2

在上面的代码里,因为输入数据可能不符合要求,所以make_userpoint()方法在解析输入数据、创建UserPoint对象的过程中,可能会抛出CreateUserPointError异常来通知调用方。

因此,每当调用make_userpoint()时, 都必须加上try/except语句来捕获异常。假如引入“空对象模式”,上面的异常处理逻辑可以完全消失, 代码如下:

QUALIFIED_POINTS = 80


class UserPoint:
    """用户得分记录"""

    def __init__(self, username, points):
        self.username = username
        self.points = points

    def is_qualified(self):
        """返回得分是否合格"""
        return self.points >= QUALIFIED_POINTS


class NullUserPoint:
    """一个空的用户得分记录"""

    username = ''
    points = 0

    def is_qualified(self):
        return False


def make_userpoint(point_string):
    """从字符串初始化一条得分记录

    :param point_string: 形如 "piglei 1" 的表示得分记录的字符串
    :return: 如果输入合法,返回 UserPoint 对象,否则返回 NullUserPoint
    """
    try:
        username, points = point_string.split()
        points = int(points)
    except ValueError:
        return NullUserPoint()

    if points < 0:
        return NullUserPoint()
    return UserPoint(username=username, points=points)


def count_qualified(points_data):
    """计算得分合格的总人数

    :param points_data: 字符串格式的用户得分列表
    """
    return sum(make_userpoint(s).is_qualified() for s in points_data)


data = [
    'bruce_liu 98',
    'nobody 61',
    'cotton 83',
    'invalid_data',
    'roland $invalid_points',
    'alfred -3',
]

print(calc_qualified_count(data))

在这个代码里,定义了一个代表“空得分记录”的新类型:NullUserPoint,每当make_userpoint()接收到无效的输入,执行失败时,就会返回一个NullUserPoint对象。这样修改后,count_qualified()就不再需要处理任何异常了:

def count_qualified(points_data):
    """计算得分合格的总人数

    :param points_data: 字符串格式的用户得分列表
    """
    return sum(make_userpoint(s).is_qualified() for s in points_data)

这里的make_userpoint()总是会返回一个符合要求的对象(UserPoint()或NullUserPoint()),同前面unset命令的故事一样,“空对象模式”也是一种转换设计观念以避免错误处理的技巧。当函数进入边界情况时,“空对象模式”不再抛出错误

相关文章
|
11天前
|
SQL 存储 算法
基于对象 - 事件模式的数据计算问题
基于对象-事件模式的数据计算是商业中最常见的数据分析任务之一。对象如用户、账号、商品等,通过唯一ID记录其相关事件,如操作日志、交易记录等。这种模式下的统计任务包括无序计算(如交易次数、通话时长)和有序计算(如漏斗分析、连续交易检测)。尽管SQL在处理无序计算时表现尚可,但在有序计算中却显得力不从心,主要原因是其对跨行记录运算的支持较弱,且大表JOIN和大结果集GROUP BY的性能较差。相比之下,SPL语言通过强化离散性和有序集合的支持,能够高效地处理这类计算任务,避免了大表JOIN和复杂的GROUP BY操作,从而显著提升了计算效率。
|
3月前
|
Java 数据库连接 数据库
|
6月前
|
存储 Java
|
6月前
|
设计模式 缓存 安全
代理模式:解析对象间的间接访问与控制
代理模式:解析对象间的间接访问与控制
|
设计模式 缓存 安全
Java代理模式:如何优雅地控制对象访问?
Java代理模式:如何优雅地控制对象访问?
240 1
|
JavaScript 前端开发
js对象的创建对象模式和继承模式(上)---构建对象模式
js对象的创建对象模式和继承模式(上)---构建对象模式
132 0
|
JavaScript 前端开发
js对象的创建对象模式和继承模式(下)---继承模式
js对象的创建对象模式和继承模式(下)---继承模式
98 0
|
缓存
读源码长知识 | 动态扩展类并绑定生命周期的新方式
在阅读viewModelScope源码时,发现了一种新的方式。 协程需隶属于某 CoroutineScope ,以实现structured-concurrency,而 CoroutineScope 应
172 0