[英]Diamond inheritance (C++)
我知道鑽石繼承被認為是不好的做法。 但是,我有2個案例,我認為鑽石繼承可以很好地適應。 我想問一下,您是否會建議我在這些情況下使用鑽石繼承,或者是否有其他設計可能更好。
案例1:我想在我的系統中創建代表不同類型“動作”的類。 這些操作按以下幾個參數進行分類:
我打算有以下設計:
// 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個動作(繼承自Action類)將實現相同的方法。
案例2:我在系統中為“命令”實現了復合設計模式。 可以讀取,寫入,刪除等命令。我還希望有一系列命令,這些命令也可以被讀取,寫入,刪除等。一系列命令可以包含其他命令序列。
所以我有以下設計:
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
};
另外,我有一種特殊的命令,“現代”命令。 一個命令和復合命令都可以是現代的。 “現代”為一個命令和復合命令添加了一定的屬性列表(兩者的屬性大多相同)。 我希望能夠擁有一個指向CommandAbstraction的指針,並根據所需的命令類型對其進行初始化(通過new)。 所以我想做以下設計(除了上面的):
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
};
同樣,我將確保從CommandAbstraction類繼承的2個類不會實現相同的方法。
謝謝。
繼承是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更加可擴展,因為在這種情況下,命令可以在實例化時綁定到任何自由函數,仿函數或成員方法一堂課。
現在我必須去,但如果你有興趣,我可以嘗試發布修改版本。
面向實現的鑽石繼承(實現是繼承的)(風險)和面向子類型的繼承(接口或標記接口是繼承的)(通常很有用)之間存在設計質量差異。
一般來說,如果你可以避免使用前者,那么你最好從某個地方開始,確切的調用方法可能會導致問題,虛擬基礎,狀態等的重要性開始變得重要。 事實上,Java不會允許你拉這樣的東西,它只支持接口層次結構。
我認為你可以為此設計的“最干凈”的設計是將鑽石中的所有類有效地轉換為模擬接口(通過沒有狀態信息,並具有純虛方法)。 這減少了歧義的影響。 當然,您可以使用多個甚至鑽石繼承,就像在Java中使用implements一樣。
然后,有一組這些接口的具體實現,可以以不同的方式實現(例如,聚合,甚至繼承)。
封裝此框架,以便外部客戶端只獲取接口,並且永遠不會直接與具體類型交互,並確保徹底測試您的實現。
當然,這是很多工作,但如果您正在編寫一個可重用的中央API,這可能是您最好的選擇。
本周我遇到了這個問題,發現了一篇關於DDJ的文章解釋了這些問題以及何時應該或不應該關注它們。 這里是:
接口繼承層次結構中的“鑽石”非常安全 - 它是代碼的繼承,讓你陷入熱水。
為了獲得代碼重用,我建議你考慮一下mixins(如果你不熟悉tequnique,可以使用google for C ++ Mixins)。 使用mixins時,您覺得可以“購物”獲取實現類所需的代碼片段,而無需使用多重繼承有狀態類。
因此,模式是 - 接口的多重繼承和一個mixin鏈(給你代碼重用)來幫助實現具體的類。
希望有所幫助!
用第一個例子.....
它的ActionRead ActionWrite是否需要成為動作的子類。
因為你最終將得到一個將成為動作的具體類,你可以繼承actionread和actionwrite而不用它們本身就是動作。
但是,你可以發明需要它們作為動作的代碼。 但總的來說,我會嘗試將Action,Read,Write和Delay分開,只是具體的類將所有這些混合在一起
如果不知道你在做什么,我可能會重新組織一些事情。 我不會對所有這些版本的動作進行多重繼承,而是進行多態讀取,編寫和編寫類,實現為委托。
類似下面的內容(沒有鑽石繼承):
在這里,我介紹了實現可選Delay的許多方法之一,並假設所有讀者的延遲方法都是相同的。 每個子類可能有自己的延遲實現,在這種情況下,您將傳遞給Read和相應的派生Delay類的實例。
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
{};
};
對於案例2,是不是OneCommand
只是CompositeCommand
一個特例? 如果你消除OneCommand
並允許CompositeCommand
只有一個元素,我認為你的設計變得更簡單:
CommandAbstraction
/ \
/ \
/ \
ModernCommand CompositeCommand
\ /
\ /
\ /
ModernCompositeCommand
你仍然有可怕的鑽石,但我認為這可能是一個可接受的案例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.