程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 對象的消息模型

對象的消息模型

編輯:關於C語言

話題從下面這段C++程序說起,你認為它可以順利執行嗎?
 //C++
class A {
    public:
        void Hello(const std::string& name) {
           std::cout << "hello " << name;
         }
};
int main(int argc, char** argv)
{
    A* pa = NULL; //!!
    pa->Hello("world");
    return 0;
}
試試的確可以順利運行輸出hello world,奇怪嗎?其實並不奇怪,根據C++對象模型,類的非虛方法並不會存在於對象內存布局中,實際上編譯器是把Hello方法轉化成了類似這樣的全局函數:
 void A_Hello_xxx(A * const this, const std::string& name) {
    std::cout << “hello “ << name;
}
對象指針其實是作為第一個參數被隱式傳遞的,pa->Hello(“world”)實際上是調用的A_Hello_xxx(pa, “world”),而恰好A_Hello_xxx內部沒有使用pa,所以這段代碼得以順利運行。
對象的消息模型
如果是研究C++對象模型,上面的討論可以到此為止,不過這裡我想從另一個層面來繼續探討這個問題。OOP的先驅人物Alan Kay在總結Smalltalk的OO特征時強調:
Smalltalk is not only NOT its syntax or the class library, it is not even about classes. I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is “messaging”.
也就是說相比類和對象的概念來講,他認為對象交互的消息模型是OOP更為本質的特征,因為消息關注的是對象間的接口和交互,在構建大的系統的時候重要的不是對象/模塊的內部狀態,而是它們的交互。根據消息模型,牛.吃(草) 的語義是發送一條消息給“牛”,消息的類型是“吃”,消息的內容是“草”。如果按照嚴格的消息模型,那麼上面那段C++代碼應解釋為向一個NULL對象發送Hello消息,這顯然是不應該順利執行的。類似的代碼如果是在Java或C#中則會拋出空引用異常,所以Java和C#的設計更符合消息模型。
不過,Java和C#中也並非完全符合消息模型,來看一個經典的封裝問題:
 //C#
 
public class Account {
    private int _amount;
 
    public void Transfer(Account acc, int delta) {
        acc._amount += delta;
        this._amount -= delta;
    }
    …
}
上面定義了一個Account類,問題在於為什麼在這個類的Transfer方法中可以直接訪問另一個對象acc的私有成員_amount呢?這是不是有破壞封裝的嫌疑呢?這個問題經典的答案是:並不破壞封裝,封裝是劃分了基於類的靜態的代碼邊界,使得類的private代碼修改不影響外界,而不是對於動態對象的保護。這個解釋當然是合理的,不過正如上面C++代碼的解釋屬於C++對象模型范疇,這個解釋則屬於基於類的靜態類型OOP語言的范疇。消息模型強調了對象內部狀態的保護,只能通過消息改變其狀態,而對象內部是否真的具有_amout這樣一個私有成員對其他任何對象(即使同類對象)都是未知的。
如果要嚴格遵守消息模型實現對象內部狀態的保護應該怎麼做呢?我們來看一個例子,定義一個集合類,包括:1.集合對象的構造函數;2.In方法:判斷元素是否存在;3.Join方法:對兩個集合做交集;4.Union方法:對兩個集合做並集。下面是一種Javascript實現:
 //Javascript
 
//集合類Set的構造函數
function Set() {
    var _elements = arguments;
    //In方法:判斷元素e是否在集合中
    this.In = function(e) {
        for (var i = 0; i < _elements.length; ++i) {
            if (_elements[i] == e) return true;
        }
        return false;
    };
}
 
//Join方法:對兩個集合求交集
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;
};
 
//Union方法:對兩個集合求並集
Set.prototype.Union = function(s2) {
    var s1 = this;
    var s = new Set();
    s.In = function(e) { return s1.In(e) || s2.In(e); }
    return s;
};
 
var s1 = new Set(1, 2, 3, 4, 5);
var s2 = new Set(2, 3, 4, 5, 6);
var s3 = new Set(3, 4, 5, 6, 7);
assert(false == s1.Join(s2).Join(s3).In(2));
assert(true == s1.Join(s2).Uion(s3).In(7));
如果是在靜態類型OOP語言中,要實現集合類的Join或Union,我們多半會像上面Account的例子一樣直接對s2內部的_elements進行操作,而上面這段Javascript定義的Set關於對象s2的訪問完全是符合消息模型的基於接口的訪問。要實現消息模型Javascript的prototype機制並非必須的,真正的關鍵在於函數式的高級函數和閉包特性。從這個例子我們也可以體會到函數式的優點不僅在於無副作用,函數的可組合性也是函數式編程強大的原因。
Method Missing
接下來我們還要進行深度歷險,讓我們思考一下如果發送一條對象不能識別的消息會怎樣?這種情況在C++、Java、C#等靜態類型語言中會得到一個方法未定義的編譯錯誤,如果是在Javascript中則會產生運行時異常。比如,s1.count()會產生一個運行時異常:Object #<Set> has no method ‘count’。
在靜態類型語言這個問題很少受到重視,但在動態類型語言中卻大有文章,來看下面的例子:
//Ruby
 builder = Builder::XmlMarkup.new
xml = builder.books {|b|
    b.book :isbn => "14134" do
        b.title "Revelation Space"
        b.author "Alastair Reynolds"
    end
    b.book :isbn => "53534" do
        b.title "Accelerando"
        b.author "Charles Stross"
    end
}
上面這段很DSL的Ruby代碼創建了這樣一個XML文件對象:
 <books>
    <book isbn="14134">
        <title>Revelation Space</title>
        <author>Alastair Reynolds</author>
    </book>
    <book isbn="53534">
        <title>Accelerando</title>
        <author>Charles Stross</author>
    </book>
</books>
builder.books, b.book, b.title都是對象方法調用,由於XML的元素名是任意的,所以不可能事先定義這些方法,類似的代碼如果是在Javascript中就是no method異常。那為什麼上面的Ruby代碼可以正確執行呢?其實只要理解了消息模型就很容易想明白,只需要定義一個通用的消息處理方法,所有未明確定義的消息都交給它來處理就行了,這就是所謂的Method Missing模式:
 class Foo
    def method_missing(method, *args, &block)
        …
    end
end
Method Missing除了對實現DSL很重要外,還可用於產生更好地調試和錯誤信息,把參數嵌入到方法名中等場合。目前,Ruby、Python、Groovy幾種語言對Method Missing都有很好的支持,甚至在C# 4.0中也可以利用動態特性實現。
總結
本文主要介紹了對象的消息模型的特征,並比較了C++對象模型,Java、C#等基於類的靜態類型語言中的對象模型與嚴格消息模型的差異,最後探討了Method Missing相關話題。
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved