Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.88/8: Рейтинг темы: голосов - 8, средняя оценка - 4.88
 Аватар для Usagi
0 / 0 / 3
Регистрация: 03.07.2016
Сообщений: 22

Расширенная версия интерфейса для потомка класса

09.08.2017, 13:44. Показов 1669. Ответов 10

Студворк — интернет-сервис помощи студентам
Всех приветствую!

Возник вопрос при проектировании некой иерархии классов.
К примеру, есть следующая небольшая иерархия классов контролёров для представлений:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class IController 
{
public:
    virtual void add(/*...*/) = 0;
    virtual void edit(/*...*/) = 0;
    // ... и другие методы
    virtual ~IController() = default; 
};
 
class StandartController : public IController
{
public:
    void add(/*...*/) override;
    void edit(/*...*/) override;
};
Стандартные представления выполняют операции с моделью: добавить строку, удалить строку(-ки), редактировать.
В процессе выяснилось, что нужно предусмотреть расширенную версию контролёра для расширенной версии представления. Расширенное представление будет принимать ссылки на стандартные модели и контролёры с целью оперировать их данными (поиск, сортировка, делегирование команд простым контролёрам для выполнения базовых функций и так далее).

C++
1
2
3
4
5
6
7
8
9
class ExtentedController : public IController
{
public:
    void add(/*...*/) override;
    void edit(/*...*/) override;
    virtual void find(/*...*/);
    virtual void sort(/*...*/);
    // ... и другие методы
};
Как теперь при наличии расширенного потомка единообразно использовать интерфейс? Решение с dynamic_cast мне не нравится. Что если появится ещё пару расширенных версий? Для них использоваться тоже dynamic_cast? Второй вариант был выделить расширенный класс в совершенно новую иерархию. Но опять же - для каждой расширенной версии делать свою иерархию? Скорее всего имеются некоторые проблемы с архитектурой, раз возникла эта проблема.

Стоить также отметить, что для производства контролёров используется фабрика, которая возвращает только указатель на интерфейс (IController*) в независимости от того, какой тип объекта потребовался клиенту.
Сигнатура принимающего метода представления выглядит следующим образом:
C++
1
void setController(IContoller* ctrl);
Интерфейс представления:

C++
1
2
3
4
5
6
7
8
9
10
class IView
{
public:
   IView() = delete;
   IView(/*...*/);
   ~IView() = default;
   //...
   void setController(IContoller* ctrl);
   //... другие методы
};
При проектировании использовал принцип "программирования на уровне интерфейсов", но когда стали появляться реализации "не такие как все", возникли проблемы с их использованием. Ведь все представление знают только об интерфейсе контролёра. Можно, конечно, в представлении сделать каст к нужному типу, но повторюсь, меня так смущает эта операция, что я всё же ищу другое решение.
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
09.08.2017, 13:44
Ответы с готовыми решениями:

Вызов специфических для потомка функций, не зная класса потомка
Доброго времени суток. Когда-то давно делал работу в C#, а сейчас захотел повторить то же в C++. Затык вот в чем. был у меня...

Реализовать оператор= для присваивания объекта класса-потомка объекту базового класса
Есть два класса A и B, причем класс B является потомком A. Как реализовать следующее: obj_A = obj_B и obj_B = obj_A? Добавлено через 9...

Вызов метода базового класса из класса-потомка
Нужно вызывать из метода, переопределенного в потомке, соответствующий метод базового класса. При этом они являются виртуальными. Вот...

10
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
09.08.2017, 14:44
Цитата Сообщение от Usagi Посмотреть сообщение
Решение с dynamic_cast мне не нравится.
"расширенный" интерфейс нужен "расширенному" потомку
и этот "расширенный" поток ожидает,
что от фабрики к нему придет его "расширенный" интерфейс.

просто дайте ему то, что он хочет.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
auto* const controller = factory::build(EXTENDED);
 
