これは私が遭遇した予期しない問題です。私は GUI アプリケーションを作成しており、GUI ライブラリーの 2 つのクラス (Model クラスと View クラス) を使用しています。モデルのコンテンツは、View クラスによって画面にレンダリングされます。ある時点で、拡張機能が必要だったので、Model クラスを派生させることにしました。ライブラリのクラスは派生することを意図しており、多くの例を見つけました。簡単で完璧に機能しました。
ここで問題があります。Model クラスは、モデル データを直接編集するためのメソッドを提供します。私はラッパーを書いたので、これらのメソッドを公開したくありません。これらのラッパーは編集する唯一の方法でなければなりません。派生した MyModel は Model クラスの多くの仮想メソッドをオーバーライドするため、継承が必要です。そこで、どうしようかと考えていました。以下は、すべての詳細をまとめたものです。
- モデルデータを反復するためのメソッドを提供する BasicModel クラスがあります
- BasicModel を継承し、データを編集するためのメソッドも提供することによってそれを拡張する Model クラスがあります (BaseModel は抽象的でデータを持たないと思います。Model は内部データ構造を定義し、BasicModel によって提供される反復インターフェースを実装します)。
- Model を継承する MyModel クラスがあります。多くの仮想メソッドをオーバーライドし、編集メカニズムを拡張し、モデルが提供する低レベルの編集メソッドを非表示にしたい
- BasicModel オブジェクトへのポインターを格納する View クラスがあります。したがって、ビューは反復インターフェースのみを使用し、編集インターフェースについても知りません。
そのため、MyModel の反復インターフェイスを公開したままにして、View オブジェクトに渡すことができるようにしますが、Model が提供する編集インターフェイスは非表示にします。
私が考えた解決策:
解決策 1:仮想メソッドをオーバーライドする必要があるため、継承を回避することはできませんが、解決策自体で継承を使用する必要はありません:カプセル化された MyModel へのconst 参照へのアクセスを提供する MyModel クラスへのラッパーを作成できます。オブジェクトであり、MyModel の編集メカニズムへのラッパーを提供します。すべてのラッパーのために醜いかもしれませんが、複数の継承とアクセス指定子を台無しにすることは避けられます。
解決策 2: MyModel にモデルを継承させる。多重継承なし。次に、MyModel.hpp の MyModel のクラス定義に移動し、すべてのモデルの編集メソッドのメソッド宣言をprotected
irの下private
に記述して、それらが非表示になるようにします。これは非常にうまく機能しますが、メンテナンスに小さな問題が 1 つあります。ライブラリの将来のバージョンで Model インターフェースが変更された場合、たとえば新しい編集メソッドが追加された場合、手動で MyModel クラスに private/ として追加する必要があります。保護されたメソッド。もちろん、ライブラリの変更を追跡したり、少なくともオンライン API リファレンスにアクセスして Model クラスのリファレンス ページを一瞥したりして、何も変更がないことを確認したり、必要に応じてコードを更新したりしてから、本番/安定バージョンの私のアプリケーションがリリースされました。
解決策 3:多重継承を使用すると、何が起こるのかわかりません。安全かどうか。動作がコンパイラに依存するかどうか。これがアイデアです: MyModel は Model および basicModel から継承します: BasicModel からのpublic
継承 (反復インターフェース用) およびprotected/private
Model からの継承 (Model の編集インターフェースを非表示にするため)。
ノート:
注 1 : 私の高レベルの編集メカニズムは、モデルの低レベルの編集方法を使用します。
注 2 : MyModel がオーバーライドする仮想メソッド。それらの一部は BasicModel によって定義され (したがってモデルにも継承されます)、一部は BasicModel に存在せず、モデルによって定義されます (たとえば、ドラッグ アンド ドロップに関連するメソッド)。
注 3:私が使用する GUI ライブラリはgtkmmであり、 GTK+の C++ バインディングです。私が話しているクラスは、Gtk::TreeModel、Gtk::TreeStore、Gtk::TreeView、および Gtk から派生した独自の MyModel クラスです。 ::ツリーストア。質問は一般的な OO 計画の質問であるため、これらの名前は無視しましたが、実際のクラスに精通している人が問題をより簡単に理解できるように、ここでは実際のクラスについて言及しています。
ここでどのようなデザインが最適かはわかりません。ソリューション 3 は、メンテナンス コストが最も低いソリューションです。継承アクセス指定子はすべての作業を自動的に行うだけなので、実際にはゼロです。問題は、ソリューション 3 が常に期待どおりに機能するかどうかです。たとえば、反復メソッドの場合、コンパイラはそれをパブリックにするか (BasicModel からのパブリック継承のため)、またはプライベート (BasicModel から派生したモデルからのプライベート継承のため) にしますか? このような多重継承を使用したことはありません...
疑似コード
これは、多かれ少なかれライブラリの仕組みです。
namespace GUI
{
class BasicModel
{
public:
/* iteration interface for observing the model */
iterator get_iter();
// but no data here, it's just an interface which calls protected virtual methods
// which are implemented by deriving classes, e.g. Model
protected:
virtual iterator get_iter_vfunc() = 0;
virtual void handle_signal();
};
class Model : public BasicModel
{
/* inherits public iteration interface*/
/* implements observation virtual methods from BasicModel*/
virtual iterator get_iter_vfunc() { /*...*/ }
/* has private data structures for the model data */
std::vector<Row> data;
/* has public editing interface, e.g. insert_row(), erase(), append()
/* has protected drag-n-drop related virtual methods*/
virtual void handle_drop();
};
}
私のコード:
class MyModel : public GUI::Model
{
/* implements virtual methods from BasicModel, e.g. default signal handlers*/
virtual void handle_signal() { /*...*/ }
/* implements drag-n-drop virtual methods from Model*/
virtual void handle_drop() { *...* }
/* inherits public iteration interface*/
/* inherits public editing interface (***which I want to hide***)
/* implements its own editing mechanism*/
void high_level_edit (int param);
};
GCC を試してみる
警告をオフにしてコンパイルした次のコードを試しました(そうしないと、GCCが文句を言います)。
#include <iostream>
class Base
{
public:
void func ()
{
std::cout << "Base::func() called" << std::endl;
func_vfunc ();
}
protected:
virtual void func_vfunc ()
{
std::cout << "Base::func_vfunc() called" << std::endl;
}
};
class Derived : public Base
{
protected:
virtual void func_vfunc ()
{
std::cout << "Derived::func_vfunc() called" << std::endl;
}
};
class MyClass : public Base, private Derived
{
};
int main (int argc, char* argv[])
{
Base base;
Derived derived;
MyClass myclass;
base.func ();
derived.func ();
myclass.func ();
return 0;
}
何らかの理由で GCC は呼び出しmyclass.func()
が曖昧であると主張しますが、func()
s の 1 つはプライベート継承のためにプライベートであると想定されているため、コンパイルできない理由がわかりません。要するに、それがバグではなく、物事がどのように機能するかを理解していないだけであると仮定すると、多重継承の提案された解決策は不可能です。私が間違っていなければ、この問題を解決する唯一の方法は仮想継承ですが、使用するクラスはライブラリクラスであり、仮想継承を使用していないため、使用できません。それでも、プライベートとパブリックの継承を一緒に使用しているため、問題が解決されず、あいまいな呼び出しになる可能性があります。
編集: Derived と MyClass を Base から仮想的に派生させようとしたところ、問題が完全に解決されました。しかし、私の場合、ライブラリのクラスを変更することはできないので、それはオプションではありません。