Ran Wei / 面向对象设计 / 第一模块
EN
面向对象设计 — Ran Wei

第一模块:对象与类

什么是对象?类如何定义对象?实例如何被创建?——以网络书店为贯穿全程的案例。

预备知识:Python 基础 Python 3 UML 类图 SysML v2 对应 约 45 分钟阅读
1

从现实世界到软件

🏠 生活类比
建筑师设计一栋楼之前,会先画建筑图纸。图纸不是楼本身,但它完整记录了楼的每一个细节。面向对象设计做的事情正是这个:将现实世界中的事物(书、顾客、订单……)翻译成软件中的对象。

软件系统用来表示和管理现实世界中的事物。一家网络书店涉及书籍、顾客、订单和支付。面向对象设计(OOD)为我们提供了一种将这些事物翻译成软件的原则性方法——直观、可维护、可扩展。

核心思路是:识别问题域中各种独立的事物,描述每样事物知道什么(数据)和能做什么(行为),再定义一个模板(类),从中按需创建任意数量的对象。

网络书店领域

整个课程使用同一个领域。下表列出我们将在八个模块中逐步构建的所有类:

现实世界的事物我们要建的类
一本书Book
一个注册用户Customer
购物车ShoppingCart
一笔购买交易Order
订单中的一行OrderItem
支付方式Payment
库存管理Inventory
用户评论Review

表 1 — 网络书店中的现实事物与对应的类。

2

对象——面向对象设计的基本单位

🏠 生活类比
一本实际的书就是一个对象。它有自己的信息(书名、书号、价格……),也有自己能做的事情(打折、查库存……)。对象就是这样一个把数据和行为封装在一起的软件实体。

每个对象都有三个基本属性:

属性含义
身份(Identity)每个对象都是独立的个体。就算两本书的书名、价格完全相同,它们仍然是两个不同的对象。
状态(State)对象当前持有的数据。状态可以随时间改变——书的库存数量随着售出而减少。
行为(Behaviour)对象能做什么,用方法表达。行为在类中定义一次,该类的所有对象共享。

表 2 — 对象的三个基本属性。

💡 核心思想:封装
对象把数据(状态)和操作(行为)打包在一起,不分开。这种打包叫做"封装",是面向对象的核心原则之一。模块二会详细讲解封装。现在只需记住:对象是一个自包含的胶囊,知道一些事情(状态),也能做一些事情(行为)。
3

类——创建对象的设计图

🏠 生活类比
有了设计图不等于有了楼房;实例化就是"照图施工"的过程。在 Python 中,"施工"就是直接调用类名。
概念定义与示例
属性(Attribute)对象内部的数据。每个对象有自己的一份属性副本。示例:Bookisbntitleauthorpricestock
方法(Method)对象能做的事情。方法定义在类中,该类所有对象共享。示例:is_available() 检查库存是否大于零。
构造函数(Constructor)创建对象时自动调用的特殊方法。在 Python 中就是 __init__,负责把属性初始化为起始值。

表 3 — 属性、方法与构造函数。

Python 实现:Book 类

Python
class Book:
    """书店中可购买的一本书。"""

    # ── 构造函数 ──────────────────────────────────────────
    def __init__(self, isbn: str, title: str, author: str,
                 price: float, stock: int = 0):
        # self 指向正在被创建的那个对象
        self.isbn   = isbn     # 唯一编号
        self.title  = title   # 书名
        self.author = author  # 作者
        self.price  = price   # 定价(英镑)
        self.stock  = stock   # 库存数量

    # ── 方法 ─────────────────────────────────────────────
    def get_details(self) -> str:
        """返回该书的可读描述。"""
        return f"{self.title} 作者:{self.author} £{self.price:.2f}"

    def is_available(self) -> bool:
        """库存大于零时返回 True。"""
        return self.stock > 0

    def apply_discount(self, percent: float) -> None:
        """按百分比打折(输入 0–100)。"""
        if not 0 <= percent <= 100:
            raise ValueError("折扣必须在 0 到 100 之间")
        self.price *= (1 - percent / 100)
💡 self 是什么?
在 Python 的方法里,self 指向调用该方法的那个具体对象。每次定义方法时,第一个参数必须是 self,但调用时 Python 会自动传入,不需要手动写。

UML 类图记法

UML 把类画成一个分三格的矩形:上格是类名,中格是属性,下格是方法。属性前的字符表示可见性: 表示私有,+ 表示公开。

UML 类图:Book 与 Customer
图 1 — UML 类图:Book 与 Customer。属性前标注 −(私有),方法前标注 +(公开)。
4

实例化——创建对象

实例化是从类创建一个具体对象的行为。在 Python 中,直接调用类名并传入构造函数所需的参数即可。每次调用都会产生一个完全独立的对象,拥有自己的属性副本。

Python
# 用同一个 Book 类创建两本不同的书
book1 = Book(
    isbn   = "978-0-13-110362-7",
    title  = "The C Programming Language",
    author = "Kernighan & Ritchie",
    price  = 45.99,
    stock  = 12
)

