2008-12-18 66 views
19

我知道有菱形繼承被認爲是不好的做法。但是,我有兩個案例,我認爲鑽石繼承可以很好地適應。我想問,你會推薦我在這些情況下使用鑽石繼承,還是有另一種設計可能會更好。菱形繼承(C++)

案例1:我想在我的系統中創建代表不同類型「操作」的類。這些操作按幾個參數分類:

  • 該操作可以是「讀取」或「寫入」。
  • 動作可以是具有延遲或無延遲(它不僅是1點的參數,它顯著改變行爲)。
  • 動作的「流量類型」可以是FlowA或FlowB。

我打算在具有以下設計:

// abstract classes 
class Action 
{ 
    // methods relevant for all actions 
}; 
class ActionRead  : public virtual Action 
{ 
    // methods related to reading 
}; 
class ActionWrite  : public virtual Action 
{ 
    // methods related to writing 
}; 
class ActionWithDelay : public virtual Action 
{ 
    // methods related to delay definition and handling 
}; 
class ActionNoDelay : public virtual Action {/*...*/}; 
class ActionFlowA  : public virtual Action {/*...*/}; 
class ActionFlowB  : public virtual Action {/*...*/}; 

// concrete classes 
class ActionFlowAReadWithDelay : public ActionFlowA, public ActionRead, public ActionWithDelay 
{ 
    // implementation of the full flow of a read command with delay that does Flow A. 
}; 
class ActionFlowBReadWithDelay : public ActionFlowB, public ActionRead, public ActionWithDelay {/*...*/}; 
//... 

當然,我會服從,沒有2個行動(從動作類繼承)將實施同樣的方法。

案例2:我在我的系統中實現了「Command」的組合設計模式。命令可以被讀取,寫入,刪除等。我也想要一系列命令,這些命令也可以被讀取,寫入,刪除等。一系列命令可以包含其他命令序列。

所以,我有如下設計:

class CommandAbstraction 
{ 
    CommandAbstraction(){}; 
    ~CommandAbstraction()=0; 
    void Read()=0; 
    void Write()=0; 
    void Restore()=0; 
    bool IsWritten() {/*implemented*/}; 
    // and other implemented functions 
}; 

class OneCommand : public virtual CommandAbstraction 
{ 
    // implement Read, Write, Restore 
}; 

class CompositeCommand : public virtual CommandAbstraction 
{ 
    // implement Read, Write, Restore 
}; 

另外,我有一種特殊的命令,「現代」的命令。一個命令和複合命令都可以是現代的。 「Modern」爲一個命令和複合命令添加了一定的屬性列表(兩者大都屬於相同的屬性)。我希望能夠持有指向CommandAbstraction的指針,並根據所需的命令類型對其進行初始化(通過新建)。所以,我要做到以下幾點設計(除了以上):

class ModernCommand : public virtual CommandAbstraction 
{ 
    ~ModernCommand()=0; 
    void SetModernPropertyA(){/*...*/} 
    void ExecModernSomething(){/*...*/} 
    void ModernSomethingElse()=0; 

}; 
class OneModernCommand : public OneCommand, public ModernCommand 
{ 
    void ModernSomethingElse() {/*...*/}; 
    // ... few methods specific for OneModernCommand 
}; 
class CompositeModernCommand : public CompositeCommand, public ModernCommand 
{ 
    void ModernSomethingElse() {/*...*/}; 
    // ... few methods specific for CompositeModernCommand 
}; 

而且,我將確保沒有2班從CommandAbstraction類繼承將實施同樣的方法。

謝謝。

回答

19

繼承是C++中第二強(更多耦合)關係,僅在友誼之前。如果你可以重新設計成只使用合成,你的代碼將更加鬆散耦合。如果你不能,那麼你應該考慮是否所有的類都應該從基地繼承。是由於實現還是僅僅是一個接口?你想使用層次結構的任何元素作爲基本元素嗎?或者只是在您的層次結構中留下真正的Action?如果只有葉子是動作並且您正在添加行爲,則可以考慮基於策略的設計來處理這種類型的行爲組合。

這個想法是,不同的(正交)行爲可以在小類集中定義,然後捆綁在一起提供真正的完整行爲。在這個例子中,我將只考慮一個策略,該策略定義了現在還是將來要執行的動作以及要執行的命令。

我提供了一個抽象類,以便模板的不同實例可以在容器中存儲(通過指針)或作爲參數傳遞給函數並以多態方式調用。

class ActionDelayPolicy_NoWait; 

class ActionBase // Only needed if you want to use polymorphically different actions 
{ 
public: 
    virtual ~Action() {} 
    virtual void run() = 0; 
}; 

