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

时间:2010-10-22 12:37:27

标签: c# wpf mvvm liskov-substitution-principle

使用案例:我正在使用数据模板将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?如果是这样,有什么更好的方法呢?

4 个答案:

答案 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继承。如果ReadOnlyTextWritableText之间存在一些共享代码,请将其放在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。