Это неожиданная проблема, с которой я столкнулся. Я пишу приложение с графическим интерфейсом и использую два класса из библиотеки графического интерфейса: класс Model и класс View. Содержимое модели визуализируется классом View на экране. В какой-то момент я решил вывести класс Model, потому что мне нужна была расширенная функциональность. Классы библиотеки должны быть производными, и я нашел много примеров. Это было легко и работало отлично.
Теперь есть проблема: класс Model предлагает методы для прямого редактирования данных модели. Я не хочу, чтобы эти методы были общедоступными, потому что я написал обертки, и эти обертки должны быть единственным способом редактирования. Мне нужно наследование, потому что моя производная MyModel переопределяет многие виртуальные методы класса Model. Так что я думал, что делать. Вот резюме со всеми подробностями:
- Существует класс BasicModel, который предлагает методы для итерации данных модели.
- Существует класс Model, который наследует BasicModel и расширяет его, также предлагая методы для редактирования данных (я думаю, что BaseModel является абстрактным и не имеет данных, а Model определяет внутренние структуры данных и реализует интерфейс итерации, предлагаемый BasicModel).
- Есть класс MyModel, который наследует Model. Он переопределяет многие виртуальные методы, имеет расширенный механизм редактирования и хочет скрыть методы редактирования более низкого уровня, предлагаемые моделью.
- Существует класс View, в котором хранится указатель на объект BasicModel. Таким образом, представление использует только интерфейс итерации и даже не знает о каких-либо интерфейсах редактирования.
Итак, я хочу оставить интерфейс итерации MyModel общедоступным, чтобы я мог передать его моему объекту View, но скрыть интерфейс редактирования, предлагаемый Model.
Решения, о которых я подумал:
Решение 1. Нельзя избежать наследования, потому что мне приходится переопределять виртуальные методы, но само решение не должно использовать наследование: я могу написать оболочку для класса MyModel, которая предлагает доступ к константная ссылка на инкапсулированный объект MyModel и предлагает оболочки для механизма редактирования MyModel. Это может быть уродливо из-за всех оберток, но позволяет избежать путаницы с множественным наследованием и спецификаторами доступа.
Решение 2. Пусть MyModel наследует Model. Отсутствие множественного наследования. Теперь перейдите к определению класса MyModel в MyModel.hpp и напишите объявления всех методов редактирования модели в разделах protected
и private
, чтобы они были скрыты. Это работает очень хорошо, но есть одна небольшая проблема с обслуживанием: если в будущих версиях библиотеки интерфейс модели изменится, например. добавлен новый метод редактирования, нам придется добавить его вручную в класс MyModel как закрытый/защищенный метод. Конечно, я могу отслеживать изменения в библиотеке или, по крайней мере, перейти к ее онлайн-справке по API и взглянуть на справочную страницу класса Model, просто чтобы убедиться, что ничего не изменилось, или обновить свой код, если это необходимо, перед рабочей/стабильной версией. мое приложение выпущено.
Решение 3. Используйте множественное наследование, и тогда я понятия не имею, что должно произойти. Это безопасно или нет. Зависит ли компилятор поведения или нет. Идея такова: MyModel наследует от Model и от basicModel: public
наследование от BasicModel (для интерфейса итерации) и protected/private
наследование от Model (чтобы скрыть интерфейс редактирования Model).
Примечания:
Примечание 1. Мой высокоуровневый механизм редактирования использует низкоуровневые методы редактирования Model.
Примечание 2. Виртуальные методы, которые MyModel переопределяет, некоторые из них определены в BasicModel (таким образом, также унаследованы от Model), а некоторые не существуют в BasicModel и определяются Model (например, методы, связанные с перетаскиванием). n-капля).
Примечание 3. Библиотека GUI, которую я использую, называется gtkmm, привязка C++ к GTK+, а классы, о которых я говорю, — Gtk:: TreeModel, Gtk::TreeStore, Gtk::TreeView и мой собственный класс MyModel, производный от Gtk::TreeStore. Я проигнорировал эти имена, потому что это вопрос общего планирования ООП, но я упоминаю здесь реальные классы, чтобы людям, которые с ними знакомы, было легче понять проблему.
Я не уверен, что здесь будет лучший дизайн. Определенно, решение 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()
должен быть закрытым из-за частного наследования, поэтому я не понимаю, почему он не может скомпилироваться. Итог, если предположить, что это не ошибка, а просто я не понимаю, как все работает, - предлагаемое решение множественного наследования невозможно. Единственный способ решить эту проблему, если я не ошибаюсь, это виртуальное наследование, но я не могу его использовать, потому что классы, которые я использую, являются библиотечными классами и они не используют виртуальное наследование. И даже в этом случае, поскольку я использую частное и публичное наследование вместе, это может не решить проблему и по-прежнему вызывать неоднозначную реакцию.
РЕДАКТИРОВАТЬ: я попытался сделать Derived и MyClass практически производными от Base, и это полностью решает проблему. Но в моем случае я не могу изменить библиотечные классы, так что это не вариант.