python: Null Object Pattern

以珠宝门店的核心业务流程为例:

珠宝实体属性:编号、名称、类型(钻石 / 黄金 / 翡翠)、重量、价格、库存、鉴定证书编号

核心业务流程:

查询指定编号的珠宝信息

计算珠宝的折后价(会员折扣)

生成珠宝的销售单据

扣减珠宝库存(销售后)

python 复制代码
# encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:空对象模式(Null Object Pattern)
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2024.3.6 python 3.11
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/3/9 20:55
# User      :  geovindu
# Product   : PyCharm
# Project   : pydesginpattern
# File      : Jewelry.py
from abc import ABC, abstractmethod
from typing import Optional
 
# 1:---定义珠宝接口(统一真实对象和空对象的行为)
 
# 珠宝实体接口(定义核心行为)
class Jewelry(ABC):
    """
    珠宝实体属性(抽象属性,强制子类实现)
    """
    @property
    @abstractmethod
    def id(self) -> str:
        """
        珠宝编号
        :return:
        """
        pass
 
    @property
    @abstractmethod
    def name(self) -> str:
        """
        珠宝名称
        :return:
        """
        pass
 
    @property
    @abstractmethod
    def type(self) -> str:
        """
        珠宝类型
        :return:
        """
        pass
 
    @property
    @abstractmethod
    def weight(self) -> float:
        """
        重量
        :return:
        """
        pass
 
    @property
    @abstractmethod
    def price(self) -> float:
        """
        原价
        :return:
        """
        pass
 
    @property
    @abstractmethod
    def stock(self) -> int:
        """
 
        :return:
        """
        pass
 
    @property
    @abstractmethod
    def certificate_id(self) -> Optional[str]:
        """
        鉴定证书
        :return:
        """
 
        pass
 
    # 业务流程方法(抽象方法,强制子类实现)
    @abstractmethod
    def calculate_discounted_price(self, discount: float = 0.95) -> float:
        """
        计算折后价(默认会员95折)
        :param discount:
        :return:
        """
        pass
 
    @abstractmethod
    def generate_sales_doc(self, customer_name: str) -> str:
        """
        生成销售单据
        :param customer_name:
        :return:
        """
        pass
 
    @abstractmethod
    def reduce_stock(self, quantity: int = 1) -> bool:
        """
        扣减库存
        :param quantity:
        :return:
        """
        pass
python 复制代码
# encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:空对象模式(Null Object Pattern)
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2024.3.6 python 3.11
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/3/9 20:56
# User      :  geovindu
# Product   : PyCharm
# Project   : pydesginpattern
# File      : RealJewelry.py
from Model.NullObject.Jewelry import Jewelry
from typing import Optional
 
# ---2:实现真实珠宝对象(真实业务逻辑)
class RealJewelry(Jewelry):
    """
    实现真实珠宝对象
    """
    def __init__(self, id: str, name: str, type: str, weight: float, price: float, stock: int, certificate_id: Optional[str] = None):
        """
        初始化珠宝实体属性
        :param id:珠宝编号
        :param name:珠宝名称
        :param type:珠宝类型
        :param weight:重量
        :param price:原价
        :param stock:折后价
        :param certificate_id:鉴定证书编号
        """
        self._id = id
        self._name = name
        self._type = type
        self._weight = weight
        self._price = price
        self._stock = stock
        self._certificate_id = certificate_id
 
    # 实现实体属性的getter
    @property
    def id(self) -> str:
        """
        珠宝编号
        :return:
        """
        return self._id
 
    @property
    def name(self) -> str:
        """
          珠宝名称
        :return:
        """
        return self._name
 
    @property
    def type(self) -> str:
        """
       珠宝类型
        :return:
        """
        return self._type
 
    @property
    def weight(self) -> float:
        """
        重量
        :return:
        """
        return self._weight
 
    @property
    def price(self) -> float:
        """
       原价
        :return:
        """
        return self._price
 
    @property
    def stock(self) -> int:
        """
        折后价
        :return:
        """
        return self._stock
 
    @property
    def certificate_id(self) -> Optional[str]:
        """
        鉴定证书
        :return:
        """
        return self._certificate_id
 
    # 实现业务流程方法
    def calculate_discounted_price(self, discount: float = 0.95) -> float:
        """
        计算折后价:原价 * 折扣(最低8折)
        :param discount:
        :return:
        """
        if discount < 0.8:
            discount = 0.8
        return round(self._price * discount, 2)
 
    def generate_sales_doc(self, customer_name: str) -> str:
        """
        生成销售单据(包含珠宝所有属性)
        :param customer_name:
        :return:
        """
        doc = f"""
        珠宝销售单
        ----------
        客户姓名:{customer_name}
        珠宝编号:{self._id}
        珠宝名称:{self._name}
        珠宝类型:{self._type}
        重量(克):{self._weight}
        原价(元):{self._price}
        折后价(元):{self.calculate_discounted_price()}
        鉴定证书编号:{self._certificate_id or '无'}
        销售日期:2026-03-08
        ----------
        """
        return doc
 
    def reduce_stock(self, quantity: int = 1) -> bool:
        """
        扣减库存:库存充足则扣减,返回True;否则返回False
        :param quantity:
        :return:
        """
        if self._stock >= quantity:
            self._stock -= quantity
            return True
        return False
