【问题标题】:How to design around lack of multiple inheritance?如何围绕缺乏多重继承进行设计?
【发布时间】:2011-09-16 17:43:58
【问题描述】:

使用接口是行不通的,因为我想要一个单一的实现。使用this 解决方案会导致大量冗余代码,因为我计划拥有相当多的子类(组合与继承)。我已经确定我正在寻找一个针对特定问题的设计解决方案,但我想不出任何优雅的东西。

基本上,我希望类具有单独的属性,并且在设计时将这些属性附加到我选择的任何子类。说,我有“忍者”课。我希望能够制作任意子类,例如“grayNinja”,其中灰色忍者将始终拥有剑和投掷星星。然后可能是永远拥有剑和斗篷的“redNinja”。很明显,剑、星星和斗篷都有自己的实现——这是我不能使用接口的地方。我能找到的最接近的解决方案是decorator pattern,但我不希望在运行时使用该功能。最好的解决方案是它的一个分支吗?在 Black Ninja 类的构造函数中,我通过剑和 throwingStar 的构造函数将它传递到哪里? (那些是抽象类)

有一段时间没有写代码,阅读也没有让我走得太远 - 如果答案很简单,请原谅我。

编辑:回答了我自己的问题。直到明天我才能将其标记为“答案”。如果有我没有发现的问题,请告诉我。这个问题迫使我做的所有阅读都很棒。学到不少。

【问题讨论】:

  • 我不清楚你为什么不喜欢作曲。
  • (+1) 因为一些 O.O.语言支持单一继承,根本没有接口。
  • @Beta - 组合要求您每次都重新输入方法。我计划不仅有很多属性,还有很多子类。冗余代码看起来非常混乱和不必要。
  • 嗯?您不必为ninja 的每种持剑类型重复sword 的实现,但我想所有(或大多数)持剑ninja 类型都需要一个简短的drawSword 方法,其中大多数情况是sworddraw 方法的简单包装。这是您要避免的冗余吗?
  • 你想把它融入什么语言?

标签: oop design-patterns language-agnostic class-design multiple-inheritance


【解决方案1】:

您希望类具有单独的属性。你有考虑过这样编码吗?

例如,您想要一个 RedNinja,它是一个拥有剑和斗篷的忍者。好的,所以定义 Ninja 以拥有一个库存,使其可以通过 Ninja 访问,并通过 RedNinja 的构造函数传入一个库存。你可以对行为做同样的事情。

【讨论】:

  • 您在使用技术意义上的“行为”吗?如何让redNinja 拔剑?它有drawSword 方法吗?
  • 使用 drawSword() 方法的另一种方法是保存一个行为对象,其动作是拔剑。类似于对象的集合,它可以包含在一组行为中。您可以使用装饰器来轻松检查 canDrawSword() 和 drawSword()。
  • 菲尔不是说他不想使用装饰器模式吗?
  • @Beta - 我相信这是我在原始问题末尾含糊地提到的解决方案(与装饰器模式略有不同)。我无法完全理解它,所以现在可能会对其进行编码,看看它是如何工作的。我会拿回我的结果。
  • 啊,我误解了它是如何工作的。安迪,你是说对于每一种行为,我都需要一个'can' 方法来检查它吗?这似乎比仅仅使用组合方法更多的代码。
【解决方案2】:

我曾经做过一个类似的应用程序。使用更早的“C++”编译器,它只支持单一继承,根本不支持接口。

// base class for all ninjas
public class Ninja {

  // default constructor
  public Ninja() { ... }

  // default destructor
  public ~Ninja() { ... }
} // class

public class StarNinja: public Ninja {

  // default constructor
  public StarNinja() { ... }

  // default destructor
  public ~StarNinja() { ... }

  public void throwStars() { ... }
} // class

public class KatannaNinja: public Ninja {

  // default constructor
  public KatannaNinja() { ... }

  // default destructor
  public ~KatannaNinja() { ... }

  public void useKatanna() { ... }
} // class

public class InvisibleNinja: public Ninja {

  // default constructor
  public InvisibleNinja() { ... }

  // default destructor
  public ~InvisibleNinja() { ... }

  public void becomeVisible() { ... }  
  public void becomeInvisible() { ... }
} // class

public class FlyNinja: public Ninja {

  // default constructor
  public FlyNinja() { ... }

  // default destructor
  public ~FlyNinja() { ... }

  public void fly() { ... }  
  public void land() { ... }
} // class

public class InvincibleNinja: public Ninja {

  // default constructor
  public InvincibleNinja() { ... }

