Наследование - это второе по силе (больше связывание) отношение в C ++, которому предшествует только дружба. Если вы сможете изменить дизайн, используя только композицию, ваш код будет более слабосвязанным. Если вы не можете, то вам следует подумать, действительно ли все ваши классы наследуются от базы. Это связано с реализацией или просто с интерфейсом? Вы хотите использовать какой-либо элемент иерархии в качестве базового элемента? Или это просто листья в вашей иерархии, которые являются реальными действиями? Если только листья являются действиями, и вы добавляете поведение, вы можете рассмотреть дизайн на основе политик для этого типа композиции поведения.
Идея состоит в том, что различные (ортогональные) поведения могут быть определены в небольших наборах классов и затем объединены вместе, чтобы обеспечить реальное полное поведение. В этом примере я рассмотрю только одну политику, которая определяет, будет ли действие выполняться сейчас или в будущем, и команду для выполнения.
Я предоставляю абстрактный класс, чтобы различные экземпляры шаблона могли храниться (через указатели) в контейнере или передаваться в функции в качестве аргументов и вызываться полиморфно.
class ActionDelayPolicy_NoWait;
class ActionBase // Only needed if you want to use polymorphically different actions
{
public:
virtual ~Action() {}
virtual void run() = 0;
};
template < typename Command, typename DelayPolicy = ActionDelayPolicy_NoWait >
class Action : public DelayPolicy, public Command
{
public:
virtual run() {
DelayPolicy::wait(); // inherit wait from DelayPolicy
Command::execute(); // inherit command to execute
}
};
// Real executed code can be written once (for each action to execute)
class CommandSalute
{
public:
void execute() { std::cout << "Hi!" << std::endl; }
};
class CommandSmile
{
public:
void execute() { std::cout << ":)" << std::endl; }
};
// And waiting behaviors can be defined separatedly:
class ActionDelayPolicy_NoWait
{
public:
void wait() const {}
};
// Note that as Action inherits from the policy, the public methods (if required)
// will be publicly available at the place of instantiation
class ActionDelayPolicy_WaitSeconds
{
public:
ActionDelayPolicy_WaitSeconds() : seconds_( 0 ) {}
void wait() const { sleep( seconds_ ); }
void wait_period( int seconds ) { seconds_ = seconds; }
int wait_period() const { return seconds_; }
private:
int seconds_;
};
// Polimorphically execute the action
void execute_action( Action& action )
{
action.run();
}
// Now the usage:
int main()
{
Action< CommandSalute > salute_now;
execute_action( salute_now );
Action< CommandSmile, ActionDelayPolicy_WaitSeconds > smile_later;
smile_later.wait_period( 100 ); // Accessible from the wait policy through inheritance
execute_action( smile_later );
}
Использование наследования позволяет публичным методам из реализаций политики быть доступными через создание экземпляра шаблона. Это запрещает использование агрегации для объединения политик, поскольку новые члены функции не могут быть вставлены в интерфейс класса. В этом примере шаблон зависит от политики, имеющей метод wait (), который является общим для всех политик ожидания. Теперь для ожидания периода времени необходим фиксированный период времени, который устанавливается с помощью метода period () public.
В этом примере политика NoWait - это просто конкретный пример политики WaitSeconds с периодом, установленным на 0. Это было сделано специально для того, чтобы отметить, что интерфейс политики не обязательно должен быть таким же. Другая реализация политики ожидания могла бы ожидать в течение нескольких миллисекунд, тактов или до некоторого внешнего события, предоставляя класс, который регистрируется как обратный вызов для данного события.
Если вам не нужен полиморфизм, вы можете взять из примера базовый класс и виртуальные методы в целом. Хотя это может показаться слишком сложным для текущего примера, вы можете принять решение о добавлении других политик в набор.
Хотя добавление новых ортогональных поведений подразумевает экспоненциальный рост числа классов, если используется простое наследование (с полиморфизмом), при таком подходе вы можете просто реализовать каждую отдельную часть отдельно и склеить ее вместе в шаблоне Action.
Например, вы можете сделать свое действие периодическим и добавить политику выхода, которая определяет, когда выходить из периодического цикла. Первые варианты, которые приходят на ум, это LoopPolicy_NRuns и LoopPolicy_TimeSpan, LoopPolicy_Until. Этот метод политики (в моем случае exit ()) вызывается один раз для каждого цикла. Первая реализация подсчитывает количество раз, когда она была названа выходами после фиксированного числа (фиксированного пользователем, так как период был зафиксирован в примере выше). Вторая реализация будет периодически запускать процесс в течение заданного периода времени, тогда как последняя будет запускать этот процесс до указанного времени (часов).
Если вы до сих пор следите за мной, я действительно внесу некоторые изменения. Во-первых, вместо использования параметра шаблона Command, который реализует метод execute (), я бы использовал функторы и, вероятно, шаблонный конструктор, который принимает команду для выполнения в качестве параметра. Смысл в том, что это сделает его гораздо более расширяемым в сочетании с другими библиотеками, такими как boost :: bind или boost :: lambda, поскольку в этом случае команды могут быть привязаны в точке создания к любой свободной функции, функтору или методу-члену. класса.
Теперь я должен идти, но если вам интересно, я могу попробовать опубликовать модифицированную версию.