钻石继承(C++)

我知道拥有钻石继承被认为是不好的做法.但是,我有两个案例,我觉得钻石继承非常适合.我想问一下,在这些情况下你会推荐我使用菱形继承,还是有其他更好的设计.

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.

案例 1: 我想在我的系统中创建代表不同类型操作"的类.动作由几个参数分类:

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

  • 操作可以是读取"或写入".
  • 动作可以有延迟或没有延迟(它不仅仅是 1 个参数.它会显着改变行为).
  • 动作的流类型"可以是 FlowA 或 FlowB.

我打算有以下设计:

// 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  {/*...*/};
//...

当然,我会遵守没有 2 个动作(继承自 Action 类)会实现相同的方法.

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

案例 2: 我在我的系统中为命令"实现了复合设计模式.一个命令可以读、写、删除等,我也想有一个命令序列,也可以读、写、删除等,一个命令序列可以包含其他的命令序列.

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.

所以我有以下设计:

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

另外,我有一种特殊的命令,现代"命令.一个命令和复合命令都可以是现代的.现代"将特定的属性列表添加到一个命令和复合命令(这两个命令的属性大多相同).我希望能够持有一个指向 CommandAbstraction 的指针,并根据所需的命令类型对其进行初始化(通过 new).所以我想做下面的设计(除了上面的):

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

同样,我将确保从 CommandAbstraction 类继承的 2 个类不会实现相同的方法.

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

谢谢.

推荐答案

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

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 polymorphically.

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() 公共方法设置.

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.

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

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 polymorphism 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.

如果使用纯继承(使用多态),添加新的正交行为意味着类的数量呈指数增长,但通过这种方法,您可以单独实现每个不同的部分,然后将它们粘合到 Action 模板中.

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

例如,您可以使您的操作具有周期性,并添加一个退出策略来确定何时退出周期性循环.首先想到的选项是 LoopPolicy_NRuns 和 LoopPolicy_TimeSpan、LoopPolicy_Until.这个策略方法(在我的例子中为 exit() )为每个循环调用一次.第一个实现计算在固定数量(由用户固定,如上例中固定的时间段)之后被称为退出的次数.第二个实现将在给定的时间段内周期性地运行该进程,而最后一个将运行该进程直到给定的时间(时钟).

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).

如果你还跟着我到这里,我确实会做一些改变.第一个是,不是使用实现方法 execute() 的模板参数 Command,我将使用函子和可能的模板化构造函数,它将命令作为参数执行.其基本原理是,这将使其与其他库(如 boost::bind 或 boost::lambda )结合起来更具扩展性,因为在这种情况下,命令可以在实例化点绑定到任何自由函数、函子或成员方法一个班级.

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.

相关文章