这是否违反Liskov替代原则,如果是这样,我该怎么办呢?

使用案例:我正在使用数据模板将View与ViewModel相匹配。 数据模板通过检查所提供的具体类型的最派生类型来工作,并且它们不查看它提供的接口,因此我必须在没有接口的情况下执行此操作。

我在这里简化了示例并省略了NotifyPropertyChanged等,但在现实世界中,View将绑定到Text属性。 为简单起见,假设带有TextBlock的View将绑定到ReadOnlyText,带有TextBox的View将绑定到WritableText。

class ReadOnlyText { private string text = string.Empty; public string Text { get { return text; } set { OnTextSet(value); } } protected virtual void OnTextSet(string value) { throw new InvalidOperationException("Text is readonly."); } protected void SetText(string value) { text = value; // in reality we'd NotifyPropertyChanged in here } } class WritableText : ReadOnlyText { protected override void OnTextSet(string value) { // call out to business logic here, validation, etc. SetText(value); } } 

通过重写OnTextSet并改变其行为,我是否违反了LSP ? 如果是这样,有什么更好的方法呢?

LSP声明子类应该可以替代它的超类(请参阅此处的 stackoverflow问题)。 问自己的问题是,“可写文本是一种只读文本吗?” 答案显然是“不”,实际上这些是相互排斥的。 所以,是的,这段代码违反了LSP。 但是,可写文本是一种可读文本(不是只读文本)吗? 答案是肯定的。 所以我认为答案是看看你在每种情况下尝试做什么,并可能改变抽象,如下所示:

 class ReadableText { private string text = string.Empty; public ReadableText(string value) { text = value; } public string Text { get { return text; } } } class WriteableText : ReadableText { public WriteableText(string value):base(value) { } public new string Text { set { OnTextSet(value); } get { return base.Text; } } public void SetText(string value) { Text = value; // in reality we'd NotifyPropertyChanged in here } public void OnTextSet(string value) { // call out to business logic here, validation, etc. SetText(value); } } 

为了清楚起见,我们使用Writeable类中Text属性上的new关键字从Readable类中隐藏Text属性。
来自http://msdn.microsoft.com/en-us/library/ms173152(VS.80).aspx :使用new关键字时,将调用新的类成员而不是已替换的基类成员。 这些基类成员称为隐藏成员。 如果将派生类的实例强制转换为基类的实例,则仍可以调用隐藏类成员。

只有当ReadOnlyText.OnTextSet()的规范承诺抛出时。

想象一下像这样的代码

 public void F(ReadOnlyText t, string value) { t.OnTextSet(value); } 

如果没有抛出,对你有意义吗? 如果没有,那么WritableText不可替代。

在我看来,像WritableText应该inheritanceText。 如果ReadOnlyTextWritableText之间有一些共享代码,请将它放在Text或另一个inheritance自它的类中(inheritance自Text

我会说取决于合同。

如果ReadOnlyText的合同说“任何设置Text的尝试都会抛出exception”,那么你肯定违反了LSP。

如果没有,你的代码仍然有一个尴尬:一个只读文本的setter。

在特定情况下,这是可接受的“非规范化”。 我还没有找到一种不依赖于大量代码的更好的方法。 在大多数情况下,干净的界面是:

 IThingieReader { string Text { get; } string Subtext { get; } // ... } IThingieWriter { string Text { get; set; } string Subtext { get; set; } // ... } 

…并仅在适当时实现接口。 但是,如果你必须处理例如Text是可写的而Subtext不是这样的实例,则会崩溃,并且很难对许多对象/属性进行处理。

是的确如此,如果受保护的覆盖void OnTextSet(字符串值)也抛出类型为“InvalidOperationException”的exception或从其inheritance的exception,则不会。

你应该有一个基类Text,以及从它inheritance的ReadOnlyText和WritableText。