extView.setController(controller);
 
...
 
 
IExtentedView::setController(IContoller* ctrl)
{
    #ifdef NDEBUG
        this->m_controller = static_cast<ExtentedController*>(ctrl);
    #else
        // для дебага на всякий случай проверим,
        // что расширенной вьюхе прислали расширенный контроллер
        this->m_controller = dynamic_cast<ExtentedController*>(ctrl);
    #endif
    assert(this->m_controller);
 
    // теперь расширенная вьюха 
    // может спокойно пользоваться 
    // расширенным функционалом 
    // расширенного интерфейса
 
    // без всяких кастов.
}
0
2393 / 1921 / 763
Регистрация: 27.07.2012
Сообщений: 5,562
09.08.2017, 15:04
Лучший ответ Сообщение было отмечено Usagi как решение

Решение

Цитата Сообщение от Usagi Посмотреть сообщение
Скорее всего имеются некоторые проблемы с архитектурой, раз возникла эта проблема.
Именно. Если поведение производных классов не удаётся выразить через поведение базового, то, скорее всего, что-то уже пошло не так. Возможно тут наследование несколько неуместно, хоть и кажется логичным. Тут нужно думать.

Цитата Сообщение от Usagi Посмотреть сообщение
Как теперь при наличии расширенного потомка единообразно использовать интерфейс?
Можно попробовать шаблон "Наблюдатель" (или "Посетитель", "Visitor"). Погуглите. Там фишка в том, что в иерархии классов есть лишь одна виртуальная функция, а также есть иерархия наблюдателе, каждый под свою задачу, для которых определены виртуальные функции, принимающие каждый из производных классов "наблюдаемой" иерархии, выполняющие определённые действия или же сообщающие, что эти действия для данного класса бессмысленны.

Добавлено через 8 минут
Как пример:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class visitor;
 
class bird
{
public:
    void accept(visitor & v) = 0;
};
 
class eagle: public bird
{
public:
    void accept(visitor & v) { v.visit(*this); } 
 
    void fly() { }
    void eat() { }
};
 
class penguin: public bird
{
public:
    void accept(visitor & v) { v.visit(*this); }
 
    void eat() { }
};
 
// -------------
class visitor
{
public:
    virtual void visit(eagle & e) = 0;
    virtual void visit(penguin & p) = 0;
};
 
class eat: public visitor
{
public:
    void visit(eagle & e) { e.eat(); }
    void visit(penguin & p) { p.eat(); }
};
 
class fly: public visitor
{
public:
    void visit(eagle & e) { e.fly(); }
    void visit(pengiun & p) { throw "penguins cannot fly!"; }
};
1
 Аватар для Usagi
0 / 0 / 3
Регистрация: 03.07.2016
Сообщений: 22
10.08.2017, 14:13  [ТС]
Цитата Сообщение от hoggy
просто дайте ему то, что он хочет.
Всё равно придётся использовать приведение. static_cast, конечно, меньшее зло, но всё же...

Цитата Сообщение от John Prick
Можно попробовать шаблон "Наблюдатель" (или "Посетитель", "Visitor").
Просмотрел информацию по паттерну "Наблюдатель", примеры, применение. Да, действительно, паттерн решает поставленную задачу. Попробовал реализовать - всё получилось. Однако тут возник вопрос "дублирования" кода. Методы add и edit одинаковы для всех реализаций контролёра и нецелесообразно их определять в каждом контролёре отдельно. Возможно, можно сделать вот так?
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class IVisitor;
 
class IController
{
public:
    IController() = default;
    virtual ~IController() = default;
    virtual void add() { std::cout << "Standart behavior\n";  }
    virtual void edit() { std::cout << "Standart behavior\n"; }
    virtual void accept(IVisitor* ) = 0;
};
 
class StandartController : public IController
{
public:
    void accept(IVisitor* ) override;
};
 
