☰
Python面向对象编程实战:从类与对象到dataclass的完整指南
2026/10/7 4:45:18 网站建设 项目流程

直接上手写代码久了,尤其是写过几个项目以后,很多人会对Python面向对象编程(OOP)产生一种"学了但没用上"的感觉。函数加字典好像就能解决大部分问题,类反而显得啰嗦。但真当项目膨胀到几万行,或者需要多人协作维护时,有没有用OOP组织代码,差距会非常明显。这篇文章我不会跟你念教科书定义,而是从"为什么需要OOP"讲起,把类与对象、封装继承多态、魔术方法、dataclass这些核心机制逐个拆开,配合能直接跑的代码和实战中踩过的坑,帮你建立一套能落地的面向对象设计思路。适合刚学完Python语法、准备进阶的中级开发者,也适合一直在用函数式写法、想重构项目的老手。

1. 从一段"能跑但没法维护"的代码说起

1.1 面向过程的写法是怎么一步步失控的

先假设你要写一个简单的库存管理程序。最初的需求很简单:记录商品名称、价格、库存数量,卖出一件就减库存。用函数加字典,第一版很快就能写完:

# 面向过程版本 v1 inventory = {} def add_product(name, price, quantity): inventory[name] = {"price": price, "quantity": quantity} def sell_product(name, quantity): if name not in inventory: print("商品不存在") return if inventory[name]["quantity"] < quantity: print("库存不足") return inventory[name]["quantity"] -= quantity add_product("苹果", 5.5, 100) sell_product("苹果", 3) print(inventory)

这段代码在功能上完全没问题。但接着需求变了:每种商品还要记录进货日期、保质期,卖出的同时要生成订单记录,后台还需要按供应商维度统计数据。这时候你会怎么做?大概率是继续往字典里塞键,继续加函数,参数列表越来越长,函数之间的隐式依赖越来越多。

大概到第三四个需求叠加进来,就会出现这种状态:一个函数改了库存结构,另外五个函数都得跟着改;字典里某个键在某个分支忘记初始化,运行时报KeyError;调试的时候频繁需要打印整个字典来确认当前状态。最要命的是,程序里没有一个地方能清晰地表达"商品到底是什么",只有一堆散落的字典和操作它们的函数。

1.2 OOP真正解决的是"数据与行为的归属问题"

面向对象编程的核心并不神秘,它做了一件非常简单的事:把数据和对数据的操作绑在一起。商品的价格、库存、保质期是数据,加减库存、判断是否过期是操作。在OOP里,这些属于同一个"商品"对象,而不是分散在不同函数里。

这带来两个很实际的好处。第一,修改的影响范围被限制住了。想改库存的扣减逻辑,只需要去改商品类内部的方法,调用方根本不关心你内部是减一还是减十。第二,代码的阅读成本大幅降低。看到product.sell(3),语义一目了然;而看到sell_product(inventory, "苹果", 3),你还要去查函数签名确认参数顺序。

对此,我个人的体会是:OOP不是一种语法装饰,而是一种思维上的"分活"机制。你用类把不同的职责切分开,每个类只对自己的数据和行为负责,协作时只需要暴露方法,不需要暴露内部结构。这跟现实中公司分部门是一个道理——财务部不会直接去翻仓库的货架,它只跟仓库管理系统对接。

当然,OOP也有适用边界。写一个几十行的处理脚本、做数据分析的流水线、或者一次性爬虫任务,硬套类反而画蛇添足。我见过有人写了两个类去封装一个 requests.post 调用,这种"为了OOP而OOP"的做法同样不可取。合适的判断标准是:当你的数据结构和操作它的逻辑开始被多处复用,或者需求复杂度让你觉得"这个函数我快不知道该传什么参数了",那就是考虑用类来收拢的时机。

2. 类与对象:图纸和实车的关系

2.1 定义类的"标准姿势"里藏着哪些约定

类和对象的关系,用"图纸和实车"来类比最形象。类就是图纸,它规定了实体应该有什么属性、能做什么操作;对象是根据图纸造出来的那台具体车子,有自己的实际数值。同一个类可以造出无限多个互不干扰的对象。

Python里定义一个最基础的类长这样:

