この問題は、C++ コーディング標準 (Sutter、Alexandrescu) の項目 39 で対処されており、「仮想関数を非パブリックにすることを検討し、パブリック関数を非仮想にすることを検討してください」と題されています。
特に、Non-Virtual-Interface 設計パターンに従う動機の 1 つ (これが項目のすべてです) は次のように述べられています。
各インターフェイスは自然な形を取ることができます: パブリック インターフェイスをカスタマイズ インターフェイスから分離すると、それぞれが同じように見えるようにする妥協点を見つけようとするのではなく、自然に取りたい形を簡単にとることができます。多くの場合、2 つのインターフェイスは異なる数の関数や異なるパラメーターを必要とします。[...]
これは特に便利です
変更のコストが高い基本クラス
この場合に非常に役立つもう 1 つのデザイン パターンは、Visitor パターンです。NVI に関しては、基本クラス (および階層全体) の変更コストが高い場合に適用されます。この設計パターンについては、多くの議論があります。Modern C++ (Alexandrescu) の関連する章を読むことをお勧めします。これは、(非常に使いやすい) ビジター機能の使用方法に関する優れた洞察を提供します。ロキで
この資料をすべて読んでから、質問を編集して、より良い回答を提供できるようにすることをお勧めします. 私たちはあらゆる種類の解決策を考え出すことができます (たとえば、必要に応じて、クラスに追加のパラメーターを与える追加のメソッドを使用します)。
次の質問に答えてみてください。
- テンプレートベースのソリューションは問題に適合しますか?
- 関数を呼び出すときに間接的な新しいレイヤーを追加することは可能でしょうか?
- 「プッシュ引数」-「プッシュ引数」-...-「プッシュ引数」-「関数呼び出し」メソッドは役に立ちますか? (これは最初は非常に奇妙に思えるかもしれませんが、「cout << arg << arg << arg << endl」のように考えてください。ここで、「endl」は「呼び出し関数」です)
- Computer::compute で関数を呼び出す方法をどのように区別するつもりですか?
いくつかの「理論」が得られたので、ビジター パターンを使用した実践を目指しましょう。
#include <iostream>
using namespace std;
class FeatureA;
class FeatureB;
class Computer{
public:
int visitA(FeatureA& f);
int visitB(FeatureB& f);
};
class Feature {
public:
virtual ~Feature() {}
virtual int accept(Computer&) = 0;
};
class FeatureA{
public:
int accept(Computer& c){
return c.visitA(*this);
}
int compute(int a){
return a+1;
}
};
class FeatureB{
public:
int accept(Computer& c){
return c.visitB(*this);
}
int compute(int a, int b){
return a+b;
}
};
int Computer::visitA(FeatureA& f){
return f.compute(1);
}
int Computer::visitB(FeatureB& f){
return f.compute(1, 2);
}
int main()
{
FeatureA a;
FeatureB b;
Computer c;
cout << a.accept(c) << '\t' << b.accept(c) << endl;
}
このコードはこちらで試すことができます。これは、ご覧のとおり、問題を解決する Visitor パターンの大まかな実装です。この方法で実装しようとしないことを強くお勧めします。非環式ビジターと呼ばれる改良によって解決できる明らかな依存関係の問題があります。Loki では実装済みですので、実装に悩む必要はありません。
実装とは別に、ご覧のとおり、型スイッチに依存しておらず (他の誰かが指摘したように、可能な限り避けるべきです)、クラスに特定のインターフェイス (たとえば、compute 関数の 1 つの引数) を持たせる必要はありません。 )。さらに、ビジター クラスが階層の場合 (この例では Computer を基本クラスにします)、この種の機能を追加するときに階層に新しい関数を追加する必要はありません。
visitA、visitB、...「パターン」が気に入らなくても、心配する必要はありません。これは単純な実装であり、必要ありません。基本的に、実際の実装では、visit 関数のテンプレートの特殊化を使用します。
これが役に立てば幸いです、私はそれに多くの努力をしました:)