class ExtentedContoller : public IController
{
public:
    void find() { std::cout << "ExtentedContoller: void find()\n"; }
 
    void accept(IVisitor* ) override;
};
Есть какие-нибудь отрицательно аспекты применения, кроме увеличения количества классов в системе?
0
2393 / 1921 / 763
Регистрация: 27.07.2012
Сообщений: 5,562
10.08.2017, 14:41
Цитата Сообщение от Usagi Посмотреть сообщение
Есть какие-нибудь отрицательно аспекты применения, кроме увеличения количества классов в системе?
Когда добавляется новый класс в иерархию, нужно не забыть добавить его во все наблюдатели. На всякий случай в базовый класс наблюдателя можно добавить функцию-заглушку, которая будет сообщать о неизвестном классе. Хотя такой код бывает довольно непросто сопровождать, когда иерархия наблюдателей разрастается. В принципе этот шаблон не самая удобная вещь, но кое-где своё применение находит.
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
10.08.2017, 14:51
Лучший ответ Сообщение было отмечено Usagi как решение

Решение

Цитата Сообщение от Usagi Посмотреть сообщение
Всё равно придётся использовать приведение.
вы хотите подсунуть указатель на самого дальнего предка
но использовать его как одного из наследников.

вам не кажется, что из самой постановки задачи прямо проистекает,
что придется делать каст, так или иначе?

и здесь вы можете:

1.
использовать static_cast,
который выполняет преобразование в статике.
то бишь времени компиляции.
то бишь без издержек в рантайме.

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

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

2.
использовать dynamic_cast
который в общем то и не нужен.
кроме защиты от ошибки-которой в принципе быть не может".

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

по сути - тот же каст,
только через одно место кучку посредников,
с сопутствующей кучкой виртуальных функций.

Цитата Сообщение от Usagi Посмотреть сообщение
Есть какие-нибудь отрицательно аспекты применения, кроме увеличения количества классов в системе?
есть такой аспект.
его в простонародье ещё называют "ооп-головного мозга".
болезнь характеризуется неоправданным
увеличением количества классов.
1
 Аватар для Usagi
0 / 0 / 3
Регистрация: 03.07.2016
Сообщений: 22
10.08.2017, 15:19  [ТС]
Да, я читал что данный паттерн не стоит применять, если имеет место частые добавления классов в иерархию. Впрочем, структура контроллеров должна быть достаточно стабильна после окончания процесса проектирования.
Может быть, есть ещё методы решения поставленной задачи? На данный момент для меня подходит использовать посетителя.

P.S. думал над первым паттерном, который Вы написали - наблюдатель. Так и не понял, как его можно применить в данном случае.

Добавлено через 17 минут
Цитата Сообщение от hoggy
есть такой аспект.
его в простонародье ещё называют "ооп-головного мозга".
Соглашусь :-) Но ведь они используются где попало не от того что "очень хочется". Скорее, от нехватки практических навыков применения. Я стараюсь особо не использовать паттерны без острой на то необходимости... а не выйдет ли боком этот каст при сопровождении кода? Я понимаю, что никаких затрат в ран-тайме я не понесу, но меня не покидает эта мысль, что касты - зло. Ведь неспроста же так говорят.
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
10.08.2017, 17:09
Цитата Сообщение от Usagi Посмотреть сообщение
а не выйдет ли боком этот каст при сопровождении кода?
как вы это себе представляете?

Цитата Сообщение от Usagi Посмотреть сообщение
касты - зло. Ведь неспроста же так говорят.
спросите у тех, кто так говорит.

с высокой степенью вероятности окажется,
что они имели ввиду именно dynamic_cast

статический каст используется повсеместно.
это - нормальная практика.
0
Каждому свое
 Аватар для Bretbas
