钻石继承(C ++) [英] Diamond inheritance (C++)

查看:205
本文介绍了钻石继承(C ++)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道有钻石遗传被认为是不好的做法。然而,我有2个例子,我觉得钻石继承可以很好地适应。我想问,你会建议我在这些情况下使用钻石继承,还是有另一种设计可以更好。



案例1: strong>我想在我的系统中创建代表不同类型的动作的类。操作按几个参数分类:




  • 操作可以是读取或写入。

  • 动作可以延迟或没有延迟(它不只是1个参数,它会显着改变行为)。

  • 动作的流类型可以是FlowA或FlowB。



我打算进行以下设计:

  //抽象类
类Action
{
//与所有操作相关的方法
};
class ActionRead:public virtual Action
{
//与读取相关的方法
};
class ActionWrite:public virtual Action
{
//与编写相关的方法
};
class ActionWithDelay:public virtual Action
{
//与延迟定义和处理相关的方法
};
class ActionNoDelay:public virtual Action {/*...*/};
class ActionFlowA:public virtual Action {/*...*/};
class ActionFlowB:public virtual Action {/*...*/};

//具体类
类ActionFlowAReadWithDelay:public ActionFlowA,public ActionRead,public ActionWithDelay
{
//读取命令的完整流程的实现
};
class ActionFlowBReadWithDelay:public ActionFlowB,public ActionRead,public ActionWithDelay {/*...*/};
// ...



当然,我将遵守没有2个动作

我在系统中实现了一个命令的复合设计模式。命令可以被读取,写入,删除等。我还希望具有命令序列,其也可以被读取,写入,删除等。命令序列可以包含其他命令序列。



所以我有以下设计:

  class CommandAbstraction 
{
CommandAbstraction(){};
〜CommandAbstraction()= 0;
void Read()= 0;
void Write()= 0;
void Restore()= 0;
bool IsWritten(){/ * implemented * /};
//和其他实现的函数
};

类OneCommand:public virtual CommandAbstraction
{
//实现读,写,还原
};

class CompositeCommand:public virtual CommandAbstraction
{
//实现读,写,还原
};

此外,我有一个特殊类型的命令,现代命令。一个命令和复合命令都可以是现代的。 现代向一个命令和复合命令添加了一定的属性列表(大部分属性都是相同的)。我想能够持有一个指向CommandAbstraction的指针,并根据需要的命令类型初始化它(通过new)。所以我想做以下设计(除了上述):

  class ModernCommand public virtual CommandAbstraction 
{
〜ModernCommand()= 0;
void SetModernPropertyA(){/*...*/}
void ExecModernSomething(){/*...*/}
void ModernSomethingElse()= 0;

};
class OneModernCommand:public OneCommand,public ModernCommand
{
void ModernSomethingElse(){/*...*/};
// ...一些特定于OneModernCommand的方法
};
class CompositeModernCommand:public CompositeCommand,public ModernCommand
{
void ModernSomethingElse(){/*...*/};
// ...特定于CompositeModernCommand的几种方法
};

再次,我将确保没有2个继承CommandAbstraction类的类将实现相同的方法。 / p>

谢谢。

解决方案

继承是第二强)关系在C + +,前面只有友谊。如果你可以重新设计成只使用组合你的代码将更松散耦合。如果你不能,那么你应该考虑你的所有类是否应该真正从基础继承。是由于实现还是只是一个接口?您要使用层次结构的任何元素作为基本元素吗?或者只是你的层次结构中的叶子是真正的动作?如果只有叶子是动作,并且您正在添加行为,您可以考虑基于策略的设计,用于这种类型的行为组成。



这个想法是可以在小类集合中定义不同的(正交)行为,然后捆绑在一起以提供真正完整的行为。在该示例中,我将仅考虑一个策略,该策略定义操作是现在还是将来执行,以及要执行的命令。



我提供一个抽象类,以便模板的不同实例化可以存储(通过指针)在容器中或传递到函数作为参数,并被称为polimorphically。 p>

  class ActionDelayPolicy_NoWait; 

class ActionBase //只有当你想使用polimorphically不同的动作时才需要
{
public:
virtual〜Action(){}
virtual void run()= 0;
};

template< 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
}
};

