我知道拥有菱形继承(钻石问题)被认为是不良做法。但是,我有2种情况,我认为菱形继承(钻石问题)非常合适。我想问一下,您是否建议我在这些情况下使用菱形继承(钻石问题),或者是否有其他更好的设计。

情况1:我想创建代表我系统中不同类型的“ Action ”的类。这些操作按几个参数分类:

  • 该 Action 可以是“读取”或“写入”。
  • 该 Action 可以有延迟或无延迟(它不仅是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  {/*...*/};
    //...
    

    当然,我会服从任何两个 Action (从Action类继承)来实现相同的方法。

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

    所以我有以下设计:
    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)。所以我想做以下设计(除了上面的):
    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类的类将实现相同的方法。

    谢谢。

    最佳答案

    继承是C++中第二强的(更多耦合的)关系,仅在友谊之后。如果您可以重新设计为仅使用合成,则您的代码将更松散地耦合在一起。如果不能,则应考虑所有类是否应真正从基类继承。是由于实现还是仅仅是接口(interface)?您是否要将层次结构的任何元素用作基础元素?还是只是您的层次结构中的叶子是真正的Action?如果只有 Action ,而您要添加行为,则可以考虑针对此类行为组合考虑基于策略的设计。

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

    我提供了一个抽象类,以便可以将模板的不同实例化(通过指针)存储在容器中,或作为参数传递给函数并进行多态调用。

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

    继承的使用允许通过模板实例访问策略实现中的公共(public)方法。由于不允许将新功能成员插入类接口(interface),因此不允许使用聚合来组合策略。在示例中,模板依赖于具有wait()方法的策略,该方法对于所有等待策略都是通用的。现在,等待一个时间段需要通过period()公共(public)方法设置的固定时间段。

    在此示例中,“NoWait”策略只是“WaitSeconds”策略的特定示例,其周期设置为0。这是为了标记策略接口(interface)不必相同。通过提供注册为给定事件的回调的类,另一种等待策略的实现可以等待数毫秒,时钟滴答或直到某个外部事件。

    如果不需要多态,则可以从示例中取出基类和虚方法。尽管对于当前示例而言,这似乎过于复杂,但您可以决定将其他策略添加到组合中。

    如果使用纯继承(具有多态性),则添加新的正交行为将意味着类的数量呈指数增长,但是使用这种方法,您可以单独实现每个不同的部分并将其粘合在Action模板中。

    例如,您可以使操作具有周期性,并添加退出策略来确定何时退出周期性循环。首先想到的选项是LoopPolicy_NRuns和LoopPolicy_TimeSpan,LoopPolicy_Until。每个循环都会调用一次此策略方法(在我的情况下为exit())。第一个实现对固定次数(由用户确定,因为在上面的示例中为周期确定)之后退出导出的次数进行计数。第二个实现将在给定的时间段内定期运行该过程,而最后一个实现将运行该过程直到给定的时间(时钟)。

    如果您仍在关注我,我确实会做出一些更改。第一个是,我将使用函子和可能使用将命令作为参数执行的模板化构造函数,而不是使用实现方法execute()的模板参数Command。这样做的基本原理是,与boost::bind或boost::lambda等其他库结合使用时,它的可扩展性更高,因为在这种情况下,命令可以在实例化时绑定(bind)到任何自由函数,函子或成员方法。一个类

    现在我必须走了,但是如果您有兴趣,我可以尝试发布修改的版本。

    关于c++ - 菱形继承(钻石问题)(C++),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/379053/

    10-11 23:03
    查看更多