class A {
public:
void Hello(const std::string& name) {
std::cout << "hello " << name;
}
};
int main() {
A* pa = NULL;
pa->Hello("world"); // 輸出 "hello world",不崩潰!
return 0;
}
為什麼?C++ 對象模型決定:非虛函數不佔用對象內存,編譯器將其轉為全局函數 void A_Hello(A* const this, ...),this 為隱式第一參數。因函數體未用到 this,空指針也能跑通。
"Smalltalk is not only NOT its syntax or the class library, it is not even about classes. The big idea is 'messaging'."
消息模型強調:對象間通過發送消息交互,關注接口與協議而非內部狀態。牛.吃(草) 本質是向「牛」發送「吃」消息,附帶「草」參數。
| 語言 | 空接收者行為 | 私有成員跨對象訪問 | 動態消息 |
|---|---|---|---|
| C++ | 未定義行為(本例巧合通過) | 允許(同類對象互訪 private) | 不支持 |
| Java/C# | 拋出 NullReferenceException | 允許(基於類的封裝) | 不支持(靜態綁定) |
| Smalltalk/Ruby | 發送消息給 nil,觸發 doesNotUnderstand/method_missing | 禁止(真正封裝) | 支持(Method Missing) |
// C# 反面教材
public class Account {
private int _amount;
public void Transfer(Account acc, int delta) {
acc._amount += delta; // 直接訪問另一對象私有字段!
this._amount -= delta;
}
}
這不違反「類封裝」(編譯期靜態邊界),但違反「對象封裝」(運行期動態邊界)。消息模型要求:只能通過消息改變對象狀態,內部結構對外完全不可知。
function Set() { var _elements = arguments; // 真正私有,外部不可見 this.In = function(e) { for (var i = 0; i < _elements.length; ++i) if (_elements[i] == e) return true; return false; }; } Set.prototype.Join = function(s2) { var s1 = this; var s = new Set(); s.In = function(e) { return s1.In(e) && s2.In(e); }; // 僅通過接口交互 return s; };這裡
s2的內部結構完全隱藏,Join僅通過In消息協作——這才是消息模型的精髓。Method Missing:動態語言的殺手鐧
當發送未定義消息時:
method_missing/__getattr__/TryInvokeMemberbuilder = Builder::XmlMarkup.new
xml = builder.books { |b|
b.book(:isbn => "14134") {
b.title "Revelation Space"
b.author "Alastair Reynolds"
}
}
任意標籤名(books, book, title)皆為動態消息,由 method_missing 攔截構建 XML 樹——這在靜態語言中幾乎不可能優雅實現。
理解這三個層次,才能透過語法糖看到 OOP 的靈魂:對象是獨立的計算單元,通過消息協作構建系統。下次設計 API 時,請問自己:我在定義類,還是在設計消息協議?