  // default destructor
  public ~InvincibleNinja() { ... }

  public void turnToStone() { ... }  
  public void turnToHuman() { ... }
} // class

// --> this doesn't need to have the same superclass,
// --> but, it helps
public class SuperNinja: public Ninja {

  StarNinja* LeftArm;
  InvincibleNinja* RightArm;
  FlyNinja* LeftLeg;
  KatannaNinja* RightLeg;
  InvisibleNinja* Body;

  // default constructor
  public SuperNinja() { 
    // -> there is no rule to call composed classes,
    LeftArm = new StarNinja();
    RightArm = new InvincibleNinja();
    LeftLeg = new FlyNinja();
    RightLeg = new KatannaNinja();
    Body = new InvisibleNinja();
  }

  // default destructor
  public ~SuperNinja() { 
    // -> there is no rule to call composed classes
    delete LeftArm();
    delete RightArm();
    delete LeftLeg();
    delete RightLeg();
    delete Body();
  }

  // --> add all public methods from peers,
  // --> to main class
  public void throwStars() { LeftArm->throwStars(); }
  public void useKatanna() { RightLeg->useKatanna(); }  
  public void becomeVisible() { Body->becomeVisible() }  
  public void becomeInvisible() { Body->becomeInvisible() }
  public void fly() { LeftLeg->fly() }  
  public void land() { LeftLeg->land() }
  public void turnToStone() { RightArm->turnToStone(); }  
  public void turnToHuman() { RightArm->turnToHuman(); }
} // class

恐怕,最接近的例子是组合设计模式。为了变得更类似于继承,我创建了一个所有复合类共享的通用基类,并创建了一个主类,它将成为多重继承的结果,它具有组件的所有公共方法的副本类。

如果你想使用接口,强制主类拥有所有重要的方法, 然后制作一个与每个组成类匹配的接口,并在主类中实现。

public interface INinja {
  public void NinjaScream() { ... }
} // class

public interface IStarNinja {
  void throwStars();
} // class

public interface IKatannaNinja {
  void useKatanna();
} // class

public interface IInvisibleNinja {
  void becomeVisible();
  void becomeInvisible();
} // class

public interface CFlyNinja {
  void fly();
  void land();
} // class

public interface IInvincibleNinja {
  void turnToStone() { ... }  
  void turnToHuman() { ... }
} // class

// base class for all ninjas
public class CNinja: public INinja {

  // default constructor
  public CNinja() { ... }

  // default destructor
  public ~CNinja() { ... }

  public void NinjaScream() { ... }
} // class

public class CStarNinja: public CNinja, INinja {

  // default constructor
  public CStarNinja() { ... }

  // default destructor
  public ~CStarNinja() { ... }

  public void NinjaScream() { ... }
  public void throwStars() { ... }
} // class

public class CKatannaNinja: public CNinja, IKatannaNinja {

  // default constructor
  public CKatannaNinja() { ... }

  // default destructor
  public ~CKatannaNinja() { ... }

  public void NinjaScream() { ... }
  public void useKatanna() { ... }
} // class

public class CInvisibleNinja: public CNinja, IInvisibleNinja {

  // default constructor
  public CInvisibleNinja() { ... }

  // default destructor
  public ~CInvisibleNinja() { ... }

  public void becomeVisible() { ... }  
  public void becomeInvisible() { ... }
} // class

public class CFlyNinja: public CNinja, IFlyNinja {

  // default constructor
  public CFlyNinja() { ... }

  // default destructor
  public ~CFlyNinja() { ... }

  public void fly() { ... }  
  public void land() { ... }
} // class

public class CInvincibleNinja: public CNinja, IInvincibleNinja {

  // default constructor
  public CInvincibleNinja() { ... }

  // default destructor
  public ~CInvincibleNinja() { ... }

  public void turnToStone() { ... }  
  public void turnToHuman() { ... }
} // class

