Нарушает ли это принцип подстановки Лискова, и если да, то что мне с этим делать? - PullRequest
4 голосов
/ 22 октября 2010

Вариант использования: я использую шаблоны данных для сопоставления View с ViewModel.Шаблоны данных работают путем проверки наиболее производного типа конкретного предоставленного типа, и они не смотрят, какие интерфейсы он предоставляет, поэтому я должен сделать это без интерфейсов.

Я упрощаю пример здесь иопуская NotifyPropertyChanged и т. д., но в реальном мире представление будет привязываться к свойству Text.Для простоты представьте, что представление с TextBlock связывается с ReadOnlyText, а представление с TextBox связывается с 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 ]

9 голосов
/ 22 октября 2010

LSP заявляет, что подкласс должен быть замещаемым для своего суперкласса (см. Вопрос stackoverflow здесь ).Вопрос, который нужно задать себе: «Является ли текст для записи типом readonly текста?»Ответ однозначно «нет», на самом деле они взаимоисключающие.Так что да, этот код нарушает LSP.Однако является ли текст для записи типом читабельного текста (не только для чтения)?Ответ "да".Поэтому я думаю, что ответ заключается в том, чтобы посмотреть, что вы пытаетесь сделать в каждом конкретном случае, и, возможно, немного изменить абстракцию следующим образом:свойство Text из класса Readable с использованием нового ключевого слова в свойстве Text в классе Writeable.
From http://msdn.microsoft.com/en-us/library/ms173152(VS.80).aspx: Когда используется новое ключевое слово, новые члены класса вызываются вместо членов базового класса, которыебыли заменены.Эти члены базового класса называются скрытыми членами.Скрытые члены класса по-прежнему можно вызывать, если экземпляр производного класса приведен к экземпляру базового класса.

8 голосов
/ 22 октября 2010

Только если спецификация ReadOnlyText.OnTextSet() обещает выдать.

Представьте себе код, подобный этому

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

Имеет ли для вас смысл, если это не выдает?Если нет, то WritableText не является заменяемым.

Мне кажется, WritableText должно наследоваться от Text.Если между ReadOnlyText и WritableText есть некоторый общий код, поместите его в Text или в другой класс, от которого они оба наследуют (который наследует от Text)

2 голосов
/ 22 октября 2010

Я бы сказал, что это зависит от контракта.

Если в контракте для ReadOnlyText говорится «любая попытка установить Text приведет к исключению», вы, безусловно, нарушаете LSP., у вас все еще есть неловкость в вашем коде: установщик для текста только для чтения.

Это приемлемая «денормализация» при данных обстоятельствах.Я еще не нашел лучшего способа, который не основывается на большом количестве кода.Чистый интерфейс будет в большинстве случаев:

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

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

... и реализовывать интерфейсы только при необходимости.Однако это ломается, если вам приходится иметь дело с случаями, когда, например, Text доступен для записи, а Subtext - нет, и это трудная задача для многих объектов / свойств.

0 голосов
/ 22 октября 2010

Да, не будет, если защищенное переопределение void OnTextSet (строковое значение) также сгенерировало исключение типа «InvalidOperationException» или унаследовано от него.

У вас должен быть базовый класс Textи оба ReadOnlyText и WritableText наследуются от него.

...