book2 = Book(
    isbn   = "978-0-20-163361-0",
    title  = "The Pragmatic Programmer",
    author = "Hunt & Thomas",
    price  = 39.99,
    stock  = 0       # 已缺货
)

print(book1.get_details())   # "The C Programming Language 作者:Kernighan & Ritchie £45.99"
print(book1.is_available())  # True
print(book2.is_available())  # False

book1.apply_discount(10)    # 只对 book1 打九折
print(book1.price)           # 41.391
print(book2.price)           # 39.99 — book2 不受影响

Customer 类

书店还需要用户。按照同样的模式:

Python
class Customer:
    """书店的注册用户。"""

    def __init__(self, customer_id: str, name: str, email: str):
        self.customer_id = customer_id
        self.name        = name
        self.email       = email

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

    def update_email(self, new_email: str) -> None:
        self.email = new_email


# 创建一个用户对象
customer1 = Customer("C001", "陈晨晨", "[email protected]")
print(customer1.get_name())  # "陈晨晨"

customer1.update_email("[email protected]")
print(customer1.email)       # "[email protected]"
5

对象身份 vs 对象值

🏠 生活类比
两张完全相同的银行卡:卡号、姓名、余额全部一样。但它们就是两张不同的卡——在其中一张上划卡不会影响另一张。这就是对象身份的含义。
Python
bookA = Book("978-0-13-110362-7", "The C Programming Language",
             "Kernighan & Ritchie", 45.99, 5)
bookB = Book("978-0-13-110362-7", "The C Programming Language",
             "Kernighan & Ritchie", 45.99, 5)

# 内容相同……
print(bookA.title == bookB.title)   # True  — 值相同

# ……但是不同的对象
print(bookA is bookB)               # False — 身份不同

# 修改一个不影响另一个
bookA.apply_discount(20)
print(bookA.price)  # 36.792
print(bookB.price)  # 45.99 — 不受影响
运算符测试的内容
==值相等——对象的内容是否相同?
is身份相同——两个变量是否指向内存中完全相同的一个对象?

表 4 — == 测值,is 测身份。

⚠️ 常见错误
初学者常把 ==is 搞混。对于自定义类,如果没有定义 __eq__ 方法,== 会退化为测身份。我们将在模块三讲解如何正确定义 __eq__
UML 对象图:两个 Book 实例与一个 Customer 实例
图 2 — 对象图:book1book2Book 的两个独立实例,各有自己的状态。customer1Customer 的独立实例。
6

UML 两种图的区别

图的类型展示内容
类图(Class Diagram)设计图——类名、属性名称和类型、方法签名。没有具体数据,描述所有对象的共同结构。
对象图(Object Diagram)实况快照——具体对象及它们在某一时刻的属性值。对象标题格式为 对象名 : 类名,并带下划线。

表 5 — 类图 vs 对象图。

💡 对象图的标题格式
对象标题应加下划线,格式为"对象名 : 类名"。如果对象没有变量名,只写 ": 类名" 也是合法的 UML。
7

SysML v2 对应关系

SysML v2 中"定义/用法"的区分,是 OOD 中"类/对象"区分的直接推广。对应关系非常精确:

OOD 概念
SysML v2
类(设计图)
part def
对象(实例)
part
属性
attribute
方法
action def / action
构造函数
隐式 — 用 := 设初始值
实例化
part myBook : Book
SysML v2
// SysML v2 中等价于 Book 类的写法
part def Book {
    attribute isbn   : String;
    attribute title  : String;
    attribute author : String;
    attribute price  : Real;
    attribute stock  : Integer := 0;   // := 是初始值

    action def get_details   : String;
    action def is_available  : Boolean;
    action def apply_discount { in percent : Real; }
}

// 创建一个具体的 Book 实例
part book1 : Book {
    :>> isbn   = "978-0-13-110362-7";
    :>> title  = "The C Programming Language";
    :>> author = "Kernighan & Ritchie";
    :>> price  = 45.99;
    :>> stock  = 12;
}
💡 一个重要区别
在 Python 中,对象在程序运行时创建。在 SysML v2 中,part建模时的声明,描述系统架构中的一个具体元素,而不是运行时堆上的内存分配。结构类比成立;运行语义不同。
8

本模块总结

封装

对象 = 数据(状态)+ 行为(方法)打包在一起。这种打包叫封装。

类 vs 对象

类是设计图,对象是实例。一个类可以创建无数个对象,每个对象独立拥有自己的状态。

实例化

从类创建对象。Python 里直接写 book1 = Book(...)

三个属性

每个对象都有身份(独立个体)、状态(属性当前的值)和行为(可调用的方法)。

身份 vs 值

两个对象可以持有相同的数据,但仍然是完全独立的个体。== 测值,is 测身份。

SysML v2

类 → part def;对象 → part。定义/用法的区分是类/实例区分的直接推广。

📦
下一讲 — 第二模块

封装

数据隐藏、公开与私有接口、内聚性原则——以及为什么封装良好的类更易使用、测试和维护。我们将在 BookCustomer 的基础上引入 ShoppingCart