// --> this doesn't need to have the same superclass,
// --> but, it helps
public class CSuperNinja: public CNinja,
  IKatannaNinja,
  IInvisibleNinja,
  IFlyNinja,
  IInvincibleNinja  
{

  CStarNinja* LeftArm;
  CInvincibleNinja* RightArm;
  CFlyNinja* LeftLeg;
  CKatannaNinja* RightLeg;
  CInvisibleNinja* Body;

  // default constructor
  public CSuperNinja() { 
    // -> there is no rule to call composed classes
    LeftArm = new CStarNinja();
    RightArm = new CInvincibleNinja();
    LeftLeg = new CFlyNinja();
    RightLeg = new CKatannaNinja();
    Body = new CInvisibleNinja();
  }

  // default destructor
  public ~SuperNinja() { 
    // -> there is no rule to call composed classes
    delete LeftArm();
    delete RightArm();
    delete LeftLeg();
    delete RightLeg();
    delete Body();
  }

  // --> add all public methods from peers,
  // --> to main class
  public void throwStars() { LeftArm->throwStars(); }
  public void useKatanna() { RightLeg->useKatanna(); }  
  public void becomeVisible() { Body->becomeVisible() }  
  public void becomeInvisible() { Body->becomeInvisible() }
  public void fly() { LeftLeg->fly() }  
  public void land() { LeftLeg->land() }
  public void turnToStone() { RightArm->turnToStone(); }  
  public void turnToHuman() { RightArm->turnToHuman(); }
} // class

我知道这个解决方案很复杂,但是,似乎没有其他方法。

干杯。

【讨论】:

  • 我熟悉 C++,但不熟悉这种语言。我认为“接口”在这种语言中具有特定含义,而您所做的在我看来确实像是多重继承。
  • @Beta 看起来像 C#,您可以从多个接口继承
  • @Beta 和@Raynos。它的伪代码,不是真正的语言,但它应该类似于 C++。第一个例子,只有 C++ 的类。第二个示例,我使用支持接口的 C++ 变体(不是 C#)。
  • 这是使用合成没有?如果这是唯一的方法,那么我可以处理。我只是认为可能有一种方法可以不使用所有这些包装方法。感谢您的宝贵时间
  • 啊,希望我能编辑我的评论 - 因为我浏览了你说它基本上是使用合成的部分。感谢您提供清晰的代码和响应。
【解决方案3】:

好的,所以mix-ins through extension methods 将是我的首选路线。我不知道如何在 vb.net 中使用动态代理(似乎需要包含大量文档的库,而这些文档并未具体涵盖我需要的内容)。动态代理似乎也比使用扩展方法更脏。如果前两个不起作用,我会默认使用合成。

所以扩展方法的一个问题是,如果你想保存变量,代码会变得有点脏。不过不多。另一个问题是所有的扩展方法都必须在模块中定义,所以代码在新的眼里可能看起来有点傻。我将通过在同一个文件中使用相应的扩展方法定义我的接口和模块来解决这个问题。

最后,如果您不想通过链接查看完整的示例,这里有一些示例 vb.net 代码。

Imports System.Runtime.CompilerServices 'for extension methods

Public Interface ISword
End Interface
Public Interface IThrowingStar
End Interface

Module ExtensionMethods

    <Extension()>
    Public Sub swingSword(ByVal hasASword As ISword)
        Console.WriteLine("Sword has been swung")
    End Sub

    <Extension()>
    Public Sub throwStar(ByVal hasAStar As IThrowingStar)
        Console.WriteLine("Star has been thrown")
    End Sub

End Module

Public Class RedNinja
    Inherits Ninja
    Implements IThrowingStar, ISword

    Public Sub New()
    End Sub

End Class

Public MustInherit Class Ninja

    private curHealth as Integer

    Public Sub New()
        curHealth = 100
    End Sub

    Public Function getHP() As Integer
        Return curHealth
    End Function

End Class

Module Module1

    Sub main()

        Console.WriteLine("Type any character to continue.")
        Console.ReadKey()

        Dim a As New RedNinja
        a.swingSword() 'prints "Sword has been swung"
        a.throwStar()  'prints "Star has been thrown"

        Console.WriteLine("End of program - Type any key to exit")
        Console.ReadKey()

    End Sub
End Module

【讨论】:

    【解决方案4】:

    如果您只是必须具有多重继承,那么肮脏的解决方案就是使用dynamic proxies in Java 之类的东西。

    但我猜你可能正在使用 C# 编程,这是与语言无关的问题,所以这里有语言无关的答案:查看 compositefactory 设计模式,这应该会给你一些想法。

    此外,可能不需要在构造函数中传递所有内容。也请查看IoC pattern

    【讨论】:

    • “java 中的动态代理”是一本非常好的读物。如果在 vb.NET 中提供了这个功能,你知道吗?如果这恰好是最好的解决方案,我会查一下。
    • 嗯,原来 C# 有类似的东西(TransparentProxy),我看到有几个库建立在它之上,使其更像 Java 中的动态代理,但我不知道.NET 足以为您提供更具体的内容。
    猜你喜欢
    • 1970-01-01
    • 2013-01-17
    • 2013-01-07
    • 1970-01-01
    • 1970-01-01
    • 2012-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多