【问题标题】:Best OOP practice for grandchild class calling methods from its parent [closed]从其父类调用方法的孙子类的最佳 OOP 实践[关闭]
【发布时间】:2017-05-12 17:40:00
【问题描述】:

我想知道构造类的最佳实践,以便在类有祖父母时调用父方法。

假设我有这种情况:

public class Vehicle { // parent

    public bool moves;
    public bool flies;
    public float maxSpeed;

    public virtual void Initialize () {
        moves = true;
    }
}

public class Airplane : Vehicle { // child
    public override void Initialize () {
        base.Initialize ();
        flies = true;
    }
}

public class Jet : Airplane { // grandchild
    public override void Initialize () {
        base.Initialize ();
        maxSpeed = 400f;
    }
}

现在,如果我实例化一个 Jet 对象,它将运行 Vehicle 初始化,但不是 Airplane 父代码(意味着它不会飞行)。

有一些解决方法。一个是将 Airplane 代码移动到每个子类中,但这违背了使用 OOP 的目的。解决此问题的最佳做法是什么?

注意:我不能使用构造函数,因为这是一个 Unity3D 应用程序,所有附加到 GameObject 的类都应该继承自 MonoBehaviour 类,并且它有自己的初始化函数,如 Awake() 和Start(),并且不允许允许构造函数。

【问题讨论】:

  • 什么是构造函数?
  • @EdPlunkett 您可以在此处了解有关构造函数的更多信息:docs.microsoft.com/en-us/dotnet/articles/csharp/…:p
  • @RufusL 如果网络上有一些资源可以自动回答这样的问题。
  • 如果这段代码以一种无需手动编辑即可编译的方式编写,那就太好了。
  • 代码中没有任何意义。为什么使用初始化方法而不是构造函数?为什么初始化器是私有的?为什么它们不是虚拟的?为什么你认为 Jet 对象不会调用基方法?为什么字段初始化器不是字段初始化器?我无法回答这个问题。

标签: c# oop


【解决方案1】:

我想知道构造类的最佳实践,以便在类有祖父母时调用父方法。

你不要这样做

问题的假设是从Airplane派生的Jet需要知道Airplane从Vehicle派生的实现细节。这是一个糟糕的假设。最佳实践是将 Airplane 视为一个黑匣子(没有双关语),而不利用其实现细节的知识。

你的 Initialize 方法有一个契约:它初始化一些东西。如果你想调用它,你就调用它。它负责确保满足其不变量;让它发挥作用。

现在,在这种 特定 的情况下,您不需要这样做任何。你不写一个初始化方法,你当然不调用基。如果你想要字段初始化器,那么你编写字段初始化器。

class Vehicle { protected bool moves = true; }
class Airplane : Vehicle { protected bool flies = true; }
class Jet : Airplane { protected double speed = 400; }

让我们考虑一个更好的例子。假设我们有一个可以重绘的显示表面:

interface IUserInterface { void Redraw(); }

现在我们可以提出一个层次结构,其中规则是:当你被重绘时,你还必须调用你的超类:

abstract class ControlWithChildren : IUserInterface
{
  ...
  protected IEnumerable<IUserInterface> children;
  public virtual void Redraw() { 
    foreach(var child in children) child.Redraw();
}

class Panel : ControlWithChildren
{
  ...
  public override void Redraw()
  {
    ... redraw panel elements ...
    base.Redraw();
  }
}

class PanelWithTitle : Panel
{
  ...
  public override void Redraw()
  {
    ... redraw title element ...
    base.Redraw();
  }
}

然后就可以了。 PanelWithTitle 只处理重绘标题,并将其余工作交给 Panel。 Panel 重绘面板背景,并将其余工作交给 ControlWithChildren。 PanelWithTitle 不需要知道 ControlWithChildren 的重绘语义。如果它确实需要知道这一点,那么 Panel 的实现有问题。

【讨论】:

  • 我认为你误读了他的问题。他在问如何调用parent 方法。所陈述的问题是父方法被跳过并且祖父方法被调用。至少我认为他是这么说的。
  • @RufusL:当然,他这么说,没有证据,也没有编译或运行的代码。问题是:在存在大基类的世界中调用基类方法的最佳实践是什么,这个问题表明对类与其大基类之间的关系可能存在不良心态。这里更重要的不是 OP 在未显示的损坏代码中修复了他们的错字。最重要的是他们开始正确思考类型层次结构。
【解决方案2】:

我会创建接口并远离继承。改用合成。换句话说,让 Jet 同时包含 Airplane 和 Vehicle 类(我假设 Jet 具有 Airplane 没有的特殊能力或属性)。并使用委托方法。

它使您的项目不那么脆弱。换句话说,如果你改变你的基类,你必须担心你所有的孩子(甚至可能是曾孙)如何处理它。但是,如果你包含它,只要接口相同,容器类就不会在意。

这并没有违背 OOP 的目的,而是一种更好的使用方式。我们仍然有这些单独的对象,而不是复制粘贴代码。然而,我们正在反对使用继承。

我建议您阅读这篇以星球大战为主题的娱乐博客文章。 * http://blog.berniesumption.com/software/inheritance-is-evil-and-must-be-destroyed/

如果这不是你的事,只需谷歌“组合胜过继承”并享受烈火大战。

【讨论】:

  • 飞机上有载具?这是货机吗?
  • “我假设喷气式飞机具有飞机没有的特殊能力或属性”。它有一个喷气发动机。
  • 你不懂组合的概念。在建模 has a 关系时使用组合,而不是 is a 关系。此外,您不只是丢弃继承,您还可以使用 with 组合。飞机是交通工具,喷气机是飞机。在一个适当组合的类中,您将仍然拥有车辆和飞机,但飞机只是定义了构成它的部件的组合,因此喷气式飞机只需交换部分组合.但是如果喷气式飞机比飞机有更多的功能,你就需要创建一个新的类。您链接中的人甚至显示了这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-14
  • 2010-09-22
  • 1970-01-01
  • 2017-04-19
  • 2021-12-24
  • 1970-01-01
相关资源
最近更新 更多