【问题标题】:hiding property from derived class隐藏派生类的属性
【发布时间】:2015-10-10 18:59:58
【问题描述】:

Jon Skeet 在他的视频中曾提出过这个问题 (虽然没有提供答案)。

假设我们有一个名为 Person 的类 并且 Person 类具有 Name 属性

然后我们有另一个班级,间谍。 当然,Spy 是 Person,所以我们将派生自 Person 类。

public class Person
{
    public string Name { get; set; }
}

public class Spy : Person
{

}

我们不希望人们知道间谍的名字,所以我们希望这会产生编译错误:

static void ReportSpy(Spy spy) {
  string name = spy.Name;
}

或者:

static void ReportSpy(Spy spy)
{
   Person spyAsPerson = spy;
   string name = spyAsPerson.Name;
}

我们怎样才能防止这种事情发生?

【问题讨论】:

  • 如果间谍拒绝透露自己的名字,就会导致怀疑他们是间谍,最好是提供一个假名,比如“John Doe”。
  • 删除这个:我们希望它给出一个编译错误。否则,接受的答案不会回答问题。
  • @user1620220 是的,或者那个。 IRL 间谍确实有名字。但可能这个间谍的真正模拟类似于 NetworkStream 示例,其中 Position 不是可以实现的东西。

标签: c# inheritance


【解决方案1】:

在基类Person 中创建Name 属性virtual。在派生的Spy 类中,覆盖属性和getter 中的throw Exception

public class Person
{
    public virtual string Name { get; set; }
}

public class Spy : Person
{
    public override string Name
    {
        get
        {
            throw new Exception("You never ask for a spy's name!");
        }
        set
        {
            base.Name = value;
        }
    }
}

但是,与其抛出异常,我建议类似

get
{
    return "**********";
}

因为它破坏了 LSP(在另一个答案中提到)。这里的意思(只是一个例子)是,我总是可以这样做

Person x = new Spy();

并将其传递给其他方法,可能类似于

void RegisterForNextBallGame(Person p)
{
    playerList.Add(p.Name);
}

这种方法不知道一些间谍在体育场周围漫游,在做一个简单的诚实任务时崩溃!

编辑

澄清一下,这个name=********** 仍然不是一个正确的解决方案。它只会从异常中保存!之后,可能会发现很多人在代码中使用名称为 ********** 的代码,这会导致后来的意外和其他问题。

更好的解决方案就是更好的设计。检查 Nathan 的答案以获得一些提示。

【讨论】:

  • 很好,但这会引发运行时错误,而不是编译错误。
  • 是的,你是对的。但是,这最接近 op 可以在 C# 代码中轻松成功地实现的功能。 .Net AFAIK 不支持简单地从类层次结构中删除属性(给出编译错误)。如果我错了,请纠正我:)
  • 您的 "**********" getter 对于 get-set 字符串属性来说是非常令人惊讶的。总的来说,我不太喜欢惊喜。您正在寻找的例外是NotSupportedException,但如果您仔细阅读IL,就会发现这些是MyDesignIsTerribleExceptions 的子类,应该避免使用。我相信这个问题的答案是正确的设计,而不是“我如何在运行时拍摄自己的脚”。
  • @NathanCooper 我完全同意你的观点,我并不是说这是正确的做法。我已经编辑了我的答案以使其更清楚。但与此同时,很难在 SO 线程中解释正确的设计!当您写ISpy : IPerson 时,对于已经理解它的人来说是有意义的。对于其他人来说,这就像“为什么我要在我的 Spy 中实现 IPerson 只是为了吃午饭!”
【解决方案2】:

如果作为一个人的一部分会泄露你的名字:间谍不是人

让 Spy 从 person 继承会破坏 Liskov substitution principle:一个对象可能会被其子类型替换。

如果间谍不透露他们的姓名,他们不应该是您设计中的人物。也许你可以设计不同的:

public interface IPerson
{
    void EatLunch();
}

public interface INameDisclosingPerson : IPerson
{
    string Name {get; set; }
}

public interface ISpy : IPerson
{
    void DrinkCocktail();
    Package MakeDrop();
}

现实世界中这种糟糕设计的一个例子是NetworkStream。它通过抛出 NotSupportedException 来实现 Position 属性。因此,您为Stream 编写的代码可能会在运行时为NetworkStream 中断。我也不喜欢这个。一条设计指南:错误的东西应该在编译时中断,从它们无法实现的接口继承的对象是可怕的。

【讨论】:

    【解决方案3】:

    你不能。如前所述,您可以在访问 Spy 的 Name 属性时抛出异常,但这仍然可以编译。而且,也已经提到过,这将打破 Liskov 替换原则,我想补充一下,也打破了开闭原则。

    【讨论】:

      【解决方案4】:

      您可以使用new关键字隐藏基类方法或属性:

      public class Person
      {
          public string Name { get; set; }
      }
      
      public class Spy : Person
      {
          public new string Name
          {
              get { throw new InvalidOperationException(); }
          }
      }
      

      它不会给你编译错误,如果你转换,你仍然可以访问基类Name 属性。

      如果您可以修改基类,请将属性更改为虚拟。然后你可以在派生类和类中重写它,即使是多态调用也可以抛出异常。

      所有这些都将在运行时起作用,而在编译时您无能为力。

      正如其他人在回答中提到的那样,这种设计违反了 Liskov 替换原则,应该避免。

      【讨论】:

      • 因此,如果我有一个名为IntegrateForName 的方法采用Spy,它们将永远不会破解。但是,如果我将Spy 传递给采用PersonAsknicelyForName 方法,他们会告诉我一切吗?我想这只是表明酷刑永远行不通。
      • 很好的例子。嗯,你不能用当前的设计做更多的事情。下一步是意识到在这种情况下,间谍不是一个人(就像你在回答中写的那样)。
      猜你喜欢
      • 2010-11-27
      • 1970-01-01
      • 1970-01-01
      • 2013-06-03
      • 2011-09-29
      • 2010-11-23
      • 2015-04-09
      • 1970-01-01
      相关资源
      最近更新 更多