隐藏派生类的属性
本文关键字:属性 派生 隐藏 | 更新日期: 2023-09-27 18:17:00
Jon Skeet曾经在他的视频中提到过这个问题(虽然没有给出答案)。
假设我们有一个名为Person的类Person类有Name属性
然后我们有另一个类,Spy。当然,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;
}
我们怎样才能防止这种事情的发生?
使Name
属性成为virtual
的基Person
类。在派生的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=**********
是仍然不是一个正确的解决方案。它只会从异常中保存!之后,您可能会发现许多person以**********的名称遍历代码,这将导致后续的意外和其他问题。
更好的解决方案是更好的设计。查看Nathan的答案,得到一些提示。
如果作为一个人的一部分是透露你的名字:间谍不是人
从person继承Spy违反了Liskov替换原则:对象可以用它的子类型替换。
如果Spys不公开他们的名字,那么在你的设计中他们就不应该是People。也许你可以用不同的方式来设计:
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
运行时中断。我也不喜欢这个。一个设计指导:错误的东西应该在编译时被破坏,从接口继承的对象不能实现是可怕的。
你不能。如前所述,在访问Spy的Name属性时可以抛出异常,但这仍然可以编译。而且,也已经提到过,这会打破利斯科夫替换原则,我想补充一下,也会打破开闭原则。
您可以使用new
关键字隐藏基类方法或属性:
public class Person
{
public string Name { get; set; }
}
public class Spy : Person
{
public new string Name
{
get { throw new InvalidOperationException(); }
}
}
它不会给你编译错误,如果你强制转换,你仍然可以访问基类Name
属性。
如果可以修改基类,则将属性更改为virtual。然后你可以在派生类和类中重写它,甚至在多态调用中抛出异常。
所有这些都将在运行时工作,在编译时你不能做任何事情。
正如其他人在他们的回答中提到的,这样的设计打破了Liskov替代原则,应该避免。