//真正执行的代码可以写一次(对于每个动作执行)
class CommandSalute
{
public:
void execute(){ std :: cout<< 嗨! << std :: endl; }
};

class CommandSmile
{
public:
void execute(){std :: cout< :)< std :: endl; }
};

//等待行为可以单独定义:
class ActionDelayPolicy_NoWait
{
public:
void wait()const {}
};

//注意,当Action继承策略时,公共方法(如果需要)
//将在实例化的地方公开获得
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_;
};

//定向执行操作
void execute_action(Action& action)
{
action.run();
}

//现在的用法:
int main()
{
Action< CommandSalute> salute_now;
execute_action(salute_now);

Action< CommandSmile,ActionDelayPolicy_WaitSeconds>微笑
smile_later.wait_period(100); //从等待策略通过继承可访问
execute_action(smile_later);
}

使用继承允许从策略实现的公共方法可以通过模板实例化。这不允许使用聚合来组合策略,因为没有新的函数成员可以被推入类接口。在该示例中,模板取决于具有wait()方法的策略,该方法对所有等待的策略是公共的。现在等待一段时间需要一个通过period()public方法设置的固定时间段。



在这个例子中,NoWait策略只是一个特定的例子周期设置为0的WaitSeconds策略。这是为了标记策略接口不需要是相同的。另一个等待策略实现可以是通过提供注册为给定事件的回调的类来等待几毫秒,时钟滴答或直到某些外部事件。



如果你不需要polimorphism,你可以从示例中取出基类和虚拟方法。虽然这对于当前示例似乎过于复杂,您可以决定是否向其中添加其他策略。



添加新的正交行为将意味着如果使用简单继承(使用polimorphism)类的数量的指数增长,使用这种方法,你可以只实现每个不同的部分并在Action模板中将它们粘合在一起。



例如,您可以定期执行操作,并添加退出策略以确定何时退出周期循环。首先想到的选项是LoopPolicy_NRuns和LoopPolicy_TimeSpan,LoopPolicy_Until。这个策略方法(我的case中的exit())对于每个循环调用一次。第一个实现计数一个固定数目后被调用退出的次数(由用户修正,因为上面的例子中固定的周期)。第二个实现将周期性地运行给定时间段的过程,而最后一个将运行该过程直到给定时间(时钟)。



如果你还在跟踪我到这里,我确实会做一些改变。第一个是,而不是使用实现方法execute()的模板参数Command,我将使用函子,并且可能是一个模板化的构造函数,该函数接受命令作为参数执行。理由是,这将使其与其他库结合使用boost :: bind或boost :: lambda更可扩展,因为在这种情况下,命令可以在实例化点绑定到任何自由函数,函子或成员方法



现在我要去了,但如果你有兴趣,我可以尝试发布修改版本。


I know that having diamond inheritance is considered bad practice. However, I have 2 cases in which I feel that diamond inheritance could fit very nicely. I want to ask, would you recommend me to use diamond inheritance in these cases, or is there another design that could be better.

Case 1: I want to create classes that represent different kinds of "Actions" in my system. The actions are classified by several parameters:

  • The action can be "Read" or "Write".
  • The action can be with delay or without delay (It is not just 1 parameter. It changes the behavior significantly).
  • The action's "flow type" can be FlowA or FlowB.

I intend to have the following design:

// abstract classes
class Action  
{
    // methods relevant for all actions
};
class ActionRead      : public virtual Action  
{
    // methods related to reading
};
class ActionWrite     : public virtual Action  
{
    // methods related to writing
};
class ActionWithDelay : public virtual Action  
{
    // methods related to delay definition and handling
};
class ActionNoDelay   : public virtual Action  {/*...*/};
class ActionFlowA     : public virtual Action  {/*...*/};
class ActionFlowB     : public virtual Action  {/*...*/};

// concrete classes
class ActionFlowAReadWithDelay  : public ActionFlowA, public ActionRead, public ActionWithDelay  
{
    // implementation of the full flow of a read command with delay that does Flow A.
};
class ActionFlowBReadWithDelay  : public ActionFlowB, public ActionRead, public ActionWithDelay  {/*...*/};
//...

Of course, I will obey that no 2 actions (inheriting from Action class) will implement the same method.

Case 2: I implement the composite design pattern for a "Command" in my system. A command can be read, written, deleted, etc. I also want to have a sequence of commands, which can also be read, written, deleted, etc. A sequence of commands can contain other sequences of commands.

