對象的訊息模型

來源:互聯網
上載者:User

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#中則會拋出Null 參考異常,所以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相關話題。

 

參考

 

Inside the C++ Object Model

冒號課堂 - 編程範式與OOP思想

Alan Kays Definition Of Object Oriented

OOP The Good Parts: Message Passing, Duck Typing, Object Composition, and not Inheritance

Patterns of Method Missing

Fun With Method Missing and C# 4

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.