Jon Skeet在他的视频中提到了这个问题(虽然没有提供答案).
假设我们有一个名为Person的类,而Person类具有Name属性
然后我们有另一个班,间谍.当然,间谍是一个人,所以我们将从Person类派生.
public class Person
{
public string Name { get; set; }
}
public class Spy : Person
{
}
Run Code Online (Sandbox Code Playgroud)
我们不希望人们知道Spy的名字,所以我们希望这会给出一个编译错误:
static void ReportSpy(Spy spy) {
string name = spy.Name;
}
Run Code Online (Sandbox Code Playgroud)
或者:
static void ReportSpy(Spy spy)
{
Person spyAsPerson = spy;
string name = spyAsPerson.Name;
}
Run Code Online (Sandbox Code Playgroud)
我们怎么能防止这种事情发生呢?
使Name财产virtual在基Person类.在派生Spy类中,覆盖属性和throw Exceptiongetter.
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
但是,我建议不要抛出异常
get
{
return "**********";
}
Run Code Online (Sandbox Code Playgroud)
因为,它打破了LSP(在另一个答案中提到).这意味着什么(只是一个例子),我总是可以这样做
Person x = new Spy();
Run Code Online (Sandbox Code Playgroud)
并将其传递给其他方法,可能就像
void RegisterForNextBallGame(Person p)
{
playerList.Add(p.Name);
}
Run Code Online (Sandbox Code Playgroud)
这种方法不知道体育场周围的一些间谍漫游,在做一个简单的诚实任务时崩溃!
编辑
只是要清楚,这name=**********是还没有一个合适的解决方案.它只会从异常中拯救!之后,人们可能会发现许多人在名称为**********的代码中走下来,这将导致后来的惊喜和其他问题.
更好的解决方案是更好的设计.检查内森的答案以获得一些暗示.
如果作为一个人的一部分透露了你的名字:间谍不是人
让间谍继承人破坏了Liskov替换原则:一个对象可以用它的子类型替换.
如果Spys没有透露他们的名字,他们就不应该是您设计环境中的人物.也许您可以设计不同的方式:
public interface IPerson
{
void EatLunch();
}
public interface INameDisclosingPerson : IPerson
{
string Name {get; set; }
}
public interface ISpy : IPerson
{
void DrinkCocktail();
Package MakeDrop();
}
Run Code Online (Sandbox Code Playgroud)
现实世界中这种糟糕设计的一个例子是NetworkStream.它Position通过抛出NotSupportedException来实现该属性.因此,您为a编写的代码Stream可能会在运行时中断NetworkStream.我也不是这个的粉丝.一个设计指南:错误的东西应该在编译时破坏,从它们无法实现的接口继承的对象是可怕的.