我知道钻石继承被认为是不好的做法.但是,我有2个案例,我认为钻石继承可以很好地适应.我想问一下,您是否会建议我在这些情况下使用钻石继承,或者是否有其他设计可能更好.
案例1:我想在我的系统中创建代表不同类型"动作"的类.这些操作按以下几个参数进行分类:
动作可以是"读取"或"写入".
动作可以延迟或没有延迟(它不仅仅是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类)将实现相同的方法.
案例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类继承的2个类不会实现相同的方法.
谢谢.
继承是C++中第二个最强大(更多耦合)的关系,仅在友谊之前.如果您可以重新设计为仅使用合成,则代码将更松散地耦合.如果你不能,那么你应该考虑你的所有类是否应该真正从基类继承.是由于实现还是仅仅是一个界面?您是否希望将层次结构中的任何元素用作基本元素?或者只是层次结构中的叶子是真正的Action?如果只有叶子是动作并且您正在添加行为,则可以考虑针对此类行为组合的基于策略的设计.
这个想法是可以在小类集中定义不同的(正交)行为,然后捆绑在一起以提供真实的完整行为.在示例中,我将仅考虑一个策略,该策略定义操作是现在还是将来执行,以及要执行的命令.
我提供了一个抽象类,以便模板的不同实例可以在容器中存储(通过指针),或者作为参数传递给函数,并以多态方式调用.
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()公共方法设置的固定时间段.
在该示例中,NoWait策略只是WaitSeconds策略的一个特定示例,其周期设置为0.这是为了标记策略接口不需要相同.另一个等待策略实现可以通过提供一个注册为给定事件的回调的类来等待数毫秒,时钟周期或直到某些外部事件.
如果您不需要多态性,您可以从示例中完全取出基类和虚拟方法.虽然对于当前示例而言这似乎过于复杂,但您可以决定将其他策略添加到组合中.
如果使用普通继承(使用多态),添加新的正交行为将意味着类的数量呈指数增长,使用此方法,您可以单独实现每个不同的部分并将其粘贴在Action模板中.
例如,您可以定期执行操作并添加退出策略以确定何时退出定期循环.首先想到的选择是LoopPolicy_NRuns和LoopPolicy_TimeSpan,LoopPolicy_Until.对于每个循环,都会调用此策略方法(在我的情况下为exit()).第一个实现计算在固定数字之后它被称为退出的次数(由用户修复,因为在上面的示例中固定了时间段).第二个实现将定期运行该过程一段时间,而最后一个将执行此过程直到给定时间(时钟).
如果你还在跟我到这里,我的确会做一些改变.第一个是不使用实现方法execute()的模板参数Command,而是使用仿函数,可能是一个模板化构造函数,它将命令作为参数执行.理由是,这将使其与其他库如boost :: bind或boost :: lambda更加可扩展,因为在这种情况下,命令可以在实例化时绑定到任何自由函数,仿函数或成员方法一堂课.
现在我必须去,但如果你有兴趣,我可以尝试发布修改版本.
面向实现的钻石继承(继承实现是有风险的)和面向子类型的继承(接口或标记接口是继承的)(通常很有用)之间存在设计质量差异.
一般来说,如果你可以避免使用前者,那么你最好从某个地方开始,确切的调用方法可能会引起问题,虚拟基础,状态等的重要性开始变得重要.事实上,Java不会允许你拉这样的东西,它只支持接口层次结构.
我认为你可以为此设计的"最干净"的设计是将钻石中的所有类有效地转换为模拟接口(通过没有状态信息,并具有纯虚方法).这减少了歧义的影响.当然,您可以使用多个甚至钻石继承,就像在Java中使用implements一样.
然后,有一组这些接口的具体实现,可以以不同的方式实现(例如,聚合,甚至继承).
封装此框架,以便外部客户端只获取接口,并且永远不会直接与具体类型交互,并确保彻底测试您的实现.
当然,这是一项很多工作,但如果您正在编写一个可重用的中央API,这可能是您最好的选择.
本周我遇到了这个问题,发现了一篇关于DDJ的文章解释了这些问题以及何时应该或不应该关注它们.这里是:
"多重继承被认为是有用的"