template < typename Command, typename DelayPolicy = ActionDelayPolicy_NoWait > 
class Action : public DelayPolicy, public Command 
{ 
public: 
    virtual run() { 
     DelayPolicy::wait(); // inherit wait from DelayPolicy 
     Command::execute(); // inherit command to execute 
    } 
}; 

// Real executed code can be written once (for each action to execute) 
class CommandSalute 
{ 
public: 
    void execute() { std::cout << "Hi!" << std::endl; } 
}; 

class CommandSmile 
{ 
public: 
    void execute() { std::cout << ":)" << std::endl; } 
}; 

// And waiting behaviors can be defined separatedly: 
class ActionDelayPolicy_NoWait 
{ 
public: 
    void wait() const {} 
}; 

// Note that as Action inherits from the policy, the public methods (if required) 
// will be publicly available at the place of instantiation 
class ActionDelayPolicy_WaitSeconds 
{ 
public: 
    ActionDelayPolicy_WaitSeconds() : seconds_(0) {} 
    void wait() const { sleep(seconds_); } 
    void wait_period(int seconds) { seconds_ = seconds; } 
    int wait_period() const { return seconds_; } 
private: 
    int seconds_; 
}; 

// Polimorphically execute the action 
void execute_action(Action& action) 
{ 
    action.run(); 
} 

// Now the usage: 
int main() 
{ 
    Action<CommandSalute> salute_now; 
    execute_action(salute_now); 

    Action< CommandSmile, ActionDelayPolicy_WaitSeconds > smile_later; 
    smile_later.wait_period(100); // Accessible from the wait policy through inheritance 
    execute_action(smile_later); 
} 

繼承的使用允許通過模板實例化訪問策略實現中的公共方法。這不允許使用聚合來合併策略,因爲沒有新的功能成員可以被推入類接口。在該示例中,模板取決於具有wait()方法的策略,這對所有等待策略都是通用的。現在等待一段時間需要通過period()公用方法設置的固定時間段。

在此示例中,NoWait策略只是WaitSeconds策略的一個特定示例,其週期設置爲0.這是爲了標記策略接口不需要相同。另一個等待策略的實現可能會等待幾毫秒,時鐘滴答,或者直到某個外部事件,通過提供一個類來註冊給定事件的回調。

如果你不需要多態性,你可以從基礎類和虛擬方法的例子中總結出來。雖然目前的例子看起來可能過於複雜,但您可以決定將其他策略添加到組合中。

雖然如果使用純繼承(使用多態性),添加新的正交行爲將意味着類的數量呈指數級增長,使用此方法,您可以分別實現每個不同的部分,並將它們粘合在Action模板中。

例如,您可以定期執行操作並添加退出策略,以確定何時退出定期循環。首先想到的選項是LoopPolicy_NRuns和LoopPolicy_TimeSpan,LoopPolicy_Until。這個策略方法(在我的情況下是exit())在每個循環中被調用一次。第一個實現計算了在一個固定的數字之後它被稱爲退出的次數(由用戶固定,因爲在上面的例子中固定了時間段)。第二個實現將定期運行一個給定時間段的進程,而最後一個將運行此進程直到給定時間(時鐘)。

如果你仍然跟着我到這裏,我確實會做一些改變。第一個是不是使用模板參數Command來實現一個方法execute()我會使用函數,並且可能是一個模板化的構造函數,它將命令作爲參數執行。其基本原理是,這將使其與其他庫結合使用,例如boost :: bind或boost :: lambda,因爲在這種情況下,命令可以在實例化時綁定到任何自由函數,函子或成員方法一類。

現在我得走了,但如果你有興趣,我可以嘗試發佈修改後的版本。

+0

嗨大衛。我有興趣學習高級OOP(繼承)主題,就像你在上面提到的帖子(函數,虛函數等)一樣。你可以推薦任何關於這個主題的書籍嗎? – 2010-10-19 13:01:50

+0

@Carlo del Mundo:在這[問題]中有一本很好的書籍綱要(http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list)我特別喜歡閱讀Alexandrescu (不適合初學者)和Sutter。 – 2010-10-19 19:01:22

9

還有的地方實現繼承(風險)面向實現的菱形繼承之間的設計品質的差異,分型爲導向的繼承某些接口或標記接口繼承(相當實用)。

一般來說,如果你能避免前者,你是因爲某處的確切調用的方法可能會導致問題行更好,和虛擬基地的重要性,規定等,開始無所謂。事實上,Java不會允許你拉這樣的東西,它只支持接口層次結構。

