【问题标题】:Composition Over Inheritance - Avoiding Abstract Classes组合优于继承——避免抽象类
【发布时间】:2016-10-02 10:51:06
【问题描述】:

我正在阅读这个wiki article 关于组合而不是继承的内容,在示例中他们使用了一个抽象类。假设您想完全避免他们使用抽象类,而只使用具体类。我想知道,我的例子是对构图的正确使用。假设我有以下 IGameobject 接口。

    public interface IGameObject
    {
        string Name {get;}
    }

以及以下武器界面:

    //Note the IGameObject getter
    public interface IWeapon
    {
        int Damage {get; }
        IGameObject gameObject {get;}
    }

通常,在我的游戏中,武器is-a IGameObject。但是,考虑到我们正在使用组合,根据 wiki 文章,它是 has-a 关系。

现在,我可以在创建 Sword 对象时这样做:

public class Sword : IWeapon
{
        private IWeapon weaponObject;

        public Sword(IWeapon weapon)
        {
            this.weaponObject = weapon;
        }

        public int Damage
        {
            get
            {
                return this.weaponObject.Damage;
            }
        }

        public IGameObject gameObject
        {
            get
            {
                return this.weaponObject.gameObject;
            }
     }

}

我现在可以将我的剑存储在一个集合中,例如一个列表:

List<IWeapon> weapons = new List<IWeapon>();
weapons.add(swordObj)

并且仍然可以访问我的 IGameObject。

现在,我有 3 个问题:

  1. 我是否正确实现了合成?
  2. 我的 Sword 类是否也应该实现 IGameObject 接口?
  3. 我的 IWeapon 包含IGameObject getter 可以吗? (原因是,如果我将它存储为 IWeapon,没有 IGameObject getter,那么我将如何获得 IGameObject 的详细信息?)

【问题讨论】:

  • 这看起来很奇怪 - 你的 Sword 类只是 IWeapon 的包装。
  • 您是否有理由避免在接口中添加行为添加方法而坚持使用属性?
  • 这不是has-a。它是-a,因为同一个对象有多个方面。这不适合组合。组合并不比继承好。这是为了不同的东西。
  • 这里的组合在IWeaponIGameObject之间而不是SwordIWeapon之间。我希望Sword 实现IWeapon 并在该方案下包含IGameObject,并让Sword.Damage 返回一些常量值。 IWeaponIGameObject 之间的组合是否是一个好主意很难说。我可能希望IWeaponIGameObject 的子接口,并且根本不使用组合。
  • 暂且不说组合与继承的问题,这里还有很多其他的问题。为什么“伤害”是武器的属性?肯定会通过考虑武器、持有者和目标的抵抗力来计算伤害;为什么这些东西中的任何一个都要对计算负责?这个领域有很多陷阱;如果您对这个主题感兴趣,请查看我的系列文章:ericlippert.com/2015/04/27/wizards-and-warriors-part-one

标签: c# oop inheritance composition


【解决方案1】:

组合与继承是一个非常重要的原则,但与任何原则一样,只有正确应用它才会产生积极的影响。 为了正确应用它,您必须了解它是如何出现的。

之所以想到 C vs I,是因为开发人员在 OOP 上“进城”,尤其是在允许多重继承的 C++ 等语言中。正如您可以想象的那样,在设计方面,一切都很快走下坡路。

通过以下方式很容易想到这个原则: 作为 OOD 系统设计者,我们希望以最类似于我们想要模仿的领域的方式对领域对象关系进行建模,因此我们需要仔细仔细地研究这些关系。

每当您要继承时,请务必考虑继承是否准确描述了相关实体之间的真实世界关系。

在您的情况下,武器实际上是一个游戏对象,因此继承实际上是一个好主意,并且会在此过程中促进多态性。

以下是 C vs I 如何在您的域中应用的示例:

interface IGameCharacter
{
  ICollection<IWeapon> Weapons {get;}
  ICollection<IGameCharacter> Party {get;}
}

【讨论】:

  • +1 表示“C vs I 被认为是因为开发人员在 OOP 上‘进城’”。我看到了在 OOP 传播的那一天和今天函数式编程变得更加主流之间的历史性相似之处。过去的 OOP 大师做出了承诺,并将 OOP 相当简单的机制变成了一个科学神话。一段时间后,人们找到了生产有效 OOP 系统的方法并开发了最佳实践。然而,当时有效的方法今天仍然有效:从您的用例和子系统的表面积开始,然后按照您认为合适的方式实施。定义良好的子系统可让您的系统保持健全。
  • @BitTickler。当场。
  • @EyalPerry - 只是一个问题,如果我的 IWeapon 有一个 IGameObject getter 可以吗?所以当我实现我的 IWeapon 时,我还必须返回一个 IGameObject?
  • 这取决于这个 IGameObject 与 IWeapon 的关系。你有什么具体的关系吗?顺便说一句,请随意提出尽可能多的问题——回答与原则相关的问题而不是愚蠢的 lmgtfy 类型的问题令人耳目一新。 @S.R
  • 好吧,我想到的是 is-a 关系,在这种情况下,IWeapon 会直接实现 IGameObject。
【解决方案2】:

您在这里使用的称为装饰器设计模式。您创建了一个扩展某些超类(IWeapon)的新类。这个类被称为装饰器(Sword)。该类包含一个与超类相同类型的对象——这个对象被称为装饰者(weaponObject)。装饰者的一些责任转移到了被装饰者身上。在实践中,这意味着装饰器在其算法之一中调用至少一个被装饰者的方法(在您的情况下,被装饰者用于损害和游戏对象)。

这样做的目的是避免重新实现装饰器中已经存在于被装饰者中的某些行为。如果被装饰者已经具有所需的行为并且您希望在您的装饰器中具有这种行为,那么装饰器模式是一个不错的选择。

另一种选择是将被装饰者子类化并继承想要的行为,但这会引入与子类化相关的其他问题(紧密耦合,除了想要的行为外,还继承了一些不需要的行为,暴露了被装饰者不应该暴露的东西装饰师等)。如果你想要组合的好处,你必须实现装饰器模式(或类似的模式)而不是子类化。但请记住,被装饰者的具体类型可以在运行时改变,正是这种类型决定了你将在装饰器类中获得什么行为。这对于支持在运行时更改行为非常有用,但在实现模式时也必须注意这一点。

现在,至于装饰器模式在这种情况下是否是正确的选择,我向您提出以下问题:您是否希望您的 Sword 类(您的装饰器)利用 IWeapon 的另一个子类(您的decoratee) 并且您希望能够在运行时更改此行为吗?如果您对该问题的回答是肯定的,那么装饰器模式非常适合。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-25
    • 2013-01-03
    • 1970-01-01
    • 2021-09-04
    • 2011-04-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多