Наследование является вторым самым сильным (больше связи) отношения в 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 );
}
использование наследования позволяет открытым методам от реализаций политики быть доступными посредством шаблонного инстанцирования. Это запрещает использование агрегирования для объединения политик, поскольку никакие новые функциональные участники не могли быть продвинуты в интерфейс класса. В примере шаблон зависит от подождавшей политики () методика, которая характерна для всех политик ожидания. Теперь ожидание какое-то время периоду требуется время установленного срока, которое установлено в течение периода () открытый метод.
В примере, политика NoWait является просто конкретным примером политики WaitSeconds с набором периода к 0. Это было намеренным для маркировки этого, интерфейс политики не должен быть тем же. Другая реализация политики ожидания могла ожидать в ряде миллисекунд, тактов системных часов, или до некоторого внешнего события, путем обеспечения класса, который регистрируется как обратный вызов для данного события.
, Если Вам не нужен полиморфизм, можно посещать из примера базовый урок и виртуальные методы в целом. В то время как это может казаться чрезмерно сложным для текущего примера, можно выбрать добавление других политик к соединению.
При добавлении новых ортогональных поведений подразумевал бы экспоненциальный рост в количестве классов, если простое наследование используется (с полиморфизмом), с этим подходом можно просто реализовать каждую другую часть отдельно и склеить его в шаблоне Action.
, Например, Вы могли сделать свое действие периодическим и добавить политику выхода, которые определяют, когда выйти из периодического цикла. Первые опции, которые приходят на ум, являются LoopPolicy_NRuns и LoopPolicy_TimeSpan, LoopPolicy_Until. Этот метод политики (выход () в моем случае) называют однажды для каждого цикла. Первая реализация считает количество раз, это назвали выходами после постоянного числа (зафиксированный пользователем, поскольку период был зафиксирован в примере выше). Вторая реализация периодически выполняла бы процесс в течение данного периода времени, в то время как последний выполнит этот процесс до данного времени (часы).
, Если бы Вы все еще следуете за мной до сих пор, я действительно внес бы некоторые изменения. Первый - то, что вместо того, чтобы использовать шаблонную Команду параметра, которая реализует метод, выполняются (), я использовал бы функторы и вероятно шаблонного конструктора, который принимает управление для выполнения как параметр. Объяснение - то, что это сделает его намного более расширяемым в сочетании с другими библиотеками как повышение:: свяжите или повысьте:: лямбда, так как в этом случае команды могли быть связаны при инстанцировании с любой бесплатной функцией, функтором или членским методом класса.
Теперь я должен пойти, но если Вам интересно, я могу попытаться отправить измененную версию.
Существует различие качества дизайна между ориентированным на реализацию ромбовидным наследованием, где реализация наследована (опасное), и ориентированное на выделение подтипов наследование, где интерфейсы или интерфейсы маркера наследованы (часто полезный).
Обычно, если можно избежать первого, Вы более обеспечены с тех пор где-нибудь по линии, точный вызываемый метод может вызвать проблемы, и важность виртуальных оснований, состояний, и т.д., начинает иметь значение. На самом деле Java не позволил бы Вам вытягивать что-то как этот, он поддерживает только интерфейсную иерархию.
я думаю, что "самый чистый" дизайн, можно предстать перед этим, должен эффективно повернуть все классы в ромбе в ложные интерфейсы (при наличии никакой информации состояния и наличии чистых виртуальных методов). Это уменьшает влияние неоднозначности. И конечно, можно использовать несколько и даже ромбовидное наследование для этого точно так же, как Вы использовали бы реализации в Java.
Затем имейте ряд конкретных реализаций этих интерфейсов, которые могут быть реализованы по-разному (Например, агрегирование, даже наследование).
Инкапсулируют эту платформу так, чтобы внешние клиенты только получили интерфейсы и никогда не взаимодействовали непосредственно с конкретными типами и удостоверились, что полностью протестировали Ваши реализации.
, Конечно, это - большая работа, но если Вы пишете центральный и допускающий повторное использование API, это могло бы быть Вашим лучшим выбором.
Я столкнулся с этой проблемой только на этой неделе и нашел статью о DDJ, который объяснил проблемы и когда Вы должны или не должны быть обеспокоены ими.Вот:
"Ромбы" в иерархии наследования интерфейсов довольно безопасны - это - наследование кода что get's Вы в горячую воду.
Для получения повторного использования кода я советую Вам рассматривать mixins (Google для C++ Mixins, если Вы незнакомы с tequnique). При использовании mixins Вы чувствуете, что можно "пойти по магазинам" для фрагментов кода, что необходимо реализовать Вас класс, не используя множественное наследование классов с сохранением информации.
Так, шаблон - множественное наследование интерфейсов и единственная цепочка mixins (предоставление Вам повторное использование кода), чтобы помочь реализовать реальный класс.
Hope, которая помогает!
С первым примером.....
, должен ли ActionRead ActionWrite быть подклассами действия вообще.
, так как Вы собираетесь закончить с одним реальным классом, который будет действием так или иначе, Вы могли просто наследовать actionread и actionwrite без них являющийся действиями в себе.
, хотя, Вы могли изобрести код, который потребует, чтобы они были действиями. Но в целом я попытался бы разделить Действие, Чтение, Запись и Задержку, и просто реальный класс смешивает все это вместе
Со знанием больше того, что Вы делаете, я, вероятно, реорганизовал бы вещи немного. Вместо нескольких наследование со всеми этими версиями действия я сделал бы полиморфное чтение и запись и запись классов, instanciated как делегаты.
Что-то как следующее (который не имеет никакого ромбовидного наследования):
Здесь я представляю один из многих способов реализовать дополнительную Задержку и предполагаю, что методология задержки является тем же для всех читателей. каждый подкласс мог бы иметь их собственную реализацию задержки, в этом случае, Вы передадите Read и экземпляру соответствующего полученного класса Задержки.
class Action // abstract
{
// Reader and writer would be abstract classes (if not interfaces)
// from which you would derive to implement the specific
// read and write protocols.
class Reader // abstract
{
Class Delay {...};
Delay *optional_delay; // NULL when no delay
Reader (bool with_delay)
: optional_delay(with_delay ? new Delay() : NULL)
{};
....
};
class Writer {... }; // abstract
Reader *reader; // may be NULL if not a reader
Writer *writer; // may be NULL if not a writer
Action (Reader *_reader, Writer *_writer)
: reader(_reader)
, writer(_writer)
{};
void read()
{ if (reader) reader->read(); }
void write()
{ if (writer) writer->write(); }
};
Class Flow : public Action
{
// Here you would likely have enhanced version
// of read and write specific that implements Flow behaviour
// That would be comment to FlowA and FlowB
class Reader : public Action::Reader {...}
class Writer : public Action::Writer {...}
// for Reader and W
Flow (Reader *_reader, Writer *_writer)
: Action(_reader,_writer)
, writer(_writer)
{};
};
class FlowA :public Flow // concrete
{
class Reader : public Flow::Reader {...} // concrete
// The full implementation for reading A flows
// Apparently flow A has no write ability
FlowA(bool with_delay)
: Flow (new FlowA::Reader(with_delay),NULL) // NULL indicates is not a writer
{};
};
class FlowB : public Flow // concrete
{
class Reader : public Flow::Reader {...} // concrete
// The full implementation for reading B flows
// Apparently flow B has no write ability
FlowB(bool with_delay)
: Flow (new FlowB::Reader(with_delay),NULL) // NULL indicates is not a writer
{};
};
Для случая 2, OneCommand
не просто особый случай CompositeCommand
? Если Вы устраняете OneCommand
и позволяете CompositeCommand
с только иметь один элемент, я думаю, что Ваш дизайн становится более простым:
CommandAbstraction
/ \
/ \
/ \
ModernCommand CompositeCommand
\ /
\ /
\ /
ModernCompositeCommand
у Вас все еще есть страшный ромб, но я думаю, что это может быть приемлемым случаем для него.