为什么要学面向对象:从写脚本到做项目
之前学 Python 都是写脚本:一行一行顺序执行,用函数把重复代码包起来。这种方式写 100 行以内的小程序很舒服,但项目一旦变大——比如要做一个游戏,里面有蛇、有食物、有障碍物——全靠函数和全局变量会乱成一锅粥。
面向对象(OOP)提供了一种新的组织方式:把数据和操作数据的函数打包成一个"类"。蛇的位置、长度、移动方法,全部封装在 Snake 类里;食物的位置、生成方法,封装在 Food 类里。代码立刻就清晰了。
面向对象不是为了炫技,而是当项目变大时,帮你把代码整理得不会失控。
- 类一张设计图,描述一类东西长什么样、能做什么。
- 对象按设计图造出来的具体东西。
- 属性对象的数据,比如蛇的长度。
- 方法对象能做的事,比如蛇移动一步。
类与对象:饼干模具和饼干的比喻
想象你有一个星形饼干模具。模具本身不是饼干,它只是一个形状设计图。但你用它按一下面团,就能做出一块星形饼干。你可以按十次,做出十块饼干,每块饼干是独立的。
在 Python 里:类就是模具,对象就是按出来的饼干。
class Snake:
pass
# 用类创建两个对象
s1 = Snake()
s2 = Snake()
这里 Snake 是类,s1 和 s2 是两个独立的对象。它们都是 Snake 类型,但各自独立。
属性与方法:给类加数据和行为
空类没用。我们给 Snake 加属性(数据)和方法(行为):
class Snake:
def __init__(self):
self.length = 3 # 属性:长度
self.direction = "右" # 属性:方向
def move(self): # 方法:移动
print("蛇向", self.direction, "移动一步")
def grow(self): # 方法:长大
self.length += 1
print("蛇现在长度", self.length)
这里 `self` 代表"对象自己"。每个对象有自己的 length 和 direction,互不干扰。
s = Snake()
s.move() # 输出:蛇向 右 移动一步
s.grow() # 输出:蛇现在长度 4
s.grow() # 输出:蛇现在长度 5
属性 `self.length` 和方法 `self.move()` 都是对象的一部分。你可以把它理解成:每一条蛇都自带一份长度记录和一套移动动作。
构造函数 __init__:新建对象时做什么
`__init__` 是 Python 类里最特殊的方法,叫构造函数。你每次用 `Snake()` 创建对象时,它会自动运行一次,用来初始化对象的属性。
我们让构造函数接受参数,这样创建对象时可以指定初始长度:
class Snake:
def __init__(self, start_length=3, direction="右"):
self.length = start_length
self.direction = direction
s1 = Snake() # 默认长度 3
s2 = Snake(start_length=10) # 长度 10
print(s1.length, s2.length) # 输出:3 10
这样一个类就能灵活地造出不同初始状态的对象。这就是面向对象的好处:写一次类,能造出无数个个性化对象。
小项目一:记账本类,记录每一笔开销
我们做一个实用小工具:记账本。它能记每一笔花销、列出所有记录、算总支出。
class Ledger:
def __init__(self, owner):
self.owner = owner
self.records = [] # 空列表存记录
def add(self, amount, note):
self.records.append({"金额": amount, "备注": note})
def total(self):
return sum(r["金额"] for r in self.records)
def show(self):
print(f"--- {self.owner} 的账本 ---")
for r in self.records:
print(r["金额"], r["备注"])
print("合计:", self.total())
mine = Ledger("小明")
mine.add(25, "奶茶")
mine.add(80, "午餐")
mine.add(12, "公交")
mine.show()
这里 Ledger 类把"记录列表""加一笔""算总和""展示"全部打包在一起。你不需要关心内部 records 怎么存,只要调用 add、show 就行。这种把实现细节藏起来、只暴露必要接口的做法叫封装,是 OOP 的第一大特性。
- __init__构造函数,初始化属性。
- self对象自己的引用,访问属性和方法。
- 列表存记录用列表 append 每条开销。
- 封装数据和操作打包在类里。
小项目二:贪吃蛇游戏的 Snake 类
把上面学的用到一个更有意思的项目:贪吃蛇。下面是简化版核心代码(用列表存蛇身坐标,按方向移动,吃到食物就长一节)。
import random
class SnakeGame:
def __init__(self, width=20, height=20):
self.width = width
self.height = height
self.snake = [(10, 10)] # 蛇身坐标列表
self.direction = (1, 0) # 向右
self.food = self.spawn_food()
self.score = 0
def spawn_food(self):
x = random.randint(0, self.width - 1)
y = random.randint(0, self.height - 1)
return (x, y)
def step(self):
head_x, head_y = self.snake[0]
dx, dy = self.direction
new_head = (head_x + dx, head_y + dy)
self.snake.insert(0, new_head)
if new_head == self.food:
self.score += 10
self.food = self.spawn_food()
else:
self.snake.pop()
def set_direction(self, dx, dy):
self.direction = (dx, dy)
game = SnakeGame()
for _ in range(5):
game.step()
print("得分:", game.score)
这段代码把整个游戏状态和操作都封装在 SnakeGame 类里。外部只要调用 step() 推进一步、set_direction() 改方向就行。以后加图形界面(用 pygame)只要在外面套一层显示逻辑,核心游戏规则完全不用改——这就是 OOP 带来的可维护性。
面向对象的核心价值:把相关代码打包
学到这里,孩子可能会问:我用函数也能写记账本和贪吃蛇,为什么非要用类?答案是:项目小的时候看不出区别,项目一大就看出来了。
如果不用类,蛇的位置、食物位置、得分都是全局变量,移动函数、碰撞函数、计分函数都直接操作这些全局变量。一旦要加第二个玩家(双人模式),你要把所有变量复制一份、所有函数改一遍,非常痛苦。
用类的话,只要 new 两个 SnakeGame 对象就行,它们各自独立、互不干扰。这就是面向对象的真正威力——让复杂系统可以被复制和扩展。
- 先理解概念:类、对象、属性、方法,用饼干模具比喻想明白。
- 再写小项目:记账本、学生管理、图书借阅,多写几个类。
- 最后做游戏:贪吃蛇、打飞机、俄罗斯方块,体会封装的好处。
面向对象不是为了应付考试,而是让你在写大项目时,代码不会变成一团乱麻。