python 复制代码
# encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:空对象模式(Null Object Pattern)
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2024.3.6 python 3.11
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/3/9 20:56
# User      :  geovindu
# Product   : PyCharm
# Project   : pydesginpattern
# File      : RealJewelry.py
from Model.NullObject.Jewelry import Jewelry
from typing import Optional
 
# ---2:实现真实珠宝对象(真实业务逻辑)
class RealJewelry(Jewelry):
    """
    实现真实珠宝对象
    """
    def __init__(self, id: str, name: str, type: str, weight: float, price: float, stock: int, certificate_id: Optional[str] = None):
        """
        初始化珠宝实体属性
        :param id:珠宝编号
        :param name:珠宝名称
        :param type:珠宝类型
        :param weight:重量
        :param price:原价
        :param stock:折后价
        :param certificate_id:鉴定证书编号
        """
        self._id = id
        self._name = name
        self._type = type
        self._weight = weight
        self._price = price
        self._stock = stock
        self._certificate_id = certificate_id
 
    # 实现实体属性的getter
    @property
    def id(self) -> str:
        """
        珠宝编号
        :return:
        """
        return self._id
 
    @property
    def name(self) -> str:
        """
          珠宝名称
        :return:
        """
        return self._name
 
    @property
    def type(self) -> str:
        """
       珠宝类型
        :return:
        """
        return self._type
 
    @property
    def weight(self) -> float:
        """
        重量
        :return:
        """
        return self._weight
 
    @property
    def price(self) -> float:
        """
       原价
        :return:
        """
        return self._price
 
    @property
    def stock(self) -> int:
        """
        折后价
        :return:
        """
        return self._stock
 
    @property
    def certificate_id(self) -> Optional[str]:
        """
        鉴定证书
        :return:
        """
        return self._certificate_id
 
    # 实现业务流程方法
    def calculate_discounted_price(self, discount: float = 0.95) -> float:
        """
        计算折后价:原价 * 折扣(最低8折)
        :param discount:
        :return:
        """
        if discount < 0.8:
            discount = 0.8
        return round(self._price * discount, 2)
 
    def generate_sales_doc(self, customer_name: str) -> str:
        """
        生成销售单据(包含珠宝所有属性)
        :param customer_name:
        :return:
        """
        doc = f"""
        珠宝销售单
        ----------
        客户姓名:{customer_name}
        珠宝编号:{self._id}
        珠宝名称:{self._name}
        珠宝类型:{self._type}
        重量(克):{self._weight}
        原价(元):{self._price}
        折后价(元):{self.calculate_discounted_price()}
        鉴定证书编号:{self._certificate_id or '无'}
        销售日期:2026-03-08
        ----------
        """
        return doc
 
    def reduce_stock(self, quantity: int = 1) -> bool:
        """
        扣减库存:库存充足则扣减,返回True;否则返回False
        :param quantity:
        :return:
        """
        if self._stock >= quantity:
            self._stock -= quantity
            return True
        return False
python 复制代码
# encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:空对象模式(Null Object Pattern)
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2024.3.6 python 3.11
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/3/9 20:59
# User      :  geovindu
# Product   : PyCharm
# Project   : pydesginpattern
# File      : NullJewelry.py
from Model.NullObject.Jewelry import Jewelry
from typing import Optional
 