533 / 219 / 81
Регистрация: 05.08.2013
Сообщений: 1,614
10.08.2017, 18:12
Контроллеры находятся в фабрике? Можно попробовать распределить контроллеры по типу. И получать их через перегруженные методы. Выйдет что-то типа такого:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ControllerFactory
{
public:
    template<typename T, typename std :: enable_if<std :: base_of<ExtentedController, T> :: value, T> :: type* = nullptr>
    void getController()
    {
        ... // Поиск контроллера в векторе _extentedControllers
        return controller;
    }
    template<typename T, typename std :: enable_if<std :: base_of<SuperController, T> :: value, T> :: type* = nullptr>
    void getController()
    {
        ... // Поиск контроллера в векторе _superControllers
        return controller;
    }
...
private:
    std :: vector<SuperController*> _superControllers;
    std :: vector<ExtentedController*> _extentedController;
...
};
Что-то вроде такого будет. Можно также наследовать от этой фабрики, если появятся новые типы контроллеров.
Кстати, чтобы их распределить по векторам, приведения также не понадобятся
0
 Аватар для Usagi
0 / 0 / 3
Регистрация: 03.07.2016
Сообщений: 22
11.08.2017, 15:34  [ТС]
Цитата Сообщение от hoggy
с высокой степенью вероятности окажется,
что они имели ввиду именно dynamic_cast
Да, верно, речь идёт о dynamic_cast. Значит вопросов по static_cast нет.

Цитата Сообщение от Bretbas
Можно попробовать распределить контроллеры по типу. И получать их через перегруженные методы.
Методы фабрик обычных и расширенных контроллеров получают различное число параметров. Добавлять все вариации методов в общий интерфейс, учитывая все возможные сигнатуры, не очень хорошо.
0
Каждому свое
 Аватар для Bretbas
533 / 219 / 81
Регистрация: 05.08.2013
Сообщений: 1,614
11.08.2017, 17:10
Usagi,
Цитата Сообщение от Usagi Посмотреть сообщение
Методы фабрик обычных и расширенных контроллеров получают различное число параметров.
Это не проблема, так как есть variadic template( Шаблоны с переменным числом параметров).

К примеру, если вы в фабрике создаете и автоматически добавляете в эту фабрику контроллер типа T, и у типа T есть конструктор с неизвестным числом и типами параметров, то это будет выглядеть примерно так:
C++
1
2
3
4
5
6
7
8
9
10
11
...
template<typename T, typename... Arg, typename std :: enable_if<std :: base_of<ExtentedController, T> :: value, T> :: type* = nullptr>
T* addController( Args... arg )
{
    ...
    T* controller = new T( std :: forward<Args>( arg )... );
    ...
    _extentedController.push_back( controller );
 
}
...
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
11.08.2017, 17:10
Помогаю со студенческими работами здесь

Не работает вызов виртуальной функции из класса потомка
Есть код: #include &lt;iostream&gt; #include &lt;vector&gt; #include &lt;list&gt; #include &lt;algorithm&gt; #include &lt;string&gt; #include &lt;sstream&gt; ...

Доступ к private переменной класса из его потомка
Здравствуйте! Как получить доступ к закрытой переменной родительского класса в методе наследника? #include &lt;stdio.h&gt; ...

Не получается вызвать метод класса потомка через ссылку
class Book { protected: char type; char title; char ISBN; char edition; char circulation; char...

Шаблон родительского класса и шаблон класса потомка
Запутался, как правильно пронаследоваться от шаблона класса? #include &lt;iostream&gt; #include &lt;cmath&gt; using namespace std; ...

Угадай число, расширенная версия
Как у многих, кто начинает учится Java есть задание написать игру угадай число. Игру написал, хотелось бы расширить функционал. Если...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
11
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
SDL3 для Web (WebAssembly): Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 12.02.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами и вызывать обработчики событий столкновения. . . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 11.02.2026
Содержание блога Библиотека SDL3 содержит встроенные инструменты для базовой работы с изображениями - без использования библиотеки SDL3_image. Пошагово создадим проект для загрузки изображения. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru