【问题标题】:Does this violate the Liskov substitution principle, and if so, what do I do about it?这是否违反了 Liskov 替换原则,如果是,我该怎么办?
【发布时间】:2010-10-22 12:37:27
【问题描述】:

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

【问题讨论】:

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


【解决方案1】:

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 类的 Text 属性。
来自http://msdn.microsoft.com/en-us/library/ms173152(VS.80).aspx: 当使用 new 关键字时,将调用新的类成员而不是已替换的基类成员。这些基类成员称为隐藏成员。如果派生类的实例被强制转换为基类的实例,隐藏的类成员仍然可以被调用。

【讨论】:

  • 这并不完全编译,但可以修改工作。我不知道您可以在派生类中为具有基类中的 getter 的属性定义属性设置器。我学到了一些新东西。这将起作用。谢谢!
  • 有趣的是,我需要将派生类中的 Text 属性声明为“新”以避免编译器警告,但在我看来这并不是真正的新属性,因为我没有覆盖 getter。跨度>
  • 我不相信您希望 SetText 和 OnTextSet 公开
  • 我编辑了帖子以包含新关键字。 C# 告诉我们,我们将 Text 属性隐藏在 Readable 类中,因为该属性是只读的(getter),但在 Writeable 类中,该属性是可读的(getter)和可写的(setter)。
  • @Steve Ellinger:没错,在我的实现中,基类处理更新属性并在业务逻辑更改值时通知视图。
【解决方案2】:

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

想象一下这样的代码

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

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

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

【讨论】:

  • 根据我的具体问题,您是正确的,但 Brandon 指出了我对属性设置器和获取器的误解,这让我能够更优雅地解决问题。感谢您的信息。
【解决方案3】:

我会说取决于合同。

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

如果不是,您的代码中仍然有一个笨拙之处:只读文本的设置器。

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

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

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

...并且仅在适当的时候实现接口。但是,如果您必须处理例如Text 是可写的,Subtext 是不可写的,对于许多对象/属性来说,这样做很痛苦。

【讨论】:

  • 正如我所说,这将是理想的,但我不能使用接口,因为数据模板不会关闭接口,它们需要具体类型。
【解决方案4】:

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

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

【讨论】:

    猜你喜欢
    • 2015-01-01
    • 2020-02-03
    • 2012-01-12
    • 1970-01-01
    • 2017-07-04
    • 2021-11-17
    • 1970-01-01
    • 2011-09-09
    • 1970-01-01
    相关资源
    最近更新 更多