Dart之面向对象概述

一、前言
什么是对象?什么又是面向对象?
这两个词很常见,但第一次认真解释时,反而容易卡住。好像知道它是什么,又很难说清楚它到底解决了什么问题。
可以先从做菜理解。
如果我们要做一道回锅肉,面向过程的想法是:第一步准备食材,第二步切肉,第三步热锅,第四步翻炒,最后出锅。它关注的是“步骤”。
面向对象的想法会换一个角度:这件事里有哪些对象?厨师、锅、肉、调料、火候都可以是对象。每个对象有自己的属性和行为,对象之间通过协作完成做菜这件事。
一句话理解:面向对象编程是一种用对象来抽象现实问题、组织代码和完成协作的编程思想。
二、面向过程编程
2.1 面向过程关注步骤
面向过程编程的核心是:把问题拆成一步一步的流程,然后按顺序执行。
比如做菜:
- 准备食材。
- 清洗食材。
- 切片。
- 热锅。
- 翻炒。
- 出锅。
写成代码时,它可能更像这样:
void prepareFood() {
print('准备食材');
}
void cutFood() {
print('切菜');
}
void cookFood() {
print('翻炒');
}
void main() {
prepareFood();
cutFood();
cookFood();
}
这种方式直接、清晰,很适合流程简单的问题。
2.2 面向过程的特点
面向过程通常有几个特点:
| 特点 | 含义 | 做菜类比 |
|---|---|---|
| 线性流程 | 按顺序执行步骤 | 先切菜,再下锅 |
| 集中管理 | 主流程负责调度所有函数 | 厨师一个人安排所有步骤 |
| 数据共享 | 多个函数可能操作同一份数据 | 肉、锅、调料被多个步骤反复使用 |
它的优势是容易上手,但当项目变大时,问题也会出现:
- 步骤越来越多,主流程越来越长。
- 数据被多个函数修改,状态不容易追踪。
- 新增功能时,可能要改动很多旧流程。
- 代码复用和扩展会变得困难。
所以面向过程不是不好,而是它更适合规模较小、流程明确的问题。
三、面向对象编程
3.1 对象是什么
面向对象里常说一句话:万物皆对象。
这句话不是说世界真的都变成代码对象,而是提醒我们:可以把现实世界中的事物、事件、规则和概念抽象成对象。
比如做菜这件事里:
- 厨师可以是对象。
- 锅可以是对象。
- 食材可以是对象。
- 菜谱可以是对象。
- 一次烹饪过程也可以是对象。
对象通常包含两类内容:
- 属性:对象有什么。
- 行为:对象能做什么。
比如一个 Chef 对象:
class Chef {
String name;
Chef(this.name);
void cut() {
print('$name 正在切菜');
}
void cook() {
print('$name 正在炒菜');
}
}
void main() {
final chef = Chef('小王');
chef.cut();
chef.cook();
}
这里的 name 是属性,cut() 和 cook() 是行为。
3.2 面向对象关注建模
面向对象编程不只是把函数塞进类里。
它真正关注的是:如何把问题中的事物抽象成对象,再让这些对象互相协作。
还是做菜:
- 厨师负责切菜和翻炒。
- 锅负责承载食材。
- 食材有生熟状态。
- 菜谱规定步骤。
这些对象各自负责一部分事情,整体通过协作完成目标。
这比一个主函数管理所有细节更接近现实世界,也更容易扩展。
3.3 对象之间通过消息协作
对象不是孤立存在的。
一个对象想让另一个对象做事,通常会调用它的方法。这个过程可以理解为消息传递。
class Pot {
void heat() {
print('锅开始加热');
}
}
class Chef {
void cook(Pot pot) {
pot.heat();
print('厨师开始炒菜');
}
}
void main() {
final chef = Chef();
final pot = Pot();
chef.cook(pot);
}
chef.cook(pot) 中,厨师对象使用锅对象完成烹饪流程。对象之间不是把所有数据摊开共享,而是通过方法调用来协作。
四、面向对象的核心特点
4.1 模块化
模块化就是让不同对象负责不同任务。
比如:
Chef负责烹饪行为。Pot负责锅相关行为。Ingredient负责食材状态。
这样每个类的职责更清晰,也更容易单独开发和测试。
4.2 封装
封装就是把对象内部的数据和实现细节藏起来,只暴露必要接口。
比如食材内部怎么变化,外部不一定需要知道。外部只需要问:熟了吗?
class Ingredient {
bool _cooked = false;
void cook() {
_cooked = true;
}
bool get isCooked => _cooked;
}
_cooked 是内部状态,外部通过 isCooked 获取结果。这样可以减少外部随意修改内部数据导致的问题。
4.3 继承
继承可以让新类型复用已有类型的属性和行为。
class Animal {
void eat() {
print('正在吃东西');
}
}
class Cat extends Animal {
void meow() {
print('喵');
}
}
Cat 继承了 Animal 的 eat(),同时又有自己的 meow()。
继承适合表达“是一种”的关系,比如猫是一种动物。
4.4 多态
多态表示同一个行为,在不同对象上可以有不同表现。
abstract class Animal {
void speak();
}
class Cat extends Animal {
@override
void speak() {
print('喵');
}
}
class Dog extends Animal {
@override
void speak() {
print('汪');
}
}
void makeSound(Animal animal) {
animal.speak();
}
void main() {
makeSound(Cat());
makeSound(Dog());
}
makeSound() 面向的是 Animal,但传入猫就喵,传入狗就汪。这就是多态带来的灵活性。
五、面向对象的优势
5.1 更容易维护
当对象职责清晰时,修改某一类逻辑通常只需要看对应类。
比如锅的加热规则变化了,就优先修改 Pot;食材状态变化了,就优先修改 Ingredient。
5.2 更容易扩展
新增功能时,可以增加新类或扩展已有类,而不是把所有逻辑堆进一个越来越长的流程。
比如要新增一道菜,不一定要重写整个厨房流程,而是可以新增菜谱对象、食材对象或烹饪策略。
5.3 更容易复用
对象和类可以在不同场景复用。
比如 Ingredient 不只可以用于回锅肉,也可以用于其他菜品。Chef 的切菜、炒菜行为也可以复用到多个流程中。
5.4 更适合复杂系统建模
当业务复杂时,我们需要先理解现实问题,再组织代码。
面向对象提供了一个比较自然的思考方式:
- 找出问题中的对象。
- 分析对象的属性。
- 分析对象的行为。
- 明确对象之间怎么协作。
这个过程就是从现实问题到代码结构的桥。
六、面向过程和面向对象怎么选
6.1 简单流程可以面向过程
如果问题很小、流程很明确,面向过程写法可能更简单。
比如一个临时脚本、一个小工具函数、一次简单数据转换,不一定非要设计很多类。
6.2 复杂业务更适合面向对象
如果问题里有多个实体、多个状态、多种行为和长期变化,面向对象会更有优势。
比如:
- 用户系统。
- 商品订单。
- IM 消息。
- 游戏角色。
- Flutter 页面状态。
这些场景中,对象之间的关系比单纯步骤更重要。
6.3 不要为了对象而对象
面向对象是一种建模方式,不是把所有代码都包进类里。
好的面向对象设计,应该让代码更清晰;如果类越写越绕,说明建模可能需要重新调整。
七、总结
这一篇我们系统了解了面向对象编程的基础概念。
面向过程关注步骤,适合流程简单的问题;面向对象关注对象、属性、行为和协作,更适合复杂系统建模。
对象可以理解为现实世界中事物、事件、规则和概念的代码抽象。对象拥有属性和行为,对象之间通过方法调用进行消息传递。
最后记住三句话:
- 面向过程问的是:第一步做什么,第二步做什么。
- 面向对象问的是:有哪些对象,它们各自负责什么。
- 面向对象的核心价值,是让复杂问题更容易建模、维护、扩展和复用。
浙公网安备 33010602011771号