class Product: def __init__(self, name, price, quantity): self.name = name self.price = price self.quantity = quantity def sell(self, amount): if self.quantity < amount: raise ValueError(f"{self.name} 库存不足") self.quantity -= amount return self.quantity product = Product("苹果", 5.5, 100) product.sell(3)

这段代码里有几个初学者最容易懵的点。__init__不是构造器,准确地说它是"初始化方法"。在你调用Product(...)的时候,Python实际上先调用__new__分配内存创建了一个空对象,然后再调用__init__对这个空对象做属性赋值。__init__不需要 return 任何东西,写了也不会生效。

再说self。它代表"正在被操作的那个对象实例"。你调用product.sell(3)时,Python干了一件隐式的事:Product.sell(product, 3),自动把实例本身作为第一个参数传进去。这也是为什么类里所有普通方法的第一个参数都叫self。很多新手问"为什么self不用传参",答案就是解释器帮你传了。

属性赋值在__init__里做而不是在类体里做,这个习惯很重要。直接在类体里写quantity = 100定义的是类属性,它被所有实例共享,后面我会细说它的坑。

2.2 实例属性与类属性:共享和私有的边界

类属性是定义在类体中的变量,实例属性是在__init__里通过self.xxx赋值的变量。两者的本质区别在于归属和查找顺序。

class Product: category = "生鲜" # 类属性,所有实例共享 def __init__(self, name, price): self.name = name # 实例属性 self.price = price

当访问product.category时,Python先在实例的__dict__里找,找不到就去类的__dict__里找。实例属性是自己专属的,改一个不影响另一个;但类属性是共享的。最容易踩的坑是"通过实例修改类属性":

p1 = Product("苹果", 5.5) p2 = Product("香蕉", 3.0) p1.category = "水果" # 这不会修改类属性!只是给p1创建了同名实例属性 Product.category = "水果" # 这才是修改类属性

因为实例赋值操作永远是在创建或修改实例自己的属性,不会动类属性。这个机制理解到位后,你就能解释很多"奇怪"的行为了。类属性适合放那些所有实例都一样的默认值、或者需要全局共享的计数器。注意:如果类属性的值是列表、字典这类可变对象,所有实例共享同一个对象,一个实例修改了内容,其他实例看到的是修改后的结果。这既是特性也是隐患,得用对场景。

2.3 实例方法、类方法、静态方法:三种工具的取舍

普通实例方法接收self,这是最常用的。类方法用@classmethod装饰,第一个参数是cls,接收的是类本身;静态方法用@staticmethod装饰,不接收self也不接收cls,就是一个放在类命名空间里的普通函数。

它们在什么时候派上用场,我举个例子:

class Product: def __init__(self, name, price, quantity): self.name = name self.price = price self.quantity = quantity @classmethod def from_dict(cls, data): # 用类方法提供"替代构造器" return cls(data["name"], data["price"], data["quantity"]) @staticmethod def is_valid_price(price): # 跟实例无关的校验逻辑 return price > 0 def total_value(self): return self.price * self.quantity

我的使用经验是:类方法的典型场景是"根据不同的参数形态创建实例"。比如从配置文件、从字典、从数据库行分别构造对象,你可以定义多个类方法作为不同入口,逻辑集中在类内部。静态方法适合放那些与类相关、但又不依赖类任何状态的工具函数,比如校验、格式化。

值得一提的是,静态方法和模块级普通函数本质没有区别,不必为了"显得面向对象"而硬塞。我见过有些人把所有辅助函数都塞进类里加 staticmethod,结果类变成了一个盛杂物的筐,这反而不利于维护。一个简单的判断标准:这个函数如果抽离出来,还跟这个类有紧密的语义关联吗?如果没有,放模块级更清晰。

3. 封装、继承、多态:三大支柱的设计意图

3.1 封装不是"藏数据",是"管好门口"

很多人一讲封装就背"把属性设为私有",但实际上Python没有真正的私有机制。所谓_name和__name,前者是约定(别碰我),后者是名字修饰(_ClassName__name),都是防君子不防小人的。理解封装,关键不在于"别人不能访问",而在于"你不需要知道内部怎么实现"。

像我手头这个商品例子,对外暴露的是total_value()、sell()这些操作,内部到底是用字典还是用数据库存储过期时间,调用方不应该关心。只要方法签名不变,内部随便重构,外部代码一行不动。这就是封装真正的价值——它把"变"的部分关在屋里,把"不变"的接口留在门外。

