【问题标题】:Alternatives to interface reimplementation接口重新实现的替代方案
【发布时间】:2021-10-26 04:28:19
【问题描述】:

我正在阅读 Joe Albahari 出色的“C# 9 in a Nutshell”的以下摘录,并试图理解这里用粗体字描述的内容。有没有人能够以我能更好理解的方式解释替代方法?出于某种原因,这对我来说似乎有些落后。

接口重新实现的替代方案 即使使用显式成员实现,接口重新实现也存在问题,原因有以下几个:

子类没有办法调用基类方法。

基类作者可能没有预料到方法会被重新实现,并且可能不会考虑到潜在的后果。

当没有预期子类化时,重新实现可能是一个很好的最后手段。然而,更好的选择是设计一个基类,这样就永远不需要重新实现。有两种方法可以实现:

当隐式实现成员时,如果合适,将其标记为虚拟。

显式实现成员时,如果您预计子类可能需要覆盖任何逻辑,请使用以下模式:

public class TextBox : IUndoable
{
  void IUndoable.Undo()         => Undo();    // Calls method below
  protected virtual void Undo() => Console.WriteLine ("TextBox.Undo");
}

public class RichTextBox : TextBox
{
  protected override void Undo() => Console.WriteLine("RichTextBox.Undo");
}

如果您不期望任何子类化,您可以将该类标记为密封以抢占接口重新实现。

【问题讨论】:

标签: c# interface class-hierarchy interface-implementation


【解决方案1】:

据我了解,作者想说的是,与其隐藏基类的成员,不如重写它们。隐藏是子类有一个具有相同签名的成员,但在基类中没有标记为虚拟。 即如果 ParentClass 有方法 A();如果您希望它被 ChildClass 覆盖,请将 A() 设置为虚拟方法。或者,如果您不希望 A() 被覆盖,请引入一个受保护的方法,该 A() 将使用该受保护方法并将该受保护方法设为虚拟,以便孩子在需要时进行覆盖。 希望这能回答您的问题。

【讨论】:

  • 这确实回答了我的问题。谢谢!粗体部分对我来说没有意义,因为我不太明白作者是如何定义显式和隐式实现的。这是我对我读到的内容的解释。
  • 很高兴有帮助。是的,这有点令人困惑,尤其是在没有定义所用术语的情况下。它是以抽象的方式编写的,这可能并带来混乱。当困难的事情以简单的方式描述时,它是美丽的。在这种情况下,一件简单的事情被描述得很艰难:)
【解决方案2】:

所描述的问题是,当基类实现接口时,该基类可能会发生某些行为。

文本框撤消的示例。假设基本文本框为撤消做了一些重要的事情(或者可能是实际的工作)。 也为了举例,假设作者在写基类的时候没有想到继承类(这里是richtextbox)

他可以这样写

public class TextBox : IUndoable
{
  void IUndoable.Undo() => ....... undo logic here
}

任何继承类都不能直接调用Undo(他们必须将其基类转换为IUndoable) 现在的问题是,当其他代码使用 IUndo 实现(例如菜单项)时,不会调用基类中的重要撤消,除非继承类明确这样做

还是一样:

public class TextBox : IUndoable
{
  public void Undo() => ...... undo logic here
}

继承的 (RichTextBox) 类,可以在显式实现 Undo 时调用它的基类,但不能确定。 如果 RichTextBox 自己做 Undo 并隐藏了基础 Undo,就没有直接的冲动去调用基础 Undo(虽然比第一个选项更多)

最后,基类作者想要的是,当外部代码调用IUndoable.Undo 方法时,总是调用所需的代码。 (旁注:在一个可以以不同方式处理的抽象类中,但这是一个直接可用的类,可能会或可能不会被继承)

使隐式实现虚拟化会有所帮助:

public class TextBox : IUndoable
{
  public virtual void Undo() => ...... undo logic here
}

一旦继承类覆盖了方法,默认的 sn-p 也会调用基类,但这是假设使用了 sn-p,并且仍然调用 base.Undo,将选项留给继承者。

这给出了您包含的最后一个示例:

public class TextBox : IUndoable
{
  void IUndoable.Undo()         => Undo();    // Calls method below
  protected virtual void Undo() => Console.WriteLine ("TextBox.Undo");
}

这为继承类提供了一个更安全的选项来实现它们自己的撤消,而不会丢失基类的撤消(除非继承类也显式实现IUndoable,但我们不要去那里:P)

在 Richtextbox 示例中,Undo 被覆盖,但基础 Undo 被调用。正是那种可能导致“撤消”的基类实现被跳过的情况。 但是在这里,如果某些东西(例如菜单项)调用了IUndoable 中的Undo,则会调用显式实现,从而强制运行所需的基类撤消功能,以及继承类(richtextbox)的覆盖实现

【讨论】:

  • 我认为这是一个很好的解释。谢谢你,我的名字!
  • 我认为我最初的困惑与本章前面如何讨论显式和隐式实现有关。我认为我不太明白作者在谈论继承与子类。这与其说是作者的解释,不如说是我把他解释的错误部分联系在一起。
  • 不客气。无论是作者的措辞还是您的解释,这就是使 stackoverflow 之类的地方很棒的原因,能够讨论这些事情:)
【解决方案3】:

是否有人能够以我可以的方式解释替代方法 更好理解?

它只是说,如果你尝试执行我下面的代码,它将无法编译。

那么,如果您需要使用显式接口实现并且一个继承类需要改变该行为,您该怎么办?好吧,您使用您在问题中提到的 exact 代码。它允许你使用显式接口实现并且允许它被覆盖。

就是这样。就是这么说的。

using System;
        
public interface IUndoable {
    void Undo();
}


public class TextBox : IUndoable
{
  void IUndoable.Undo()         => Console.WriteLine ("TextBox.Undo");
}

public class RichTextBox : TextBox
{
    void IUndoable.Undo()         => Console.WriteLine ("RichTextBox.Undo");

}

public class Program
{
    public static void Main()
    {
    }
}

【讨论】:

  • 这也是一个简洁的答案。谢谢mjwills!
猜你喜欢
  • 2012-04-24
  • 1970-01-01
  • 1970-01-01
  • 2011-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-10
  • 1970-01-01
相关资源
最近更新 更多