2010-10-22 96 views
4

使用案例:我正在使用数据模板将视图与ViewModel进行匹配。数据模板通过检查所提供的具体类型的最派生类型来工作,并且它们不查看它提供的接口,所以我必须在没有接口的情况下执行此操作。这是违反Liskov替代原则吗?如果是这样,我该怎么办?

我在这里简化了这个例子,而忽略了NotifyPropertyChanged等,但在现实世界中,一个View将绑定到Text属性。为了简单起见,假设带有TextBlock的视图将绑定到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

http://en.wikipedia.org/wiki/Liskov_substitution_principle(对于那些还没有喝过咖啡的人) – 2010-10-22 12:41:20

+0

@SomeMiscGuy:对不起,添加链接:) – 2010-10-22 12:42:49

+0

顺便说一下,有可能解决一个基于数据模板在实现接口的类上使用DataTemplateSelector。这对我来说效果很好:http://complexdatatemplates.codeplex.com/ – 2010-10-22 13:11:36

回答

9

LSP指出一个子类应该是它的超类的子类(参见stackoverflow问题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类获取属性。
http://msdn.microsoft.com/en-us/library/ms173152(VS.80).aspx: 使用new关键字时,会调用新的类成员,而不是已被替换的基类成员。这些基类成员被称为隐藏成员。如果将派生类的实例转换为基类的实例,则仍然可以调用隐藏类成员。

+0

这不完全编译,但可以修改工作。我不知道你可以在派生类中为基类中的getter属性定义属性设置器。我学到了一些新东西。这将工作。谢谢! – 2010-10-22 13:05:18

+0

有趣的是,我需要将派生类中的Text属性声明为“new”以避免编译器警告,但在我看来,它并不是真正新的,因为我不重写getter。 – 2010-10-22 13:18:33

+0

我不相信你想让SetText和OnTextSet公开 – 2010-10-22 13:19:29

8

只有ReadOnlyText.OnTextSet()的规格承诺会抛出。

想像这样

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

代码是否有意义给你,如果这个没扔?如果不是,则WritableText不可替代。

它看起来像WritableText应该从文本继承。如果有ReadOnlyTextWritableText之间的一些共享代码,把它放在文本或其它类,它们都继承(从Text继承)

+0

根据我的具体问题你是正确的,但布兰登指出了我对属性设置者和获取者的误解,这让我更加优雅地解决了这个问题。谢谢(你的)信息。 – 2010-10-22 13:12:09

2

我想说取决于合同。

如果ReadOnlyText的合同显示“任何设置文本的尝试都会抛出异常”,那么肯定是违反了LSP。

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

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

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

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

...并仅在适当的时候实现接口。然而,如果你必须处理例如Text是可写的,并且Subtext不是,对许多对象/属性来说这是一件很痛苦的事情。

+0

正如我所说,这将是理想的,但我不能使用接口,因为数据模板不会关闭接口,他们需要一个具体的类型。 – 2010-10-22 12:59:32

0

是的,它不会,如果受保护的覆盖无效的OnTextSet(字符串值)也抛出了一个类型为“InvalidOperationException”或从它继承的异常。

你应该有一个基类Text和ReadOnlyText和WritableText都从它继承。

相关问题