我認爲「最乾淨」的設計,你可以拿出來,這是有效地把鑽石成模擬接口所有的類(由沒有狀態信息,並具有純虛方法)。這減少了歧義的影響。當然,您可以爲此使用多個甚至是菱形繼承,就像在Java中使用implements一樣。然後,有一組這些接口的具體實現可以用不同的方式實現(例如,聚合,甚至繼承)。

封裝此框架,以便外部客戶端只能獲取接口並且不會直接與具體類型進行交互,並確保徹底測試您的實現。

當然,這是很多工作,但如果你正在編寫一箇中心且可重用的API,這可能是你最好的選擇。

1

隨着第一個例子.....

其ActionRead ActionWrite是否需要採取行動的子類的。

因爲您將最終得到一個具體的類,無論如何,這只是一個動作,您可以繼承動作讀取和動作寫入,而不必將它們作爲自己的動作。

雖然,你可以發明代碼,要求他們是行動。但總的來說,我會嘗試和分開行動,閱讀,寫,延遲,只是具體的類混合在一起

-1

對於情況2,是不是OneCommand只是CompositeCommand的特例?如果您消除OneCommand並允許CompositeCommand s到只有一個元素,我覺得你的設計變得更加簡單:

   CommandAbstraction 
       /  \ 
       /   \ 
      /   \ 
     ModernCommand  CompositeCommand 
       \    /
       \   /
       \   /
      ModernCompositeCommand 

你仍然有可怕的鑽石,但我認爲這可能是它可以接受的情況下。

+0

謝謝,但不是,OneCommand和CompositeCommand有根本的不同。 – 2008-12-18 21:19:05

1

不知道你在做什麼, 我可能會重組一些東西。 而不是多個繼承與所有這些版本的行動, 我會做多態的閱讀和寫作和寫作類, instanciated作爲代表。

類似下面的(沒有鑽的傳承):

這裏我提出的途徑之一實現可選的延遲, 並承擔延遲的方法是爲所有讀者一樣。 每個子類可能都有自己的延遲 的實現,在這種情況下,您可以將其傳遞給相應派生的延遲類的讀取和實例 。

class Action // abstract 
{ 
    // Reader and writer would be abstract classes (if not interfaces) 
    // from which you would derive to implement the specific 
    // read and write protocols. 

    class Reader // abstract 
    { 
     Class Delay {...}; 
     Delay *optional_delay; // NULL when no delay 
     Reader (bool with_delay) 
     : optional_delay(with_delay ? new Delay() : NULL) 
     {}; 
     .... 
    }; 

    class Writer {... }; // abstract 

    Reader *reader; // may be NULL if not a reader 
    Writer *writer; // may be NULL if not a writer 

    Action (Reader *_reader, Writer *_writer) 
    : reader(_reader) 
    , writer(_writer) 
    {}; 

    void read() 
    { if (reader) reader->read(); } 
    void write() 
    { if (writer) writer->write(); } 
}; 


Class Flow : public Action 
{ 
    // Here you would likely have enhanced version 
    // of read and write specific that implements Flow behaviour 
    // That would be comment to FlowA and FlowB 
    class Reader : public Action::Reader {...} 
    class Writer : public Action::Writer {...} 
    // for Reader and W 
    Flow (Reader *_reader, Writer *_writer) 
    : Action(_reader,_writer) 
    , writer(_writer) 
    {}; 
}; 

class FlowA :public Flow // concrete 
{ 
    class Reader : public Flow::Reader {...} // concrete 
    // The full implementation for reading A flows 
    // Apparently flow A has no write ability 
    FlowA(bool with_delay) 
    : Flow (new FlowA::Reader(with_delay),NULL) // NULL indicates is not a writer 
    {}; 
}; 

class FlowB : public Flow // concrete 
{ 
    class Reader : public Flow::Reader {...} // concrete 
    // The full implementation for reading B flows 
    // Apparently flow B has no write ability 
    FlowB(bool with_delay) 
    : Flow (new FlowB::Reader(with_delay),NULL) // NULL indicates is not a writer 
    {}; 
}; 
4

接口的繼承層次結構中的「鑽石」是相當安全的 - 它是代碼的繼承,讓你進入熱水。

爲了獲得代碼重用,我建議您考慮mixins(如果您不熟悉tequnique,請考慮使用mixin)。在使用mixins時,您覺得您可以「購物」獲取代碼片段,而無需使用多態繼承的有狀態類來實現您的類。

所以,這種模式是 - 接口的多重繼承和單個mixin鏈(給你代碼重用)來幫助實現具體類。

希望有幫助!