3.2 继承的正确姿势:先问"是不是",再问"有没有"

继承能实现代码复用,但滥用继承是项目腐化的最大根源。判断该不该继承,用的判断标准是"is-a"关系:子类确实是一种父类吗?猫是动物,所以猫继承动物没问题;但"猫需要喝水"这件事,不应该通过让猫继承"喝水器"来实现,那是"has-a"关系,用组合。

Python支持多继承,语法上没什么门槛,但组合起来时的方法解析顺序(MRO)会成为诡异bug的来源。即使有C3线性化算法兜底,我仍然建议绝大多数场景用单继承+多层继承,或者干脆用组合。一个非常实际的经验是:把公共逻辑放到一个基类里,子类只做差异化的部分;如果发现两个类之间的复用关系是"我希望借用它几个方法",优先考虑把那些方法抽成独立的类,通过依赖注入来协作,而不是制造继承链。

3.3 多态:接口统一,行为各异

多态在Python里比在Java、C#里自然得多。Java要显式地定义接口或者抽象类,Python依靠鸭子类型——"如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子"。只要一个对象有sell方法,不管它是Product还是别的什么类,调用方都能用它。

class DigitalProduct: def sell(self, amount): print(f"数字商品卖出 {amount} 份,无需减库存") for product in [Product("苹果", 5.5, 100), DigitalProduct("电子书", 9.9, 9999)]: product.sell(1)

这段代码里,调用循环根本不需要知道列表里是哪种商品,统一调用sell就行。这就是多态的好处:写通用的代码,适配无穷多的具体类型。在Python里,与其继承一个基类再去重写方法,不如直接约定方法名和签名。这是Python的灵活之处,也是它的风险之处——约定一旦被破坏,运行期才报错。为此,后面我会讲抽象基类,它能在一定程度上把这种"运行期才发现"变成"定义期就约束"。

3.4 MRO与super():多继承下你要知道的事

在单继承里,super()调用父类方法,非常直观。多继承下,super()不一定是调"直接父类",而是按MRO顺序调用下一个类。MRO遵循C3线性化:子类优先于父类,多个父类按声明顺序从左到右,保证每个父类在MRO中只出现一次且顺序一致。

class A: def greet(self): print("A") super().greet() class B(A): def greet(self): print("B") super().greet() class C(A): def greet(self): print("C") super().greet() class D(B, C): pass D().greet()

执行顺序是 D -> B -> C -> A。很多人会误以为 super() 直接从B跳到A,实际上它把C也走了一遍。这种"协作式多继承"是Python的特色,但如果父类的 greet 里都有各自的 super 调用而没有最终兜底类,会陷入死循环。所以多继承的基类设计要格外小心,最好有一个根类把链收尾。老实说,日常业务代码里能用菱形继承的机会微乎其微,你只要记住"MRO是diamonds顺序、用mro查看"就够应付绝大多数问题了。

4. 魔术方法:让自定义类"活成"原生类型

4.1new:想拦在初始化之前,先弄清楚它和init的分工

绝大多数情况下,你只需要__init__。但当你需要控制对象创建本身时(比如实现单例模式、缓存复用实例、创建不可变对象),就要重写__new__。__new__是真正的构造过程,它负责创建并返回实例;如果__new__返回的不是当前类的实例,__init__不会被调用。

一个典型的单例实现:

class Singleton: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance s1 = Singleton() s2 = Singleton() print(s1 is s2) # True

这里有个容易被忽略的规则:__new__是类方法,但不需要加@classmethod,因为Python会特殊处理。它的第一个参数是cls,而不是self。每次实例化的时候,__new__先跑,__init__后跑;如果__new__返回的是已有实例,那么__init__还会被再次调用,Simingleton模式里常见的坑就是对同一个实例重复初始化。

4.2str和repr:调试体验的分水岭

__str__定义的是给人看的信息,str(obj)和print(obj)走它;__repr__定义的是给解释器/开发者看的信息,直接敲对象名或者在列表里查看元素走它。没有实现__str__时,Python 会退回去用__repr__。

class Product: def __init__(self, name, price): self.name = name self.price = price def __repr__(self): return f"Product(name={self.name!r}, price={self.price})" def __str__(self): return f"{self.name}: ¥{self.price}" p = Product("苹果", 5.5) print(p) # 走 __str__,输出:苹果: ¥5.5 print([p]) # 列表打印走 __repr__,输出:[Product(name='苹果', price=5.5)]