So I have the following design:

class CommandAbstraction
{
    CommandAbstraction(){};
    ~CommandAbstraction()=0;
    void Read()=0;
    void Write()=0;
    void Restore()=0;
    bool IsWritten() {/*implemented*/};
    // and other implemented functions
};

class OneCommand : public virtual CommandAbstraction
{
    // implement Read, Write, Restore
};

class CompositeCommand : public virtual CommandAbstraction
{
    // implement Read, Write, Restore
};

In addition, I have a special kind of commands, "Modern" commands. Both one command and composite command can be modern. Being "Modern" adds a certain list of properties to one command and composite command (mostly same properties for both of them). I want to be able to hold a pointer to CommandAbstraction, and initialize it (via new) according to the needed type of command. So I want to do the following design (in addition to the above) :

class ModernCommand : public virtual CommandAbstraction
{
    ~ModernCommand()=0;
    void SetModernPropertyA(){/*...*/}
    void ExecModernSomething(){/*...*/}
    void ModernSomethingElse()=0;

};
class OneModernCommand : public OneCommand, public ModernCommand
{
    void ModernSomethingElse() {/*...*/};
    // ... few methods specific for OneModernCommand
};
class CompositeModernCommand : public CompositeCommand, public ModernCommand
{
    void ModernSomethingElse() {/*...*/};
    // ... few methods specific for CompositeModernCommand
};

Again, I will make sure that no 2 classes inheriting from CommandAbstraction class will implement the same method.

Thank you.

解决方案

Inheritance is the second strongest (more coupling) relations in C++, preceded only by friendship. If you can redesign into using only composition your code will be more loosely coupled. If you cannot, then you should consider whether all your classes should really inherit from the base. Is it due to implementation or just an interface? Will you want to use any element of the hierarchy as a base element? Or are just leaves in your hierarchy that are real Action's? If only leaves are actions and you are adding behavior you can consider Policy based design for this type of composition of behaviors.

The idea is that different (orthogonal) behaviors can be defined in small class sets and then bundled together to provide the real complete behavior. In the example I will consider just one policy that defines whether the action is to be executed now or in the future, and the command to execute.

I provide an abstract class so that different instantiations of the template can be stored (through pointers) in a container or passed to functions as arguments and get called polimorphically.

class ActionDelayPolicy_NoWait;

class ActionBase // Only needed if you want to use polimorphically 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 );
}

The use of inheritance allows public methods from the policy implementations to be accessible through the template instantiation. This disallows the use of aggregation for combining the policies as no new function members could be pushed into the class interface. In the example, the template depends on the policy having a wait() method, which is common to all waiting policies. Now waiting for a time period needs a fixed period time that is set through the period() public method.

In the example, the NoWait policy is just a particular example of the WaitSeconds policy with the period set to 0. This was intentional to mark that the policy interface does not need to be the same. Another waiting policy implementation could be waiting on a number of milliseconds, clock ticks, or until some external event, by providing a class that registers as a callback for the given event.

If you don't need polimorphism you can take out from the example the base class and the virtual methods altogether. While this may seem overly complex for the current example, you can decide on adding other policies to the mix.

While adding new orthogonal behaviors would imply an exponential growth in the number of classes if plain inheritance is used (with polimorphism), with this approach you can just implement each different part separatedly and glue it together in the Action template.

For example, you could make your action periodic and add an exit policy that determine when to exit the periodic loop. First options that come to mind are LoopPolicy_NRuns and LoopPolicy_TimeSpan, LoopPolicy_Until. This policy method ( exit() in my case ) is called once for each loop. The first implementation counts the number of times it has been called an exits after a fixed number (fixed by the user, as period was fixed in the example above). The second implementation would periodically run the process for a given time period, while the last one will run this process until a given time (clock).

If you are still following me up to here, I would indeed make some changes. The first one is that instead of using a template parameter Command that implements a method execute() I would use functors and probably a templated constructor that takes the command to execute as parameter. The rationale is that this will make it much more extensible in combination with other libraries as boost::bind or boost::lambda, since in that case commands could be bound at the point of instantiation to any free function, functor, or member method of a class.

Now I have to go, but if you are interested I can try posting a modified version.

这篇关于钻石继承(C ++)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