我自己的编程学习记录走到第六天,标题就四个字:面向对象。说实话,前面几天我都是用“学语法”的心态推动的,变量、分支、循环、函数,每一节看完就能照着例子敲出结果,正反馈特别强。到了面向对象这节,我头一次感到一种不是看不懂,而是不知道怎么落笔的沮丧——类我认识,对象我也认识,可真让我自己设计一个类的时候,好像手里有锤子却找不到钉子。
这篇笔记写给自己复盘,也写给正在Day6前后挣扎的人。它不打算按教科书的顺序从“什么是类、什么是对象”开始念定义,而是把我踩了几天坑之后的理解拆开:为什么需要面向对象、设计时看哪里、Java/Python/C++三种语言怎么表现、写完怎么自检。如果你也在学编程的半路上被这个概念卡住,这篇内容应该能帮你把“面向对象”从一句口号变成手上能用的工具。
1. 为什么Day6成了很多人编程自学的分水岭
1.1 前五天靠记忆就能撑住,Day6开始靠“建模直觉”
我前面五天的学习路径大概是这样:先认识变量,再学分支循环,然后写函数,最后处理文件读写。这些内容有一个共同特征——它们都是“一条路走到黑”式的线性思维。哪怕有函数把逻辑拆开,核心思路仍然是从输入开始,一步步执行到输出,中间遇到条件就判断,遇到重复就循环。这种代码写起来很有安全感,因为它每一步都看得见、摸得着。
面向对象偏偏把这条“路”给拆了。你要先定义一群东西,再让它们之间互相发消息、互相协作,最后整个程序跑起来。这意味着学习重点从“记住语法”切换成“从现实需求里抽象出类和类之间的关系”。语法顶多花半天就能过一遍,可“这个类该有几个方法”“这些方法应该放在哪个类里”“两个类之间是继承还是组合”这类问题,没有任何一本语法手册能给你标准答案。
我的亲身感受是,这个过程被称为分水岭毫不夸张。前五天学得越快的人,反而可能在这里越难受,因为前五天的练习让人习惯了“拿到需求直接写流程”的舒适区。到了Day6,要求突然变成“先停下来思考结构”,大脑会本能地抗拒这种延迟满足。
1.2 用“改需求”当标尺,看看自己到底在用什么思维写代码
我后来找到一个很有效的判断方法:别再问自己懂不懂类和方法,去看当你面对一个“新需求”时,第一反应是什么。
如果你发现一条新规则要加进来,你的第一反应是“在原来的主流程里再插一个if判断”,那不管你是不是用了class关键字,本质上还是过程式思维。如果你的第一反应是“我是不是该新写一个类,或者给某个类加一个方法,让主流程尽量不用动”,那恭喜,这才是面向对象思维的起点。
举一个特别常见的例子。购物车结算功能,原始需求只有两种优惠:满100减10,以及会员额外95折。过程式写法非常直接:
python复制def settle(cart, user):
total = sum(item.price * item.qty for item in cart)
if total >= 100:
total -= 10
if user.is_member:
total *= 0.95
return total
这段代码放在Day5完全没问题,短小、清晰、能跑。问题出在第二个星期。产品经理跑来说,我们要加“第二件半价”,还要加“生日月双倍积分”,还要加“新人首单立减20”。你再看这个函数,就会发现它正在变成一个不断堆积if的泥潭。
我并不是说用if就是错的。我是说,当你发现每次需求变更都要打开同一个函数、往里再塞一段逻辑时,就该意识到流程式思维已经撑不住了。面向对象真正解决的,不是让代码“看起来更高级”,而是让“变化的部分”和“不变的部分”分离,让每次加需求都能像插U盘一样,插一个新的类进去就结束。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 类与对象:不看抽象定义,先看它们承担的三种角色
2.1 类同时是“类型”“工厂”和“责任边界”
教科书最爱说“类就是图纸,对象就是按照图纸造出来的房子”。这个比喻不能算错,但它把类简化成了一个静态模板,导致很多人误以为面向对象的价值全在“复用图纸”。实际上,一个类在代码里至少承担三种角色。
第一种角色是“类型”。当你写下一个变量叫user,并且说它的类型是User,你其实是在告诉编译器、也告诉未来读代码的人:这个变量身上应该有User所有的方法和状态,不应该拿一个字符串或者字典去冒充它。类型是一种约束,约束能帮我们提前发现很多低级错误。
第二种角色是“工厂”。类可以通过构造方法生成对象,比如Python里调用User(name="张三"),就是在让User这个工厂生产一个具体的用户实例。每个实例自己保存自己的数据,张三的年龄改一改,不会影响李四。
第三种角色是“责任边界”。这是最容易被忽略但最重要的。一个类应该把自己的状态和行为收拢在一起,内部数据只允许通过自己定义的方法来修改。谁负责计算订单总额,谁负责判断会员等级,谁负责发送通知,这些职责一旦被划定清楚,代码里就不容易出现“一个函数里操作了五种对象内部字段”的混乱场面。
我理解到第三层的时候才明白,为什么很多老程序员说“类的本质是边界”。写代码的过程里,大部分冲突和bug不是语法导致的,而是边界没划好——A类直接改了B类内部的数据,B类出了问题,还要翻遍整个项目才能找到是谁动的手。
2.2 对象最核心的三件套:状态、行为、身份
对象不是“带方法的字典”。字典只有键值对,对象还有自己的行为规则。我总结了一个很朴素的三件套框架,每次设计新类都会拿出来对照。
第一是状态。也就是对象内部保存的数据。对一个订单来说,状态可能是商品列表、总金额、创建时间;对一个用户来说,状态可能是昵称、会员等级、积分。状态会变化,所以类需要提供方法来修改它。
第二是行为。也就是这个对象能做什么。订单能计算总额、能生成支付链接、能取消;用户能下单、能评价、能申请退货。行为会调用自己的状态,也可能调用其他对象的行为,来实现一个完整的业务动作。
第三是身份。即使两个对象内部的每个字段值都完全一样,它们在内存里仍然是两个不同的东西。Python里可以用id()看对象的身份,Java里可以用==比较引用是否相同。这一点初学很容易忽略,调试的时候遇到“明明两个数据一样,程序却报错”的情况,多半就是身份问题。
我记得自己第一次写用户注册模块时,就把用户类写成了一个只装着几个字段的“高级字典”。后来要给用户加一个“修改密码”的行为,才发现密码散列应该由User自己完成,而不是由外部函数传一个散列值进来硬塞给字段。顺着“对象负责维护自己的状态”这条原则想,很多设计上的疑问会自己解开。
2.3 从需求里找类和对象的最朴素方法:圈名词和动词
我知道初学者最头疼的问题就是:“我拿到一段需求,到底怎么知道哪些东西该写成类?”
这里有一个不保证完美、但能让你立刻动起来的笨办法。找一段需求描述,把里面的名词全部圈出来,再找动词圈出来。名词大概率是候选对象,动词大概率是候选方法。然后追问一句:这个动词到底是哪个名词的动作?它的执行需不需要修改某个名词的内部状态?
举个例子,“用户添加商品到购物车,结算时系统计算折扣并生成订单,用户支付后订单状态变为已支付”。名词有“用户、商品、购物车、系统、折扣、订单、状态”,动词有“添加、结算、计算、生成、支付、变为”。于是很自然的,用户是一个类,购物车是一个类,商品是一个类,订单是一个类,折扣这一个词也可以先落成一个接口,后面再考虑怎么实现。
这个方法不复杂,但它会把你的注意力从“写流程”拉回“划分类的边界”。名词之间的关系,决定了类之间是继承、组合还是聚合。动词的归属,决定了方法应该写进哪个类。做多了之后你会发现,面向对象其实就是“用名词组织代码,用动词定义协作”。
3. 封装、继承、多态真正保护的,是代码里那三个“变化点”
3.1 封装不是“禁止访问”,而是“保证状态一致”
很多人理解封装,就记住一句话:把字段设为private,然后提供getter和setter。这种理解太表面了。要是辛辛苦苦封装半天,最后setter还是无条件赋值,那封装跟没封装几乎没有区别。
封装的价值在于,对象能拦截外部对它内部状态的每一次修改,确保修改符合业务规则。我们拿账户余额举例。如果balance是public字段,外部可以直接写account.balance -= 1000,哪怕余额只有600,也能扣成负数,这在真实系统里就是资金事故。但如果把修改余额的路全部堵上,只提供一个withdraw方法,那这个方法里就可以检查余额是否充足:
python复制class Account:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("存款金额必须大于0")
self._balance += amount
def withdraw(self, amount):
if amount <= 0:
raise ValueError("取款金额必须大于0")
if amount > self._balance:
raise ValueError("余额不足")
self._balance -= amount
@property
def balance(self):
return self._balance
外部想改余额,只能通过deposit和withdraw两个入口,每个入口都做了一次校验。这样一来,余额永远不会出现负数,不是因为我们靠自觉,而是因为设计上就没有给非法操作留出通道。
我在实际写代码时越来越觉得,封装表面上是“属性私有化”,实质上是“把修改状态的权利收回到对象自己手里”。这不是为了防黑客,是为了防队友,也防三个月后的自己手滑。
3.2 继承最常见的误区:为了省几行代码就乱认父子关系
继承是面向对象里最容易被误用的特性。因为它的表面效果太诱人了:父类写一遍方法,子类就能直接拿来用,省代码的感觉特别爽。但省代码不等于设计合理。
判断继承是否合理,有一条经典原则叫“is-a关系”:一个子类必须真的是一种父类。狗是一种动物,所以Dog继承Animal没毛病;正方形是一种矩形,但让Square继承Rectangle却很危险,因为矩形可以分别修改长和宽,正方形不允许边长不相等,一旦父类方法被调用,子类的一致性就被破坏了。
我当初学继承的时候做过一个反面教材。项目里先有一个Bird类,鸟有fly方法。后来加了一个企鹅类,企鹅明显也是一种鸟,于是顺手就让它继承了Bird。结果企鹅也能调用fly方法了,这当然不对。问题根源不是我写错了代码,而是我在父类里放了一个不该所有子类都有的能力。
后来我才知道,比“把共性上提”更重要的是“把差异留下”。当你不确定一个方法是不是所有子类都需要的时候,宁可先不放进父类,或者把父类定义成一个抽象类,只给出“应该有什么能力”的约定,不给出具体实现。真实项目里的继承树最好又矮又窄,三层之内的继承通常还可以接受,超过五层就很容易变成牵一发而动全身的积木塔。
另外,组合优先于继承,这句话不是让你完全不用继承,而是告诉你:当你不确定该不该用继承时,先想想能不能把一个对象放进另一个对象内部,通过“有一个”而不是“是一个”来复用能力。购物车有商品,这是组合;经理是一种员工,这才考虑继承。
3.3 多态的本质:让调用方只依赖“稳定的接口”,不依赖“具体的实现”
多态这个词听起来很玄,但它背后的问题非常现实:代码里经常出现同一类动作、不同执行方式的情况。怎么让调用方写出“一套逻辑,适配各种形态”?
我拿一个宠物声音的小例子来说明。假如我们要写一个方法,让传入的动物发出叫声。没有多态概念时,代码会长成这个样子:
python复制def play(animal):
if isinstance(animal, Dog):
animal.bark()
elif isinstance(animal, Cat):
animal.meow()
elif isinstance(animal, Duck):
animal.quack()
每来一种新动物,play函数就要加一个分支,这就是典型的“面向修改开放”的代码。使用多态的写法是这样的:所有动物都实现同一个sound方法,play函数完全不用关心它收到的是什么动物。
python复制class Dog:
def sound(self):
return "汪汪"
class Cat:
def sound(self):
return "喵喵"
class Duck:
def sound(self):
return "嘎嘎"
def play(animal):
print(animal.sound())
新增一只羊,只需要加一个Sheep类,实现sound方法即可,play函数一行都不用改。这里的“约定”就是动物都有一个sound()方法,In Java里这个约定会被写成一个接口或者抽象类,在Python里约定靠程序员自己保证。
多态带来的核心价值是“可替换性”。调用方依赖的是一个抽象能力(会叫),而不是某个具体类(狗叫)。这样,新类型出现时,不用修改老代码,只需新增代码,老代码自然能通过接口适配新类型。这个特性非常有用。在软件开发里有个著名的开闭原则——“对扩展开放,对修改关闭”,多态正是实现这句话的基础操作。
4. 同一个需求用Java、Python、C++写一遍之后,我对OOP的理解才真正落地
4.1 三道“动物声音”代码,风格差异一目了然
如果只学一门语言,很难分清“哪些是OOP的通用思想,哪些是语言的特定表达”。我自己把“动物叫”的需求分别用三门上常见的语言写了一遍,对比之后,对面向对象的理解一下子立体了起来。
Java是非常典型的强类型面向对象语言,接口是语言里的头等公民。同样的需求写出来大概是这个样子:
java复制abstract class Animal {
public abstract String sound();
}
class Dog extends Animal {
@Override
public String sound() {
return "汪汪";
}
}
public class Main {
public static void play(Animal a) {
System.out.println(a.sound());
}
}
注意Java里play方法参数类型是Animal,编译器要求传入的对象要么是Animal的子类,要么是Animal接口的实现类。类型错了根本过不了编译。这对新手其实很友好,因为错误在写代码的那一刻就被提示了。
Python的写法是动态类型,代码可以长成下面这样,完全不需要Animal作为父类:
python复制class Dog:
def sound(self):
return "汪汪"
class Cat:
def sound(self):
return "喵喵"
def play(animal):
print(animal.sound())
play函数里没有出现任何Animal类型,它只要求传入对象能响应sound()方法。这在Python社区叫“鸭子类型”:走起来像鸭子、叫起来像鸭子,那就可以把它当鸭子用。这种灵活省去了很多类型声明的代码,代价是,如果你把没有sound方法的对象传进来,程序运行到那一行才报错。
C++的写法会把“运行时多态需要哪些底层配套”也暴露出来:
cpp复制#include <iostream>
#include <string>
class Animal {
public:
virtual std::string sound() const = 0;
};
class Dog : public Animal {
public:
std::string sound() const override {
return "汪";
}
};
void play(const Animal& a) {
std::cout << a.sound() << std::endl;
}
C++里为了实现多态,需要声明virtual、需要用到引用或指针、需要保证对象生命周期不会提前销毁。语法细节比前两者多出一大截,但一旦把底层机制想清楚,你对“虚函数表、动态绑定”这些概念就不再是死记硬背了。
4.2 静态类型和动态类型,会深刻影响你对“接口”的理解
用这三门语言各写一遍之后,我最大的收获是理解了静态类型和动态类型的分野。
Java和C++属于静态类型语言,编译时期就知道每个变量是什么类型,所以多态主要靠“继承关系”来体现。你要定义一个接口或抽象类,子类通过extends或implements来和它建立关联,编译器会约束你:只有满足关系的类型才能被当作参数传进去。
Python这类动态类型语言则走另一个极端,它不强求变量预先声明类型,任何对象只要具备了需要的方法,就能参与协奏。所以Python里的多态很常见,但依赖的是“约定”而不是“类型检查”。自己写项目可以这么潇洒,但在大型团队协作中,如果没有文档和类型标注,动态类型有时候会让人改代码改到怀疑人生。
我现在不会再争“哪种语言更好”。Java适合需要强约束的大型项目,Python适合快速迭代和数据分析,C++适合压榨性能的系统软件。可它们的共同点都是从抽象接口出发,让调用方不绑定具体实现。这一条不会因为语言不同而改变。
4.3 三语言OOP风格速查对比
| 维度 | Java | Python | C++ |
|---|---|---|---|
| 类型检查 | 编译期强类型 | 运行期动态类型 | 编译期强类型 |
| 接口表达 | interface关键字独特 | 靠鸭子类型/ABC模块 | 纯虚类/抽象类 |
| 继承方式 | 单继承+接口多实现 | 支持多继承 | 支持多继承 |
| 封装体现 | private/protected/public | 约定_下划线前缀 | private/protected/public |
| 新手门槛 | 中,编译器提示较多 | 低,能快速跑起来 | 高,要理解内存和虚函数机制 |
| 常见用途 | 企业级后端 | Web自动化、数据分析、脚本 | 游戏引擎、底层系统、高频交易 |
| 容易出现的坑 | 类设计过重、样板代码多 | 过度依赖鸭子类型导致类型错乱 | 多继承复杂、生命周期管理难 |
这张表不是让你现在就记住,而是想说明一件事:面向对象的核心思想是稳定且跨语言的,语法只是不同语言给同一思想穿的外衣。把某种语言的写法当成“标准答案”,往往会限制你的视野。
5. 实战复盘:用“订单结算”把OOP从语法变成设计习惯
5.1 第一版:先用最朴素的代码把需求跑通
我在Day6时已经从大量例子里明白一个道理:不要一开始就追求完美的对象设计。先写一版能跑的代码,通常能帮你更清楚哪里难受、哪里该改。下面以订单结算为例,我先把最朴素的需求列出来:
- 购物车里有若干商品,每个商品有单价和数量。
- 用户可以是非会员、普通会员和超级会员,不同会员折扣不同。
- 订单总额达到某个阈值时,可以享受额外的满减优惠。
第一版过程式代码通常会长成这样:
python复制def calculate_order(cart_items, user):
raw_total = 0.0
for item in cart_items:
raw_total += item["price"] * item["quantity"]
if user["member_type"] == "super":
raw_total *= 0.8
elif user["member_type"] == "normal":
raw_total *= 0.9
if raw_total >= 200:
raw_total -= 20
elif raw_total >= 100:
raw_total -= 10
return round(raw_total, 2)
先别嫌弃这段代码。它能跑,逻辑也没有大错。但随着优惠类型变多,它的问题会越来越扎眼:会员折扣和满减规则耦合在一个函数里,新增一种会员类型、或者新增一个优惠券,都得改这个函数;测试的时候,为了测“超级会员满300减50”,得把前置条件全跑一遍,代码越改越难维护。
5.2 从“哪里在变”出发,把代码拆成多个类
如果说第一版代码已经暴露了痛点,下一步就是面向对象所谓的“重构”。我的切入口不是“我要用几个类”,而是“哪些需求以后会变”。在这个结算场景里,最容易变化的是“优惠策略”:今天满减,明天第二件半价,后天又来个新客立减。所以我优先把优惠策略抽象出来。
先做一个策略接口,然后每一种优惠规则成为一个独立的类:
python复制from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def apply(self, order_total: float) -> float:
pass
class NoDiscount(DiscountStrategy):
def apply(self, order_total: float) -> float:
return order_total
class MemberDiscount(DiscountStrategy):
def __init__(self, discount_rate: float):
self.discount_rate = discount_rate
def apply(self, order_total: float) -> float:
return order_total * self.discount_rate
class FullReduction(DiscountStrategy):
def __init__(self, threshold: float, reduction: float):
self.threshold = threshold
self.reduction = reduction
def apply(self, order_total: float) -> float:
if order_total >= self.threshold:
return order_total - self.reduction
return order_total
这样,用户被建模为一个拥有优惠策略的对象,订单负责汇总商品金额,再把自己交给优惠策略计算最终价格。下面是重构后的部分:
python复制class User:
def __init__(self, name: str, strategy: DiscountStrategy):
self.name = name
self.strategy = strategy
class Order:
def __init__(self, items: list):
self.items = items
def raw_total(self) -> float:
return sum(item["price"] * item["quantity"] for item in self.items)
def final_total(self, user: User) -> float:
raw = self.raw_total()
return round(user.strategy.apply(raw), 2)
# 使用方式
ordinary_user = User("张三", NoDiscount())
member_user = User("李四", MemberDiscount(0.85))
order = Order([{"price": 80, "quantity": 2}])
print(order.final_total(member_user))
设计上的变化很明显:新增一种优惠,我只需要新增一个继承DiscountStrategy的类,Order和User都不需要动。如果优惠之间要叠加,我也可以写一个组合策略类,内部把多个策略依次执行。新增需求对旧代码的影响被压到了最小。
5.3 这个重构案例教会我的三件事
第一,类应该围绕“稳定的概念”和“易变的概念”来划分。订单、用户是稳定概念,它们会长期存在;折扣规则是易变概念,所以它需要被隔离出来,给它一个稳定的接口边界。
第二,策略模式这类“设计模式”,其实不是花架子。很多人在网上看设计模式,总觉得是炫技。但自己写过一遍从if堆到策略重构的代码后,你会明白模式的本质就是回答“多个类如何协作才能不互相牵制”。
第三,不用为了面向对象而把一个简单需求拆得无比复杂。如果需求永远只是满100减10这种一条规则,第一版代码完全够用。可现实是,业务规则会一直增加。我在不同的学习阶段反复体会到:设计是“未来需求的提前量”,设计过度和设计不足都是灾难,很难一学就会,只能不断权衡。
从这次实战往后,我做小项目养成了一个习惯:先记录“需求里哪些词可能变化”,再动笔设计类。而不是反过来,一上来就画一堆我以为很美的类图,结果和需求脱节,写两天就推倒重来。
6. 初学阶段最容易混淆的两类坑:写出来的假对象,和搜出来的假同义词
6.1 五个坏味道,让我认出“披着面向对象外衣的过程式代码”
第一个坏味道:类里面全是一堆不读写自身状态的静态方法。这种类实质上是“命名空间”,只是恰好用了class关键字,里面的方法互相之间没有任何状态关系,也没有对象之间协作。代码该难维护还是难维护,因为核心逻辑依然靠外部传参飞来飞去。
第二个坏味道:类里只有字段,行为全靠外部函数操作。比如一个User类只有name和age属性,所有修改操作都在外部代码里完成。这等于把封装丢掉了,对象成了被动挨打的“数据袋”。一旦某处忘记校验,非法数据就会溜进系统。
第三个坏味道:为了省代码强用继承。Dog继承Bird,Manager继承Product,这种违和感强烈的关系一旦出现,多半是设计有问题。判断方式很简单:把子类替换成父类放到业务场景里,如果逻辑上说不通,那继承就是生搬硬套。
第四个坏味道:方法参数列表长得吓人。如果一个方法需要六个参数才能工作,往往说明这些参数应该被封装成一个对象,或者在提示你方法放错了类。参数列表越长,调用方越容易传错顺序,测试越难写。
第五个坏味道:双向依赖满天飞。Order类里直接操作User类的内部字段,User类又反向修改Order的状态。这种写法会让类与类之间强耦合,改一个类不得不连带翻出另一个类。
以上每条都是我在练习项目里真实摔过的跤。踩坑之后我发现,写出假面向对象代码的人,并不是不懂语法,而是不明白“类不是用来装代码的容器,是用来管理状态、行为和边界的契约”。
6.2 三分钟自测:用三个问题检查自己的代码
问自己下面三个问题,基本能判断你有没有把一个类设计明白。
第一个问题:如果要增加一个同一“家族”的新类型,比如新动物、新折扣、新支付渠道,你需要改动哪些文件?如果你要改调用方的if-else,说明抽象没做干净;如果只是新增一个类,再把实例组合进来,说明设计基本合格。
第二个问题:某个类的内部状态,有没有可能被外部代码绕过方法直接修改?如果答案是可能,说明封装还停留在“字段设置成private”的表面功夫上,没有把状态修改权收回来。
第三个问题:父类和子类能否互相替换而不闹出业务笑话?如果子类继承了一个它根本不该有的能力,比如企鹅会飞、正方形被改成长方形,那就说明继承放错了东西。
这三个问题不是书本上的标准答案,是我自己写废了几个小项目之后总结出来的“体检清单”。每次新写一个类,我都会拿它照一遍。刚开始几乎每照必中招,后来慢慢就习惯了在设计时多想三五分钟。
6.3 被热搜词引出来的“面向对象分类ENVI”,其实是另一套体系
我在整理学习笔记时顺手看了下搜索联想,发现“面向对象”这个词下面居然挂着“面向对象分类envi”这样的组合。一开始还以为是有人把OOP和某款编程IDE的关键词混在一起搜了,后来一查,发现自己差点踩进一个完全不同的知识领域。
在遥感影像处理里,“面向对象分类”是一种经典的分析方法,常出现在ENVI、eCognition这类软件的用户交流中。那里的“对象”不是代码里的对象,而是将影像分割出来的一个个同质连片区域,也叫影像对象。算法会先根据光谱、纹理、形状等特征把影像切成若干块,然后把这些“块”当成分类的基本单元,再判断它属于农田、建筑、水体还是林地。
这和编程里的面向对象本质上没什么关系,只是中文都叫“对象”而已。编程的OOP处理的是类、实例、方法、继承这些代码组织的问题;遥感里的面向对象分类处理的是图像分割、特征提取、分类器训练这类空间信息分析的问题。如果你是因为学编程搜到了这些内容,千万别以为是同一个东西,也别顺着这个岔路口越走越远。
这件事情倒给了我一个额外提醒:同一个术语在不同领域里经常有截然不同的含义。搜索资料之前,最好先把“面向对象”后面加上限定词,比如“面向对象编程”“面向对象设计”“面向对象影像分类”。否则很容易把半天的学习时间浪费在不相关的内容上。
学到第六天,我最想留给自己的一句话是:写一个类,本质上是在写一份对外界的承诺。方法签名是承诺的边界,状态一致性是承诺的底线,接口抽象是承诺的稳定性。以后每次写完一个类,我会先不看内部实现,而是问一句“调用我的人需不需要知道我内部干了什么”。如果答案是需要,多半就是抽象不够,得继续重构——这可能就是面向对象教给我的最实在的道理。
