Фон:
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.
Таким образом, операции чтения и записи принципиально отличаются в отношении наследования.
Возвращаясь к ООП 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 { ... };
который генерирует беспорядок в иерархии наследования, но тот, в котором ограничительные контракты могут быть размещены для каждого метода, и каждый объект, который реализует метод, будет подчиняться им.
Теперь вы должны заметить, что вышесказанное становится смехотворно легче, если ваши объекты неизменны. Тогда единственная форма записи — через фабричные функции, и все становится на свои места.
Итак, конкретный урок здесь — разделение частей вашего кода на чтение и изменение. Общая модифицирующая часть (которая работает с базовыми классами) не является общедоступной, поскольку в случаях с подклассами операция недопустима.
Общедоступная часть для чтения является общедоступной, как и часть для чтения подтипов.
Код написания подтипа пересылается в частный код написания общего базового класса.
Множественный выбор :