4

用例:我正在使用数据模板将 View 与 ViewModel 匹配。数据模板通过检查提供的具体类型的最派生类型来工作,它们不查看它提供的接口,所以我必须在没有接口的情况下这样做。

我在这里简化了示例并省略了 NotifyPropertyChanged 等,但在现实世界中,视图将绑定到 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?如果是这样,有什么更好的方法呢?

4

4 回答 4

9

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 关键字时,将调用新的类成员而不是已替换的基类成员。这些基类成员称为隐藏成员。如果派生类的实例被强制转换为基类的实例,隐藏的类成员仍然可以被调用。

于 2010-10-22T13:03:31.453 回答
8

仅当规范ReadOnlyText.OnTextSet()承诺抛出时。

想象一下这样的代码

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

如果这没有抛出,对你有意义吗?如果不是,则 WritableText 不可替代。

在我看来,它WritableText应该继承自 Text。ReadOnlyText如果and之间有一些共享代码WritableText,请将其放在 Text 或它们都继承自(继承自Text)的另一个类中

于 2010-10-22T12:47:38.167 回答
2

我会说取决于合同。

如果 ReadOnlyText 的合同说“任何设置 Text 的尝试都会引发异常”,那么您肯定违反了 LSP。

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

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

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

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

...并仅在适当的时候实现接口。但是,如果您必须处理例如Text可写和Subtext不可写的实例,并且对于许多对象/属性来说这是一件痛苦的事情,那么这就会崩溃。

于 2010-10-22T12:48:30.553 回答
0

是的,如果受保护的覆盖 void OnTextSet(string value) 也引发了类型为“InvalidOperationException”或继承自它的异常,则不会。

您应该有一个基类 Text 并且 ReadOnlyText 和 WritableText 都继承自它。

于 2010-10-22T12:57:46.583 回答