我强烈建议每个类都实现__repr__,而且让它尽量输出能重建对象的完整信息。这样你在控制台看到Product(name='苹果', price=5.5)时,一眼就知道这个对象的值;如果它只打出<Product object at 0x...>,那你每次调试都要额外打印属性,效率低得离谱。

4.3eq与hash:配对规则决定了你能否把对象放进集合

默认情况下,两个对象相等基于身份比较is,也就是内存地址。要实现"值相等",重写__eq__:

class Product: def __init__(self, sku, name): self.sku = sku self.name = name def __eq__(self, other): if not isinstance(other, Product): return NotImplemented return self.sku == other.sku def __hash__(self): return hash(self.sku)

Python有一条规则:如果重写了__eq__而没有定义__hash__,类会变成不可哈希的(__hash__被设为 None),不能放进集合或作为字典键。原因是两个相等的对象必须有相同的哈希值,你自定义了相等逻辑后,默认的哈希实现可能违反这条规则。所以要么同时定义__hash__,要么明确把__hash__设为 None(表示这个对象不可哈希)。哈希值只需要基于参与相等比较的字段来计算即可。

我自己在写数据模型的时候,习惯把sku这类业务唯一标识作为相等和哈希的依据,而不是把所有字段都算进去。如果两个对象所有字段都相同才相等,那在集合里去重可能不符合预期。

4.4call:让实例拥有函数的"手感"

实现了__call__后,实例可以像函数一样被调用。这在状态化函数、装饰器、以及某些回调场景里非常实用。

class Logger: def __init__(self, prefix): self.prefix = prefix def __call__(self, message): print(f"[{self.prefix}] {message}") log = Logger("INFO") log("服务已启动") # 输出 [INFO] 服务已启动

跟闭包相比,__call__的类方式最大的优势是可读性和可扩展性明确:状态存在__init__的属性里,行为集中在__call__里,要加方法直接加。闭包再复杂一点,嵌套函数读起来就很吃力了。

4.5enter和exit:把资源管理封装进"with"里

with open(...) as f背后的机制就是上下文管理器协议。你写自己的类时,如果它有"使用前需要准备、使用后需要清理"的语义,就可以实现这两个方法:

class DatabaseConnection: def __enter__(self): print("连接数据库") return self def __exit__(self, exc_type, exc_val, exc_tb): print("关闭数据库连接") return False with DatabaseConnection() as conn: print("执行查询")

__exit__接收三个参数,分别代表异常类型、异常值、回溯信息。如果 with 块里抛了异常,这三个参数会被填上;返回 True 表示异常被吞掉,返回 False 表示继续向上抛。多数情况我推荐返回 False,不要静默吞异常,不然排障的时候会非常痛苦。

5. 进阶工具箱:dataclass、property 与抽象基类

5.1 dataclass:用最少的样板代码定义数据模型

Python 3.7 之后引入了@dataclass装饰器,它是我推荐任何写业务代码的人优先考虑的选择。它自动帮类生成__init__、__repr__、__eq__,省下的代码量相当可观:

from dataclasses import dataclass @dataclass class Product: name: str price: float quantity: int = 0

这一小段代码就能获得与手写__init__、__repr__、__eq__相同的效果,默认值也支持了。更重要的是,类型注解强制你把字段说清楚,IDE补全体验也好了不少。

dataclass还支持frozen=True,生成不可变对象,这在线程安全场景很受用;支持field(default_factory=list)来应对可变默认值问题(这个坑下面会重点聊)。不过要注意,dataclass仍然是个普通类,你还能给它加方法、加__post_init__做校验或者派生字段。如果你维护的是旧Python版本(3.6及以下),也可以用attrs库,体验几乎一样。

5.2 property:把方法调用"伪装"成属性访问

@property能把方法伪装成属性,好处是调用方代码不用加括号,而且可以在赋值或读取时插入校验逻辑。更重要的是,它允许你在不改变对外接口的情况下改内部实现:

class Product: def __init__(self, name, price, quantity): self._name = name self._price = price self._quantity = quantity @property def total_value(self): return self._price * self._quantity @property def price(self): return self._price @price.setter def price(self, value): if value < 0: raise ValueError("价格不能为负") self._price = value