# ---3:实现空珠宝对象(替代 None,避免空指针判断)
class NullJewelry(Jewelry):
    """
    空珠宝对象:所有属性返回默认值,所有方法返回无操作结果
    """
    # 空对象的默认属性
    @property
    def id(self) -> str:
        """
       珠宝编号
        :return:
        """
        return "NULL_JEWELRY_ID"
 
    @property
    def name(self) -> str:
        """
          珠宝名称
        :return:
        """
        return "未找到该珠宝"
 
    @property
    def type(self) -> str:
        """
        珠宝类型
        :return:
        """
        return "未知类型"
 
    @property
    def weight(self) -> float:
        """
       重量
        :return:
        """
        return 0.0
 
    @property
    def price(self) -> float:
        """
       原价
        :return:
        """
        return 0.0
 
    @property
    def stock(self) -> int:
        """
           折后价
        :return:
        """
        return 0
 
    @property
    def certificate_id(self) -> Optional[str]:
        """
          鉴定证书
        :return:
        """
        return None
 
 
    def calculate_discounted_price(self, discount: float = 0.95) -> float:
        """
        空对象的业务方法(无操作/返回默认值)
        :param discount:
        :return:
        """
        return 0.0  # 无珠宝则折后价为0
 
    def generate_sales_doc(self, customer_name: str) -> str:
        """
 
        :param customer_name:
        :return:
        """
        return f"【错误】客户{customer_name}:未找到对应珠宝,无法生成销售单"
 
    def reduce_stock(self, quantity: int = 1) -> bool:
        """
 
        :param quantity:
        :return:
        """
        return False  # 无珠宝则扣减库存失败
python 复制代码
# encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:空对象模式(Null Object Pattern)
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2024.3.6 python 3.11
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/3/9 21:01
# User      :  geovindu
# Product   : PyCharm
# Project   : pydesginpattern
# File      : JewelryWarehouse.py
from Interface.NullObject.NullJewelry import NullJewelry
from Model.NullObject.Jewelry import Jewelry
from Model.NullObject.RealJewelry import RealJewelry
 
 
# ----4:珠宝仓库(业务入口,使用空对象模式)
class JewelryWarehouse:
    """
    珠宝仓库:管理珠宝库存,提供查询珠宝的接口
    """
    def __init__(self):
        """
 
        """
        # 模拟珠宝库存数据
        self.jewelry_list = [
            RealJewelry(
                id="J001",
                name="18K金钻石戒指",
                type="钻石",
                weight=3.2,
                price=15800.0,
                stock=5,
                certificate_id="GIC20260308001"
            ),
            RealJewelry(
                id="J002",
                name="足金项链",
                type="黄金",
                weight=12.5,
                price=680.0 * 12.5,  # 黄金按克计价
                stock=10,
                certificate_id="GIC20260308002"
            ),
            RealJewelry(
                id="J003",
                name="冰种翡翠手镯",
                type="翡翠",
                weight=58.8,
                price=89800.0,
                stock=2,
                certificate_id="GIC20260308003"
            )
        ]
 
    def find_jewelry_by_id(self, jewelry_id: str) -> Jewelry:
        """
        查询珠宝:找到则返回RealJewelry,否则返回NullJewelry(而非None)
        :param jewelry_id:
        :return:
        """
        for jewelry in self.jewelry_list:
            if jewelry.id == jewelry_id:
                return jewelry
        # 核心:用NullJewelry替代None
        return NullJewelry()
python 复制代码
# encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:空对象模式(Null Object Pattern)
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2024.3.6 python 3.11
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/3/9 21:03
# User      :  geovindu
# Product   : PyCharm
# Project   : pydesginpattern
# File      : NullObjectBll.py
from abc import ABC
from NullObjectPattern.JewelryWarehouse import JewelryWarehouse
 
# ----5:业务流程测试(对比有无空对象模式的差异)
class NullObjectBll(object):
    """
 
    """
    def demo(self):
        """
 
        :return:
        """
        # 初始化仓库
        warehouse = JewelryWarehouse()
 
        # 场景1:查询存在的珠宝(J001),执行完整业务流程
        print("=== 场景1:查询存在的珠宝(J001)===")
        jewelry1 = warehouse.find_jewelry_by_id("J001")
        print(f"珠宝名称:{jewelry1.name}")
        print(f"原价:{jewelry1.price} 元")
        print(f"会员折后价:{jewelry1.calculate_discounted_price()} 元")
        print(f"扣减库存前:{jewelry1.stock}")
        print(f"扣减库存结果:{jewelry1.reduce_stock()}")
        print(f"扣减库存后:{jewelry1.stock}")
        print("销售单据:")
        print(jewelry1.generate_sales_doc("张三"))
 
        # 场景2:查询不存在的珠宝(J999),使用空对象避免空指针
        print("\n=== 场景2:查询不存在的珠宝(J999)===")
        jewelry2 = warehouse.find_jewelry_by_id("J999")
        # 无需判断jewelry2是否为None,直接调用方法即可
        print(f"珠宝名称:{jewelry2.name}")
        print(f"折后价:{jewelry2.calculate_discounted_price()} 元")
        print(f"扣减库存结果:{jewelry2.reduce_stock()}")
        print("销售单据:")
        print(jewelry2.generate_sales_doc("李四"))

调用:

