Как определить контейнеры, которые наследуют друг друга, если содержащиеся объекты наследуют тоже? (С QObject в качестве базы)

Фон:

  • Мой класс называется ObjectListModel который наследует QAbstractListModel и содержит QObjectList, Объекты — это строки, а их свойства — столбцы (задается с помощью QMetaObject), а изменения уведомлений распространяются на представления. Также есть несколько контейнерных помощников (начало / конец / итератор / размер), так что я могу перебирать сохраненный объект QObject.
  • у меня тоже есть TypedObjectListModel<T>, что обеспечивает безопасность типов (в основном путем переопределения push_back и другие. с и определения нового iterator типы, которые делают static_cast к т).

Это все работает очень хорошо, когда у меня есть только один тип объектов. Я просто создаю новый класс (например, FruitsModel, у которого есть Q_OBJECT в нем и наследуется TypedObjectListModel<Fruit>, Это может содержать только Фрукты или Фрукты-подобъекты.

Однако теперь у меня есть приложение, которое может работать в двух разных состояниях. Во втором состоянии модель должна содержать только яблоки, а не бананы (или фрукты в этом отношении, который является конкретным базовым классом).

Итак, я хотел бы создать ApplesModel тип, который должен наследовать FruitsModel и просто изменить тип Т. Это доставляет мне неприятности, потому что я получаю алмаз наследства СМЕРТИ:

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

Это также концептуально неверно, поскольку FruitsModel :: push_back (Fruit *) недопустима в ApplesModel. Тем не менее, чтение / перебор фруктов (не только яблок) должно быть возможным.

Кроме того, у меня есть некоторые функции в FruitsModel (findFruitById), который должен быть переопределен и возвращать яблоки только в ApplesModel.

Какой шаблон проектирования предпочтителен при решении этой проблемы в C ++?

Я подозреваю (надеюсь), что я не первый, кто пытается сделать что-то подобное.

Я испробовал много идей, но застрял в разных тупиках. Можно подумать, что виртуальное наследование ObjectListModel решит проблему, но потом я получаю это, используя QObject::findChild:

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

Вышесказанное можно исправить с помощью моей собственной реализации findChild, используя вместо него dynamic_cast, но все еще есть некоторые тупики.

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 и не мешай с ApplesModel

Как мне тогда обеспечить, чтобы в FruitsModel были только яблоки? Также я
нужно опускать руки каждый раз, когда я принесу яблоко (как фрукт).

Не наследуйте от FruitsModel (зачем вам, если вы не используете его
методы?)

Я использую некоторые методы, особенно те, что для чтения.

Не наследуйте от TypesObjectListModel от Apple и подкласс только FruitsModel.

Те же недостатки, что и не заморачиваться с AppleModel.

2

Решение

Таким образом, операции чтения и записи принципиально отличаются в отношении наследования.

Возвращаясь к ООП 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 { ... };

который генерирует беспорядок в иерархии наследования, но тот, в котором ограничительные контракты могут быть размещены для каждого метода, и каждый объект, который реализует метод, будет подчиняться им.

Теперь вы должны заметить, что вышесказанное становится смехотворно легче, если ваши объекты неизменны. Тогда единственная форма записи — через фабричные функции, и все становится на свои места.

Итак, конкретный урок здесь — разделение частей вашего кода на чтение и изменение. Общая модифицирующая часть (которая работает с базовыми классами) не является общедоступной, поскольку в случаях с подклассами операция недопустима.

Общедоступная часть для чтения является общедоступной, как и часть для чтения подтипов.

Код написания подтипа пересылается в частный код написания общего базового класса.

2

Другие решения

Множественный выбор :

  • Наследуй яблоко от Fruit и не мешай с ApplesModel
  • Не наследуйте от FruitsModel (зачем, если вы не используете его методы?)
  • Не наследуйте от TypesObjectListModel от Apple и подкласс только FruitsModel
0

По вопросам рекламы [email protected]