有个容易被诱惑的点:property和getter/setter组合会让代码变得冗长。如果你纯粹为了"封装"而把每个属性都包一层property,无异于自找麻烦。Python不像Java,没有"所有字段必须私有再加getter"的惯例。我的实践是:先直接用公开属性,等真的需要校验或者计算派生字段时,再升级为property。这个升级不需要改外部调用,所以说property是"成本最低的演进手段"。

5.3 抽象基类与鸭子类型:约定 vs 强制

鸭子类型灵活,但出了问题要等运行期才暴露。抽象基类(ABC)则能在"定义期"就约束子类必须实现某些方法,把错误提前:

from abc import ABC, abstractmethod class Product(ABC): @abstractmethod def sell(self, amount): pass class Fruit(Product): def sell(self, amount): print(f"卖出 {amount} 个水果") class InvalidProduct(Product): pass # 直接实例化会报 TypeError

两个使用场景值得考虑用ABC:一是框架代码,你发布一个类,希望使用方必须实现某些接口;二是团队协作,统一约定接口防止有人偷懒漏实现。但单兵作战或者内部临时脚本,强行上ABC反而增加噪音。Python生态更推荐的做法是"Protocol"——结构子类型,它不需要继承任何基类,只要类的方法签名匹配就算满足协议。这个思想更贴合鸭子类型,比ABC更Pythonic,适合那些"不打算建立继承关系"的场景。

6. 从需求到代码:一个完整建模案例

6.1 原始需求

下面用一个简单但真实的小项目来走一遍OOP建模过程。需求是:做一个命令行图书库存系统,支持添加图书、按ISBN查询图书、借出图书、归还图书、查看所有过期未还的记录。不需要数据库,先存内存,但要留好持久化接口。

6.2 识别领域对象

初看需求,最容易把"图书"设计成一个类,"借阅记录"又是一个类,"图书管理系统"再一个类。这个划分基本合理。借出和归还涉及到的核心状态是"书的库存量"和"借阅关系",所以Book负责自己的库存增减,BorrowRecord负责记录借阅信息,Library负责组织整体流程。

这种设计背后,每个类的职责单了一:Book不知道外头谁在借书,BorrowRecord不知道书的定价,Library把一切串起来。以后想加"逾期罚款",只需要在BorrowRecord里加字段和方法,Library里加流程,其他类不用动。

6.3 代码落地

from dataclasses import dataclass, field from datetime import date, timedelta from typing import Dict, List, Optional @dataclass(frozen=True) class Book: isbn: str title: str author: str @dataclass class BorrowRecord: isbn: str borrower: str borrow_date: date due_date: date return_date: Optional[date] = None def is_overdue(self, today: Optional[date] = None) -> bool: today = today or date.today() return self.return_date is None and today > self.due_date class Library: def __init__(self): self._books: Dict[str, Book] = {} self._stocks: Dict[str, int] = {} self._records: List[BorrowRecord] = [] def add_book(self, book: Book, quantity: int = 1) -> None: if book.isbn not in self._books: self._books[book.isbn] = book self._stocks[book.isbn] = quantity else: self._stocks[book.isbn] += quantity def find_book(self, isbn: str) -> Optional[Book]: return self._books.get(isbn) def borrow(self, isbn: str, borrower: str, days: int = 14) -> None: stock = self._stocks.get(isbn, 0) if stock <= 0: raise ValueError(f"图书 {isbn} 无可借库存") self._stocks[isbn] -= 1 self._records.append(BorrowRecord( isbn=isbn, borrower=borrower, borrow_date=date.today(), due_date=date.today() + timedelta(days=days), )) def return_book(self, isbn: str, borrower: str) -> None: for record in self._records: if (record.isbn == isbn and record.borrower == borrower and record.return_date is None): record.return_date = date.today() self._stocks[isbn] += 1 return raise ValueError(f"未找到 {borrower} 借阅 {isbn} 的记录") def overdue_records(self) -> List[BorrowRecord]: return [r for r in self._records if r.is_overdue()]

这里有几个建模细节值得说。Book用frozen=True,ISBN、书名、作者是写死的,杜绝修改,很契合"图书元数据"的语义。BorrowRecord用普通dataclass,因为return_date可变。库存单独用一个字典_stocks映射ISBN到数量,而不是把quantity塞进Book里——因为图书信息是不可变的元数据,库存是可变的业务状态,两者混在一起会在传引用时出bug。