python 复制代码
# encoding: utf-8
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: 设计模式 Design Patterns
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2023.1 python 3.11
# OS        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  oracle 21c Neo4j
# Datetime  : 2026/2/18 20:58
# User      : geovindu
# Product   : PyCharm
# Project   : pydesginpattern
# File      : main.py
# explain   : 学习
 
import Controller.CheckPatterns
 
 
def select_design_pattern() -> tuple[int, Controller.CheckPatterns.DesignPattern | None]:
    """
    返回 (序列号, 选中的枚举对象),退出则返回 (0, None)
    :return:
    """
    print("\n=== 方式3:用户选择展示 ===")
    print("可选设计模式(输入0或q退出):")
    for idx, pattern in enumerate(Controller.CheckPatterns.DesignPattern, 1):
        print(f"{idx}. {pattern._name_to_cn(pattern.name)}({pattern.name})")
    print("0. 退出")
 
    while True:
        user_input = input("\n请输入序号选择要展示的设计模式(输入0/q退出):").strip()
        if user_input in ("0", "q", "Q"):
            print("👋 退出选择流程")
            return (0, None)
 
        try:
            choice = int(user_input)
            if 1 <= choice <= len(Controller.CheckPatterns.DesignPattern):
                selected_pattern = list(Controller.CheckPatterns.DesignPattern)[choice - 1]
                print(f"✅ 你选择了序号:{choice}(对应{selected_pattern._name_to_cn(selected_pattern.name)})")
                return (choice, selected_pattern)  # 返回(序列号, 枚举对象)
            else:
                print(f"❌ 输入无效!请输入1-{len(Controller.CheckPatterns.DesignPattern)}之间的数字,或0/q退出")
        except ValueError:
            print("❌ 输入无效!请输入数字序号,或0/q退出")
 
def ask_continue() -> bool:
    """
    询问用户是否继续选择,返回True(继续)/False(退出)
    """
    while True:
        user_choice = input("\n是否继续选择其他设计模式?(y/n):").strip().lower()
        if user_choice == "y":
            return True
        elif user_choice == "n":
            print("👋 感谢使用,程序结束!")
            return False
        else:
            print("❌ 输入无效!请输入 y(继续)或 n(退出)")
 
 
if __name__ == '__main__':
 
    # 方式1:用户输入选择展示(交互版)
    '''
    print("\n=== 方式1:用户选择展示 ===")
    print("可选设计模式:")
    for idx, pattern in enumerate( bll.CheckPatterns.DesignPattern, 1):
        print(f"{idx}. {pattern._name_to_cn(pattern.name)}({pattern.name})")
 
    try:
        choice = int(input("\n请输入序号选择要展示的设计模式:"))
        selected_pattern = list( bll.CheckPatterns.DesignPattern)[choice - 1]
        selected_pattern.show_example()
    except (ValueError, IndexError):
        print("❌ 输入无效,请输入正确的序号!")
    '''
    # 2
 
    print("🎉 设计模式示例展示程序")
    while True:
        # 1. 选择设计模式
        selected_num, selected_pattern = select_design_pattern()
 
        # 2. 判断是否直接退出(输入0/q)
        if selected_num == 0:
            print("👋 程序结束!")
            break
 
        # 3. 执行选中的示例
        selected_pattern.show_example()
        print(f"\n📌 本次选择的序列号是:{selected_num}")
 
        # 4. 询问是否继续
        if not ask_continue():
            break  # 用户选择不继续,终止循环
 
    print('hi,welcome geovindu.')

输出:

相关推荐
wefly20172 小时前
M3U8 播放调试天花板!m3u8live.cn纯网页无广告,音视频开发效率直接拉满
java·前端·javascript·python·音视频
敷衍一下X2 小时前
Selenium元素定位
python·selenium·测试工具
与虾牵手2 小时前
Redis 缓存穿透,我在线上被教做人的全过程(附 3 种方案 + 代码)
python
lisus20072 小时前
GO并发统计文件大小
开发语言·后端·golang
梦游钓鱼2 小时前
Logger.h和Logger.cc文件分析
开发语言·c++
CRMEB系统商城2 小时前
CRMEB标准版系统(PHP)v6.0公测版发布,商城主题市场上线~
java·开发语言·小程序·php
安逸sgr2 小时前
【端侧 AI 实战】BitNet 详解:1-bit LLM 推理优化从原理到部署!
人工智能·python·scrapy·fastapi·ai编程·claude
我爱学习好爱好爱2 小时前
ELK日志分析平台(三):Logstash 7.17.10 独立节点部署与基础测试(基于Rocky Linux 9.6)
linux·python·elk
yangminlei2 小时前
openclaw对接飞书
开发语言·python·飞书