2

バックグラウンド:

  • ObjectListModelを継承QAbstractListModelして含むクラスを呼び出していQObjectListます。オブジェクトは行で、そのプロパティは列 ( を使用して設定QMetaObject) であり、通知の変更はビューに伝達されます。また、いくつかのコンテナ ヘルパー (begin/end/iterator/size) もあるので、格納されている QObject を反復処理できます。
  • タイプセーフを提供するも持っています (主にet.al. をTypedObjectListModel<T>オーバーライドし、Tに対して行う新しいタイプを定義することによって)。push_backiteratorstatic_cast

オブジェクトのタイプが 1 つしかない場合、これはすべて非常にうまく機能します。新しいクラスを作成するだけです (f.ex.FruitsModelが含まれQ_OBJECT、継承しTypedObjectListModel<Fruit>ます。これには、Fruits または Fruit-subobjects のみを含めることができます。

ただし、2 つの異なる状態で実行できるアプリがあります。2 番目の状態では、モデルはリンゴのみを保持し、バナナ (具体的な基底クラスである果物) は保持しません。

ApplesModelしたがって、T の型を継承して変更する必要がある型を作成したいと思います。継承ダイヤモンドOF DEATHFruitsModelを取得するため、これにより問題が発生します。

 QObject
       |
 QAbstractListModel
       |
 ObjectListModel -------------------
       |                           |
 TypedObjectListModel<Fruit>     TypedObjectListModel<Apple>
       |                           |
 FruitsModel  -------------------ApplesModel

FruitsModel::push_back(Fruit*) は ApplesModel では違法であるため、これも概念的に間違っています。ただし、果物 (リンゴだけでなく) の読み取り/反復は可能である必要があります。

また、FruitsModel ( findFruitById) には、オーバーライドして ApplesModel でのみ Apple を返す必要がある関数がいくつかあります。

C++ でこの問題を解決する際に推奨される設計パターンは何ですか?

似たようなことを試みたのは私が初めてではないと思います (希望します)。

私は多くのアイデアを試しましたが、さまざまな行き止まりに行き詰まっています。ObjectListModel の仮想継承が問題を解決すると思うかもしれませんが、私はこれを使用して取得しQObject::findChildます:

error C2635: cannot convert a 'QObject*' to a 'ApplesModel*'; conversion from a virtual base class is implied

上記は、代わりに dynamic_cast を使用して、findChild の独自の実装で修正できますが、まだいくつかの行き止まりがあります。

template<typename T>
inline T myFindChild(const QObject *parent, const QString &name = QString())
{ 
    return dynamic_cast<T>(qt_qFindChild_helper(parent, name, reinterpret_cast<T>(0)->staticMetaObject)); 
}

アップデート

geekp には次の提案がありました。

Fruit から Apple を継承し、ApplesModel を気にしない

FruitsModel にリンゴだけが含まれるようにするにはどうすればよいでしょうか。また、リンゴを (フルーツとして) フェッチするたびにダウンキャストする必要があります。

FruitsModel から継承しないでください (そのメソッドを使用していないのに、なぜ継承するのでしょうか?)

私はいくつかの方法を使用していますが、特に読書用のものです。

Apple の TypesObjectListModel から継承せず、FruitsModel のみをサブクラス化します。

AppleModel を気にしないのと同じ欠点。

4

2 に答える 2

2

したがって、読み取り操作と書き込み操作は、継承に関して根本的に異なります。

OOP 101 に戻って、正方形と長方形のたとえ話を覚えていますか? 正方形は一種の長方形であるとよく言われますが、それは読書の場合にのみ当てはまります。

書くとき、正方形は長方形の一種ではありませんが、長方形は正方形の一種です!

すなわち:

bool test( Rectangle* r ) {
  int old_height = r->GetHeight();
  int old_width = r->GetWidth();
  r->SetWidth(old_width+100);
  return old_height == r->GetHeight();
}

true上記の関数はすべての「実際の」長方形を返しますが、Squaresそうでない場合もあります。したがって、その周りの契約はSetWidth、 にとって合理的でRectangleあり、 に対して違反されSquareます。

一方、Rectangle読み取り専用のすべてのインターフェイスは、 によって完全に処理されSquareます。

これにより、次のような混乱が生じる可能性があります。

struct IRectangleRead { ... };
struct ISquareRead { ... };

struct ISquareWrite: virtual ISquareRead { ... };
struct IRectangleWrite:ISquareWrite, virtual IRectangleRead { ... };

struct ConstRectangle: virtual IRectangleRead { ... };
struct ConstSquare: virtual ISquareRead, virtual IRectangleRead { ... };

struct Rectangle: ConstRectangle, IRectangleWrite { ... };
struct Square: ConstSquare, ISquareWrite { ... };

これは継承階層の混乱を引き起こしますが、制限的なコントラクトを各メソッドに配置でき、そのメソッドを実装するすべてのオブジェクトがそれらに従います。

ここで、オブジェクトが不変である場合、上記はとてつもなく簡単になることに注意してください。その場合、書き込みの唯一の形式はファクトリ関数を介することであり、物事は整頓されます。

ここでの具体的な教訓は、コードの読み取り部分と変更部分を分割することです。サブクラスの場合は操作が無効であるため、共通の変更部分 (基本クラスで機能する) は公開されていません。

共通読み取り部分は、サブタイプ読み取り部分と同様に公開されます。

サブタイプの書き込みコードは、プライベート共通基本クラスの書き込みコードに転送されます。

于 2013-01-18T15:31:22.897 に答える
0

複数の選択肢 :

  • Fruit から Apple を継承し、ApplesModel を気にしない
  • FruitsModel から継承しないでください (そのメソッドを使用していないのに、なぜ継承するのでしょうか?)
  • Apple の TypesObjectListModel から継承せず、FruitsModel のみをサブクラス化する
于 2013-01-18T15:17:44.370 に答える