使用系统的代码长这样:

lib = Library() lib.add_book(Book("978-7-100-12345-6", "Python编程", "张三"), 3) lib.borrow("978-7-100-12345-6", "李四") lib.return_book("978-7-100-12345-6", "李四") print(lib.overdue_records())

6.4 这个案例的扩展方向

这套模型后续演进的空间很清晰。想接数据库,只需要把Library类里的字典替换成持久化存储,方法签名保持不变,调用方无感。想增加"逾期罚款",给BorrowRecord加fine方法和compute_fine逻辑。想支持多副本独立编号,可以再引入一个Copy实体。这种"变更局部化"正是OOP建模的回报——需求变的时候,你大概知道应该改哪个类,而不是在半个项目里全局搜索。

7. 面向对象实践中的常见陷阱与救火清单

7.1 可变默认参数:所有人都会踩的洞

在类方法的__init__里写def __init__(self, items=[]),是经典的Python陷阱。默认参数只会在函数定义时被创建一次,所有实例共享同一个列表对象,一个实例加东西,其他实例全都看得到。

class ShoppingCart: def __init__(self, items=[]): # 危险 self.items = items cart1 = ShoppingCart() cart1.items.append("苹果") cart2 = ShoppingCart() print(cart2.items) # 输出 ['苹果']

正确的办法是用None做默认值,或者在dataclass里用field(default_factory=list):

class ShoppingCart: def __init__(self, items=None): self.items = items if items is not None else []

这个坑的底层原因是Python函数的默认值在定义时求值,而不是调用时。理解了这一点,以后凡是默认值是可变对象(list、dict、set),你都会本能地想:它是不是共享的?

7.2 继承层级过深:维护成本递增的元凶

我见过一个项目,一个"客户订单"类有六级继承链,最底层的子类想改一个行为,得翻上面五个类的代码才能确定影响范围。这是典型的"为了抽象而抽象"。继承层级每多一层,"这个类的真实行为到底是什么"就越难回答,因为它在六个类各拿了一点。

我给自己定的规则是:继承深度尽量不超过3层。超过3层,先反思是否可以用组合替代。比如"VIP客户订单"到底是"客户订单"的一种,还是"订单"加了一个"VIP客户"的关联?很多时候后者更符合真实世界。组合的方式是让类持有另一个类的实例,需要委托时手动转发方法,代码看着多一点,但每个类的语义都清楚不少。

7.3 滥用getter/setter:把Python写成了Java

我曾经在代码评审里看到一位同事把类的每个属性都配了@property加上setter,二十个属性四十个方法,没有一个是纯逻辑的。这种防君子不防小人的"假装封装"增加了大量样板代码,还降低阅读速度。Python社区默认的调性是"直来直去"——需要时再加控制。先写普通属性,等校验逻辑真来了,再上property,调用方不需要改一行。

7.4 循环引用:类与类相互import的设计信号

两个类互相依赖,AimportB,BimportA,运行时报ImportError。这个技术问题背后往往藏着设计问题——两个类的职责边界没划清楚。我遇到这种情况,第一步不是用TYPE_CHECKING或者延迟import去糊弄,而是思考:能不能把相互依赖的那部分逻辑抽到第三个类/模块里?循环import是"耦合过重"的敏感指标,疏通它的正确动作是解耦,而不是打补丁。

7.5 类属性还是实例属性:先问自己"它跟实例有关吗"

最后再强调一遍最常被忽略的问题。写类的时候,先问自己:这个属性是所有实例共用的,还是每个实例各自不同?题目、价格、库存显然跟实例相关,放__init__里;一个全局的折扣率、一个统计实例数量的计数器,才适合做类属性。放错位置的结果通常不会立刻报错,但会在某个深夜调试时给你惊喜。

我个人在实际项目里踩过的坑,几乎都集中在"对象之间共享状态"和"继承层级蔓延"这两类。处理手段其实都是同一个方法论:明确每个类的职责边界,把共享的东西显式地放在共享的位置,把可变的东西限制在最小的作用域里。面向对象编程写久了,你对代码的关心点从"怎么让函数跑通"逐渐变成了"怎么让改动落地时不波及其他模块",这种设计意识的进步,比多背几个语法点有用得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询