C# 这是否违反了Liskov替换原则,如果是,我该怎么办?
用例:我正在使用数据模板将视图与ViewModel匹配。数据模板通过检查所提供的具体类型中最派生的类型来工作,它们不查看它提供的接口,所以我必须在没有接口的情况下完成这项工作 我在这里简化了这个示例,省略了NotifyPropertyChanged等,但在现实世界中,视图将绑定到文本属性。为简单起见,假设具有TextBlock的视图将绑定到ReadOnlyText,而具有TextBox的视图将绑定到WritableTextC# 这是否违反了Liskov替换原则,如果是,我该怎么办?,c#,wpf,mvvm,liskov-substitution-principle,C#,Wpf,Mvvm,Liskov Substitution Principle,用例:我正在使用数据模板将视图与ViewModel匹配。数据模板通过检查所提供的具体类型中最派生的类型来工作,它们不查看它提供的接口,所以我必须在没有接口的情况下完成这项工作 我在这里简化了这个示例,省略了NotifyPropertyChanged等,但在现实世界中,视图将绑定到文本属性。为简单起见,假设具有TextBlock的视图将绑定到ReadOnlyText,而具有TextBox的视图将绑定到WritableText class ReadOnlyText { private str
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并更改其行为,我是否违反了?如果是这样的话,还有什么更好的方法呢?只有在
ReadOnlyText.OnTextSet()
的规范承诺抛出时
想象一下这样的代码
public void F(ReadOnlyText t, string value)
{
t.OnTextSet(value);
}
如果这个没有扔出去,对你有意义吗?如果不是,那么WritableText是不可替代的
在我看来,
可写文本
应该继承自文本。如果在ReadOnlyText
和WritableText
之间有一些共享代码,请将其放入Text或它们都从中继承的另一个类中(从Text
继承的类)我认为这取决于合同
如果ReadOnlyText的合同中说“任何设置文本的尝试都会引发异常”,那么您肯定违反了LSP
否则,代码中仍然有一个尴尬之处:只读文本的setter
在特定情况下,这是可接受的“非规范化”。我还没有找到一种不依赖大量代码的更好方法。在大多数情况下,干净的界面是:
IThingieReader
{
string Text { get; }
string Subtext { get; }
// ...
}
IThingieWriter
{
string Text { get; set; }
string Subtext { get; set; }
// ...
}
…并仅在适当时实施接口。但是,如果您必须处理例如
文本
可写而子文本
不可写的实例,那么这种情况就会发生,并且对于许多对象/属性来说,这是一件很痛苦的事情。是的,如果受保护的覆盖void OnTextSet(字符串值)也引发了类型为“InvalidOperationException”的异常,则不会发生这种情况或者从中继承
您应该有一个基类Text,并且从中继承ReadOnlyText和WritableText。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);
}
}
需要明确的是,我们在可写类的Text属性上使用new关键字对可读类隐藏Text属性。发件人:
使用new关键字时,将调用新的类成员,而不是已替换的基类成员。这些基类成员称为隐藏成员。如果派生类的实例被强制转换为基类的实例,则仍然可以调用隐藏的类成员 (对于那些还没有喝咖啡的人)@SomeMiscGuy:对不起,添加了链接:)顺便说一句,可以使用DataTemplateSelector基于实现接口的类解析数据模板。这一个对我来说很好:正如我所说的,这将是理想的,但我不能使用接口,因为数据模板不会关闭接口,它们需要一个具体的类型。这不太适合编译,但可以修改以工作。我不知道可以在派生类中为基类中具有getter的属性定义属性setter。我学到了一些新东西。这会奏效的。谢谢有趣的是,我需要将派生类中的Text属性声明为“new”以避免编译器警告,但在我看来,这并不是真正的新属性,因为我没有重写getter。我不相信您希望SetText和OnTextSet被公开。我编辑了文章以包含new关键字。C#告诉我们,我们将文本属性隐藏在Readable类中,因为该属性仅为Readable(getter),但在Writeable类中,该属性为Readable(getter)和Writeable(setter)。@Steve Ellinger:True,在我的实现中,基类负责更新属性,并在业务逻辑更改值时通知视图。根据我的具体问题,您是正确的,但Brandon指出了我对属性设置器和获取器的误解,这使我能够更优雅地解决问题。谢谢你的信息。