面向物件程式設計中如何描述物件之間的關係?
阿新 • • 發佈:2019-01-08
談到面向物件程式設計中如何選擇物件之間的關係,實在是一件令人頭疼的事。
物件之間的關係大致有以下幾種:
1. 繼承關係
舉個例子:Person是一個描述所有人的類,Student可以繼承於Person,所有Person具有的公有方法Student都有,即任何使用Person物件的地方,都可以毫無顧忌的直接用Student代替。
這種關係是非常強的,有時候產生的問題會讓你始料未及,而且很難修改和重構,特別是如果想修改基類的時候,可能就會導致不是所有的子類都能適用了。例如:
Bird類是描述所有的鳥的類,有一個Penguin類繼承自Bird類;最初的時候Bird類沒有fly()這個公有介面,所以Penguin類可以正常使用,現在想要讓fly作為Bird類的公有介面,那麼所有Bird的子類都必須實現這個新的介面,但是問題來了,Penguin是不能飛的,如果強行用丟擲異常來實現這個介面就必須只能在執行期才能檢查出來了。這其實就導致了“不好的介面”。時間一長,程式的維護將會非常困難。
繼承關係用C++表示出來如下:
class Base
{}
class Derived : public Base
{}
2. 聚合關係
傳統的OO書上將組合關係還細分為組合和聚合。它們的區別可以從物件使用者的角度來看,如果是組合,那麼使用者不需要知道物件內部組合的東西;如果是聚合,那麼使用者需要知道物件組合了什麼。
這麼看來,聚合通常是比組合更鬆的組合,通常類似於一個容器的概念。組合則類似於多個小的組成部分(可以是相同的類,也可以是不同的類)共同組合成了一個大的物件,沒有容器的概念。
所以,聚合我們就把它當作容器來看,如果聚合物件有一些特有的公有方法的話,可以用一個模板容器類來做聚合類(例一:一般的Pool,有一些特有的方法,可以包裝一下stl的list/deque等容器,提供特定的方法接口出來);如果簡單的話,直接用stl提供的集合即可。
組合關係其實也是很強的關係,而且針對不同的應用場景,劃分組合的方法也不同。例如,對於人來說,可以說是由頭,軀幹,四肢組成,任何部位不能單獨被外界直接接觸,而必須通過人這個組合物件來間接操作,甚至外界根本不知道人的組成結構,只知道人這個東西可以走路,思考等等。組合關係隱含了很強的封裝思想,但是帶來的可能是不靈活性,萬一以後的新需求需要讓外界知道組合體內部的情況則顯得會有點亂;而且組合體內部物件的構造也可能是個問題,因為組合體內部物件的構造可能不是組合體自己就能完成的,可能需要藉助外部,這樣組合體可能需要依賴於一系列工廠,或者利用現代面向物件更多會採用的依賴注入。
組合關係用C++表示出來如下:
class Comb
{
public:
(public methods ...)
private:
ClassA a;
}
聚合關係類似於stl的容器。
3. 使用關係
繼承和聚合關係中兩個物件的關係都比較強,而使用關係是一種表達兩個物件弱相關性的關係,包含傳統的UML中的關聯,依賴這兩個關係。
使用關係描述的是一個物件可以使用另一個物件完成某件自己想完成的事。
使用關係也可以叫做委託關係,就是把一項任務委託給另一個物件去完成的含義。這樣把任務細分,就類似於人類社會的分工一樣,分工越細,社會也越進步。
使用關係用C++表示出來如下: class ClassA { private: ClassB *b; // 用指標因為ClassA只是使用了b,b必須從外面設定進來 } 這個實現和UML中的關聯關係是一致的。 在使用關係的使用中,最好把被使用物件(上面的ClassB)設定為抽象基類,而且要從ClassA的角度來為ClassB命名,這樣ClassA的實現可以很單純,不需要有不必要的依賴。 如果ClassB中只有一個公有介面,那麼可以像C#裡面使用委託物件來代替(其實就是一個std::function),無需再為此專門弄一個類了。 另外,如果把ClassB *b放到方法呼叫的引數中,那麼就是UML中的依賴關係了。 小結: 1. 繼承關係是最強的一種關係,如果基類介面以後會更改,那麼千萬不要採用繼承關係。 2. 聚合關係是描述系統中的實體的主要方法,大多數情況下可以使用樹形結構來描述系統中的實體的關係,採用聚合關係是非常合適來構造樹形結構的。 3. 使用關係是最常用的,在兩個弱相關的物件之間,使用關係是唯一的選擇。使用關係的建立往往需要其他物件來完成,使用第三個物件把兩個物件的關聯關係建立起來。
使用關係用C++表示出來如下: class ClassA { private: ClassB *b; // 用指標因為ClassA只是使用了b,b必須從外面設定進來 } 這個實現和UML中的關聯關係是一致的。 在使用關係的使用中,最好把被使用物件(上面的ClassB)設定為抽象基類,而且要從ClassA的角度來為ClassB命名,這樣ClassA的實現可以很單純,不需要有不必要的依賴。 如果ClassB中只有一個公有介面,那麼可以像C#裡面使用委託物件來代替(其實就是一個std::function),無需再為此專門弄一個類了。 另外,如果把ClassB *b放到方法呼叫的引數中,那麼就是UML中的依賴關係了。 小結: 1. 繼承關係是最強的一種關係,如果基類介面以後會更改,那麼千萬不要採用繼承關係。 2. 聚合關係是描述系統中的實體的主要方法,大多數情況下可以使用樹形結構來描述系統中的實體的關係,採用聚合關係是非常合適來構造樹形結構的。 3. 使用關係是最常用的,在兩個弱相關的物件之間,使用關係是唯一的選擇。使用關係的建立往往需要其他物件來完成,使用第三個物件把兩個物件的關聯關係建立起來。