#c #design-patterns #multiple-inheritance
#c #шаблоны проектирования #множественное наследование
Вопрос:
У меня есть несколько вариантов поведения, которые я хочу, чтобы у класса были. Я хотел бы изолировать эти варианты поведения, чтобы я мог повторно использовать этот код, смешивать и сопоставлять по своему усмотрению.
Например, способ сделать это мог бы быть:
class BehaviorAbstract {
protected:
virtual void processInfo(Info i) = 0;
}
class Behavior1: public BehaviorAbstract {
protected:
virtual void processInfo(Info i) { ... }
void performBehavior1() { ... }
}
class Behavior2: public BehaviorAbstract {
protected:
virtual void processInfo(Info i) { ... }
void performBehavior2() { ... }
}
class ConcreteObject: public Behavior1, Behavior2 {
protected:
void processInfo(Info i) {
// needs to call processInfo of Behavior1 and Behavior2
Behavior1::processInfo(i);
Behavior2::processInfo(i);
}
void perform() {
this->performBehavior1(); this->performBehavior2();
}
}
Итак, вот суть вопроса: ConcreteObject
необходимо вызвать 2 функции processInfo
(с одинаковым именем, с одинаковыми аргументами) всех классов, от которых она наследуется. Представьте, что все поведенческие классы кодируются разными разработчиками. Функция ДОЛЖНА иметь одинаковое имя, потому что все они являются производными от BehaviorAbstract.
Какой разумный шаблон проектирования для этого? Я подозреваю, что множественное наследование здесь может быть неправильным, и, возможно, «множественная композиция» была бы лучше, но мне нужны все Behavior
классы и ConcreteObject
производные от BehaviorAbstract
, и все они должны работать с одним и тем же защищенным элементом данных BehaviorAbstract
.
Решение, которое я написал выше, кажется неправильным и уродливым. Есть ли способ автоматически вызывать все родительские классы, которые реализуют ProcessInfo, без явной необходимости переписывать их имена?
Большое спасибо за помощь.
Комментарии:
1. предупреждение используйте виртуальное наследование (
class Behavior1: virtual public BehaviorAbstract {
и т.д.) При использовании множественного наследования, создавая ‘ромб’, добавьте отсутствующий ‘;’ и установите на минимуме методы, которые должны быть защищены , а не частными2. в вашем коде много синтаксических ошибок, и все ваши методы являются закрытыми, но как только я исправлю это, код, похоже, будет в порядке: wandbox.org/permlink/9fu554mXWXrPSzcV
3. спасибо, исправит проблемы
4. Поправьте меня, если я ошибаюсь, но я думаю, что вопрос заключается в рефакторинге
ConcreteObject
класса, чтобы избежать использования множественного наследования… Вы пробовали шаблоны? Черты или политика?5. Также я предлагаю вам прочитать о проблеме xy . Ваши вопросы сосредоточены на решении, вы уже поняли, что оно вам не нравится, но не совсем ясно, в чем заключается реальная проблема, которую вы пытаетесь решить
Ответ №1:
Если я правильно понял, то этот вопрос касается рефакторинга ConcreteObject
класса.
Подход # 1:
Если вы можете сделать performBehavior()
часть BehaviorAbstract
базового класса, то вы можете просто использовать вектор BehaviorAbstract*
и позволить полиморфизму делать свое дело. Я думаю, это можно рассматривать как шаблон стратегии.
#include <iostream>
#include <vector>
typedef int Info;
struct BehaviorAbstract
{
virtual void processInfo(Info i) = 0;
virtual void performBehavior() = 0;
};
struct Behavior1 : BehaviorAbstract
{
void processInfo(Info i) override
{ std::cout<< "Behavior1::processInfo()" <<std::endl; }
void performBehavior() override
{ std::cout<< "Behavior1::performBehavior()" <<std::endl; }
};
struct Behavior2 : BehaviorAbstract
{
void processInfo(Info i) override
{ std::cout<< "Behavior2::processInfo()" <<std::endl; }
void performBehavior() override
{ std::cout<< "Behavior2::performBehavior()" <<std::endl; }
};
//------------------------------------------------//
struct ConcreteObject
{
typedef std::vector<BehaviorAbstract*> vec_behavior;
vec_behavior vba;
ConcreteObject(vec_behavior amp;amp;v) : vba(v)
{;}
void processInfo(Info i)
{
for (auto amp;amp;itr : vba)
itr->processInfo(i);
}
void perform()
{
for (auto amp;amp;itr : vba)
itr->performBehavior();
}
};
int main()
{
ConcreteObject foo = {{new Behavior1(), new Behavior2()}};
foo.processInfo(23);
foo.perform();
}
Пример: https://rextester.com/UXR42210
Подход # 2:
Использование шаблона переменных, который создает кортеж. Выполните итерацию по этому кортежу и запустите функции. Опять же, если бы performBehavior1()
и performBehavior2()
могли использовать одно и то же имя функции, тогда это стало бы проще. Дополнительная сложность здесь заключается в том, что вам нужно написать ручной способ итерации по этому кортежу. Для простоты я вызвал processInfo()
непосредственно из iterate_tuple
структуры.
#include <iostream>
#include <tuple>
typedef int Info;
struct BehaviorAbstract
{
virtual void processInfo(Info i) = 0;
};
struct Behavior1 : BehaviorAbstract
{
void processInfo(Info i) override
{ std::cout<< "Behavior1::processInfo()" <<std::endl; }
void performBehavior1()
{ std::cout<< "Behavior1::performBehavior1()" <<std::endl; }
};
struct Behavior2 : BehaviorAbstract
{
void processInfo(Info i) override
{ std::cout<< "Behavior2::processInfo()" <<std::endl; }
void performBehavior2()
{ std::cout<< "Behavior2::performBehavior2()" <<std::endl; }
};
//------------------------------------------------//
template<typename T, std::size_t N>
struct iterate_tuple
{
static void run(T amp;t, Info i)
{
std::get<N>(t).processInfo(i);
iterate_tuple<T, N-1>::run(t,i);
}
};
template<typename T>
struct iterate_tuple<T, 0>
{
static void run(T amp;t, Info i)
{
std::get<0>(t).processInfo(i);
}
};
//------------------------------------------------//
template<typename ...T>
struct ConcreteObject
{
std::tuple<T ...> tmp;
static constexpr std::size_t tuple_size = std::tuple_size<decltype(tmp)>::value;
ConcreteObject() : tmp{std::forward<T>(T()) ...}
{;}
void processInfo(Info i)
{
iterate_tuple<decltype(tmp), tuple_size-1>::run(tmp, i);
}
void perform()
{
std::get<0>(tmp).performBehavior1();
std::get<1>(tmp).performBehavior2();
}
};
int main()
{
ConcreteObject<Behavior1,Behavior2> foo;
foo.processInfo(23);
foo.perform();
}
Пример: https://rextester.com/SBRE16218
Оба подхода позволяют избежать множественного наследования, которого, насколько я понял, вы хотите избежать. К вашему сведению, чем проще, тем лучше.
Комментарии:
1. Спасибо, это круто. Это действительно то, чего я добивался. Интересен шаблон стратегии. Насколько я понимаю, это просто шаблон композиции, в котором составляется несколько объектов. Существует ли авторитетный список шаблонов проектирования? Когда вы говорите «шаблон стратегии», это то, что говорится в нескольких книгах, или что-то общепризнанное в отрасли?
2. @DevShark: Безусловно! Я бы порекомендовал следующие книги по шаблонам проектирования: 1) Шаблоны проектирования — элементы многоразового объектно-ориентированного программного обеспечения Эриха Гаммы, Ричарда Хелма, Ральфа Джонсона, Джона Влиссидеса, 2) Шаблоны программирования игр Роберта Нистрома, 3) Шаблоны проектирования Head First Эрика Фримена, Элизабет Фримен. Последняя книга (3) написана на Java и не охватывает все известные шаблоны проектирования, но читать ее интересно, и она даст вам плавное введение в шаблоны проектирования. Первая книга (1) обязательно должна быть на вашей книжной полке. Это как словарь шаблонов проектирования.