ダイヤモンドの継承(C++)
-
22-08-2019 - |
質問
知っているダイヤモンドの継承とは悪いです。しかし、私は2つの場合を感じるダイヤモンドの継承が合う。えていただきたいと思いいただけますでしょうお勧めし使えるようになったのでダイヤモンドの継承この場合、又はしおりを付けることができるデザインは改善の余地がある。
例1: をしたいと思っているクラスを表すの異なる種類の"行動"私のシステム。行動分類による複数パラメータ:
- の問題を編集し直すときは、"Read"、"Write"です。
- の作動不良の原因になりますと遅延または遅滞なく(すなわずか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: 私の実施の複合デザインパターンのための"コマンド"私のシステム。コマンドが読み取り可能な記述を削除等また、私はい配列のコマンドで読み込みが完了すると、削除等配列のコマンドでそれ以外のものを含む配列をコマンド.
いて以下のデザイン:
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、初期化で経由新により、必要なタイプのコマンドです。きたいと思っていましたしなければならないデザイン(上記の他):
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クラスの実装と同じ方法です。
お願い致します。
解決
継承のみ友情が先行C ++における第最強(複数の結合)の関係です。あなたは、あなたのコードをより疎結合される組成のみを使用してに再設計することができます。あなたができない場合は、すべてのクラスが本当に底から継承する必要があるかどうかを検討すべきです。それは、実装または単にインターフェイスのためですか?あなたは基本要素として階層の任意の要素を使用したいと思うでしょうか?それとも、実際の行動のあるその階層の葉されていますか?唯一の葉はアクションであり、あなたが行動を追加する場合は、行動のこの種の組成物のためのポリシーベースの設計を考慮することができます。
アイデアが異なる(直交)行動は、小さなクラスのセットで定義され、その後、実際の完全な動作を提供するために一緒にバンドルすることができるということです。例では、私はちょうど1アクションが現在または将来的に実行されるかどうかを定義するポリシー、および実行するコマンドを検討します。
テンプレートの異なるインスタンスは、コンテナ内に(ポインタを介し)保存したり、引数として関数に渡され、多形と呼ばれ得ることができるように、私は、抽象クラスを提供します。
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 );
}
継承の使用は、ポリシーの実装から公開方法はテンプレートのインスタンスを介してアクセスすることを可能にします。これは、新しい機能のメンバーは、クラスのインターフェイス内に押し込むことはできなかったとして政策を組み合わせるための集約の使用を禁止します。例では、テンプレートは、すべての待機中のポリシーに共通である待機()メソッドを持つ政策に依存しています。今期間の期間を経て設定された一定期間の時間()パブリックメソッドを必要と待っています。
の例では、待ちなしポリシーは、ポリシー・インターフェースは同じである必要はないことをマークするために意図的であった0これに設定された期間とWaitSecondsポリシーのちょうど特定の一例です。もう待っている政策の実施は、クロックティック、または何らかの外部イベントが発生するまで、与えられたイベントのためのコールバックとして登録するクラスを提供することにより、ミリ秒数で待機していることができます。
あなたが多型を必要としない場合は、あなたは完全に例から基底クラスと仮想メソッドを取り出すことができます。これは、現在例えば過度に複雑に見えるかもしれませんが、あなたがミックスに、他のポリシーを追加することを決定することができます。
このアプローチであなただけの個別異なる部分を実装することができますし、アクションテンプレートにそれを一緒にのり、無地の継承が使用されている場合(多型と)クラスの数が指数関数的な成長を暗示する新しい直交行動を添加しながら。
たとえば、あなたのアクションが定期的に作成し、定期的にループを終了するかを決定終了ポリシーを追加することができます。心に来る最初の選択肢はLoopPolicy_NRunsとLoopPolicy_TimeSpan、LoopPolicy_Untilです。このポリシー・メソッド(私の場合は、出口は、())各ループごとに1回呼び出されます。最初の実装は、固定数(期間は上記の例では、固定されたように、ユーザによって固定)した後に終了呼び出された回数をカウントします。最後のものは、所定の時間(クロック)まで、このプロセスを実行する間に、第2の実装は、定期的に、所定の時間期間のためのプロセスを実行します。
あなたはまだここに私をフォローアップしている場合は、、私は確かにいくつかの変更を行います。一つ目は、私はファンクタ、パラメータとして実行するコマンドを取り、おそらくテンプレートコンストラクタを使用する代わりの方法は、(実行実装するテンプレートパラメータコマンドを使用する)ということです。根拠は、これが::ブーストなどの他のライブラリとの組み合わせで、それははるかに拡張可能にするバインドまたは後押し::ラムダを、その場合にはコマンドは任意のフリー機能、ファンクタ、またはメンバーメソッドにインスタンス化の時点でバインドすることができので、ということですクラスのます。
今、私が行かなければならないが、あなたが興味を持っている場合、私は修正版を投稿してみてくださいすることができます。
他のヒント
実装は(危険な)継承、およびインターフェイスまたはマーカー・インタフェースが(多くの場合に有用)継承されるサブタイピング指向の継承された実装配向ダイヤモンド継承の間で設計品質の違いがあります。
一般的に、あなたはかつてのを避けることができれば、あなたはどこかにラインの下の正確な呼び出されたメソッドは、問題、およびなどの仮想基盤、状態、の重要性を引き起こす可能性があるため方がいいでしょう、異物感を開始します。実際には、Javaはあなたがそのような何かをプルすることができないだろう、それが唯一のインタフェースの階層をサポートしています。
私はあなたがこれを考え出すことができる「クリーン」なデザインが効果的に(何の状態情報を持たない、純粋な仮想メソッドを持つことによって)モックインタフェースにダイヤモンドにすべてのクラスを有効にすることだと思います。これは、あいまいさの影響を低減します。そしてもちろん、あなたはJavaで実装を使用するのと同じように、このためにも、複数のダイヤモンドの継承を使用することができます。
次に、様々な方法(例えば、凝集、偶数継承)で実施することができ、これらのインタフェースの具体的な実装のセットを有している。
外部クライアントが唯一のインターフェイスを取得し、具体的な種類と直接対話することはありません、と徹底的にあなたの実装をテストすることを確認するようにこのフレームワークをカプセル化します。
もちろん、これは多くの作業ですが、中央および再利用可能なAPIを書いているならば、これはあなたの最善の策かもしれません。
私はちょうど今週、この問題に遭遇し、問題とするとき、あなたがすべきか、それらを心配すべきではないと説明しDDJの記事を見つけました。ここでは、次のとおりです。
「ダイヤモンド」非常に安全である - それはあなたがお湯の中にあります取得するコードの継承です。
。コードの再利用を取得するには、私は(あなたがtequniqueに慣れていない場合はC ++ミックスインのためのgoogle)ミックスインを検討することをアドバイス。ミックスインを使用する場合は、あなたがステートフルクラスの多重継承を使用せずに、あなたクラスを実装する必要があるコードスニペットは、「買い物に行く」ことができますようにあなたが感じています。
だから、パターンがある - インタフェースの多重継承と具象クラスの実装を支援するために(あなたのコードの再利用を与える)ミックスインの単鎖
。 助け希望!
最初の例では.....
そのActionRead ActionWriteは、すべてのアクションのサブクラスであることを必要があるかどうか。
あなたはとにかくアクションになります1つの具象クラスで終わるしようとしているので、、あなただけの彼らが自分自身で行動せずにactionreadとactionwriteを継承することができます。
しかし、あなたが行動すること、それらを必要とするコードを発明することができます。しかし、一般的に、私は試してみた、別のアクションは、読み取り、書き込み、および遅延やただ具体的なクラスが一緒にすべてのことをミックス
あなたが何をしているかのより多くを知ってして、 私はおそらく少し物事を再編成します。 代わりに、アクションのすべてのこれらのバージョンで複数の継承の、 私は、多型の読み取りと書き込みと書き込みのクラスになるだろう、 代表者としてインスタンス化ます。
(なしダイヤモンド継承を持っていません)、次のような何か:
ここで私は、オプションの遅延を実装する多くの方法のうちの1つを提示し、 遅延の方法論は、すべての読者のために同じであると仮定します。 各サブクラスは、遅延の独自の実装を持っているかもしれません その場合、あなたは読みのインスタンスにダウン渡します それぞれの遅延クラスを派生します。
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
sを許可した場合、私はあなたのデザインがシンプルな取得と思う:
CommandAbstraction
/ \
/ \
/ \
ModernCommand CompositeCommand
\ /
\ /
\ /
ModernCompositeCommand
あなたはまだ恐ろしいダイヤモンドを持っていますが、私は、これはそれのために許容可能なケースかもしれないと思う。