使用案例:我正在使用数据模板将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?如果是这样,有什么更好的方法呢?
答案 0 :(得分:9)
LSP声明子类应该可以替代它的超类(参见stackoverflow question here)。问自己的问题是,“可写文本是一种只读文本吗?”答案显然是“不”,实际上这些是相互排斥的。所以,是的,这段代码违反了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关键字时,将调用新的类成员而不是已替换的基类成员。这些基类成员称为隐藏成员。如果将派生类的实例强制转换为基类的实例,则仍可以调用隐藏类成员。
答案 1 :(得分:8)
仅当ReadOnlyText.OnTextSet()
的规范承诺抛出。
想象一下像这样的代码
public void F(ReadOnlyText t, string value)
{
t.OnTextSet(value);
}
如果没有抛出,对你有意义吗?如果没有,则WritableText不可替代。
我认为WritableText
应该从Text继承。如果ReadOnlyText
和WritableText
之间存在一些共享代码,请将其放在Text或其他继承自的类中(继承自Text
)
答案 2 :(得分:2)
我会说取决于合同。
如果ReadOnlyText的合同说“任何设置Text的尝试都会抛出异常”,那么你肯定会违反LSP。
如果没有,你的代码仍然有一个尴尬:一个只读文本的setter。
在特定情况下,这是可接受的“非规范化”。我还没有找到一种不依赖于大量代码的更好的方法。在大多数情况下,干净的界面是:
IThingieReader
{
string Text { get; }
string Subtext { get; }
// ...
}
IThingieWriter
{
string Text { get; set; }
string Subtext { get; set; }
// ...
}
...仅在适当时实现接口。但是,如果你必须处理例如Text
是可写的,Subtext
不可写,并且对许多对象/属性来说都很痛苦。
答案 3 :(得分:0)
是的,如果受保护的覆盖void OnTextSet(字符串值)也引发了类型为“InvalidOperationException”或从其继承的异常,则不会。
你应该有一个基类Text,继承自它的ReadOnlyText和WritableText。