ATM 系统设计实战:基于 awesome-low-level-design 的 Python 低层设计实现解析

原创2026-10-01 14:43:151,976 阅读
文章标签:示例工程教程后端

ATM 系统设计实战:基于 awesome-low-level-design 的 Python 低层设计实现解析

导读

本文围绕开源仓库 awesome-low-level-design 中 ATM 系统的低层设计(Low Level Design)文档与 Python 实现展开。你将掌握 ATM 的六大核心需求拆解方法、从 Card/Account/Transaction 到 BankingService/CashDispenser/ATM 的完整类设计,以及并发安全(线程锁)、状态模式(State Pattern)、责任链模式(Chain of Responsibility)在真实项目中的落地写法。读完后,你可以直接把这套设计骨架复用到银行、支付、取款机等需要"认证 + 事务 + 并发一致性"的场景中。

一、需求分析与设计目标

ATM 系统的设计文档(solutions/python/atm/README.md)将需求明确划分为六条,它们是后续所有类的设计依据:

需求 对应设计落点
余额查询、取款、存款三种基础操作 OperationType 枚举 + ATM.select_operation 分发
卡片 + PIN 身份认证 Card 类与 BankService.authenticate
与银行后端交互校验账户并执行事务 BankService 作为银行侧服务门面
现金出钞 CashDispenser + 责任链式 NoteDispenser
并发访问与数据一致性 threading.Lock 对账户余额与出钞的互斥保护
用户友好交互界面 ATMState 状态机对流程的约束与错误提示

文档给出的七个核心构件(Card、Account、Transaction、BankingService、CashDispenser、ATM、ATMDriver)在 Python 实现中全部有对应文件,可以直接对照阅读。

二、核心类设计与源码走读

1. Card —— 卡片实体

card.py 是最简单的实体类,只承载两个不可变字段:

class Card:
    def __init__(self, card_number: str, pin: str):
        self._card_number = card_number
        self._pin = pin

PIN 以字符串形式存储(示例中为 "1234")。真实系统中 PIN 应加密存储,本示例仅用于教学演示。

2. Account —— 账户与余额操作

account.py 实现了余额的存取,并引入了第一个并发安全点:

class Account:
    def __init__(self, account_number: str, balance: float):
        self._balance = balance
        self._cards: Dict[str, Card] = {}
        self._lock = threading.Lock()

    def deposit(self, amount: float):
        with self._lock:
            self._balance += amount

    def withdraw(self, amount: float) -> bool:
        with self._lock:
            if self._balance >= amount:
                self._balance -= amount
                return True
            return False
  • 每个 Account 拥有独立的 threading.Lock,存款/取款均在锁内完成,避免并发读写导致余额不一致。
  • withdraw 先校验余额是否充足再扣减,返回 bool 表示是否成功;deposit 直接累加。
  • _cards 记录了绑定到该账户的卡片,形成"账户 ↔ 卡片"的多对多关联。

3. OperationType —— 操作枚举

operation_type.py 用标准库 enum 定义了三种操作:

class OperationType(Enum):
    CHECK_BALANCE = "CHECK_BALANCE"
    WITHDRAW_CASH = "WITHDRAW_CASH"
    DEPOSIT_CASH = "DEPOSIT_CASH"

4. BankService —— 银行后端门面

bank_service.py 扮演"银行后端系统",负责账户/卡片管理、认证与事务执行。构造函数中预置了两组演示数据:

account1 = self.create_account("1234567890", 1000.0)
card1 = self.create_card("1234-5678-9012-3456", "1234")
self.link_card_to_account(card1, account1)

account2 = self.create_account("9876543210", 500.0)
card2 = self.create_card("9876-5432-1098-7654", "4321")
self.link_card_to_account(card2, account2)

关键方法:

  • authenticate(card, pin):比较传入 PIN 与卡片存储 PIN 是否一致;
  • authenticate_card(card_number):按卡号从 _cards 字典取回 Card 对象,卡不存在返回 None;
  • get_balance / withdraw_money / deposit_money:通过 _card_account_map 从卡反查账户,再委托给 Account 执行;
  • link_card_to_account:建立卡片与账户的双向绑定。

文档中描述的 Transaction 抽象类(由 WithdrawalTransaction、DepositTransaction 继承)在 Go 实现中以 transaction.go 的形式存在,Python 版本则将取款/存款逻辑直接收敛到 ATM 层,属于同需求的不同实现风格,可对照学习。

5. CashDispenser 与责任链出钞

文档要求"现金分发器负责出钞并保证线程安全"。Python 实现拆成了两个文件:接口 dispense_chain.py 与实现 note_dispenser.py。

CashDispenser(cash_dispenser.py)持有链头并整体加锁:

class CashDispenser:
    def __init__(self, chain: DispenseChain):
        self._chain = chain
        self._lock = threading.Lock()

    def dispense_cash(self, amount: int):
        with self._lock:
            self._chain.dispense(amount)

    def can_dispense_cash(self, amount: int) -> bool:
        with self._lock:
            if amount % 10 != 0:
                return False
            return self._chain.can_dispense(amount)
  • 出钞前先调用 can_dispense_cash 做可用性预检,取款金额必须为 10 的倍数;
  • NoteDispenser 实现 dispense:优先用大面额,剩余金额转交 _next_chain 继续处理,形成责任链;can_dispense 同样沿链递归判断面额组合能否凑出目标金额;
  • 三个具体节点以子类形式定义:NoteDispenser100(10)、NoteDispenser50(20)、NoteDispenser20(30)(见 atm.py),即 10 张 $100、20 张 $50、30 张 $20。

6. ATM 门面 + 单例

atm.py 中的 ATM 是用户操作的门面,同时实现了线程安全的单例:

class ATM:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._initialized = False
        return cls._instance

双重检查加锁(Double-Checked Locking)确保多线程环境下只创建一个 ATM 实例。__init__ 中完成状态机、银行服务、出钞链的一次性组装。

取款流程(withdraw_cash)展示了"先预检、再扣账、失败回滚"的事务语义:

def withdraw_cash(self, amount: int):
    if not self._cash_dispenser.can_dispense_cash(amount):
        raise RuntimeError("Insufficient cash available in the ATM.")

    self._bank_service.withdraw_money(self._current_card, amount)

    try:
        self._cash_dispenser.dispense_cash(amount)
    except Exception as e:
        self._bank_service.deposit_money(self._current_card, amount)  # 出钞失败则回存
        raise e

7. ATMState —— 状态模式约束交互

atm_state.py 定义了抽象基类与三个具体状态:

  • IdleState:等待插卡。插卡时按卡号查找卡片,找不到直接退卡,找到则进入 HasCardState;
  • HasCardState:等待输 PIN。PIN 正确进入 AuthenticatedState,错误则退卡回到空闲;
  • AuthenticatedState:执行操作。select_operation 按 OperationType 分发到余额查询/取款/存款,完成一次交易后自动退卡回到 IdleState。

非法操作(如未插卡就输 PIN、重复插卡、金额为负)在各状态中被拦截并打印明确错误,这正是文档"用户友好交互"需求的体现。状态转移调用 atm.change_state(...) 完成。

三、运行方式与完整演示

演示入口 atm_demo.py 与文档中的 ATMDriver 角色对应,依次执行:

cd solutions/python/atm
python atm_demo.py

演示脚本覆盖了主要路径与异常分支(使用默认账户 1234-5678-9012-3456 / PIN 1234,初始余额 $1000):

步骤 操作 预期结果
1 插卡 + PIN + 查余额 打印余额 $1000.00
2 插卡 + PIN + 取款 $570 出钞 5×$100 + 1×$50 + 1×$20,余额变 $430
3 插卡 + PIN + 存款 $200 余额变 $630
4 再查余额 打印余额 $630.00
5 取款 $700(超过余额) 打印 "Insufficient balance"
6 输入错误 PIN "3425" 认证失败并退卡

从输出可以看到责任链的实际出钞分配:$570 由 $100 面额链节点先出 5 张,余 $70 交给 $50 节点出 1 张,最后 $20 节点出 1 张,剩余为 0 时完成。

四、设计模式与并发要点的归纳

这套 Python 实现是"状态模式 + 责任链模式 + 单例模式 + 线程安全"的组合样板,文档与源码的对应关系如下:

设计要点 模式/机制 源码位置
插卡 → 输 PIN → 操作 的流程状态机 State Pattern atm_state.py
大额 → 小额递进出钞 Chain of Responsibility dispense_chain.py + note_dispenser.py
全局唯一 ATM Singleton(双重检查加锁) atm.py
余额与出钞并发安全 threading.Lock 互斥 account.py / cash_dispenser.py
事务失败回滚 先扣账、出钞失败回存 atm.py

五、与其他语言实现的横向对照

同一套需求在仓库中还有多种实现,适合对比学习"同样的设计如何在不同语言中表达":

各实现的类图可参考仓库 class-diagrams/atm-class-diagram.png,它直观展示了 Card、Account、BankService、CashDispenser、ATM 与状态类之间的静态关系。

六、可延伸的改进方向

基于现有代码结构,可推断出以下增强点(均为合理的工程推演,非仓库已实现功能):

  1. PIN 加密与失败次数限制:目前 PIN 明文比较、可无限重试;可引入哈希存储与"三次错误锁卡"策略;
  2. Transaction 抽象层:若需记录交易流水、做持久化,可参照 Go 版把取款/存款收敛为 Transaction 子类,便于审计与回滚;
  3. 更细粒度的并发控制:当前用粗粒度锁保护整个出钞链,高并发场景可改为按面额分锁;
  4. 真实银行后端对接:BankService 目前是进程内模拟,可替换为 RPC/HTTP 客户端而不改动 ATM 门面。

这些方向都建立在文档"并发访问与数据一致性"与"与银行后端交互"两条需求的延伸之上。

参考路径速查

登录后查看全文
awesome-low-level-design