Как гарантировать, что наследование классов не нарушит контракт базового класса в контексте LSP?

На C#, но может быть применим и к другим языкам, в контексте LSP Как я могу гарантировать, что класс, наследуемый от другого (изменяемого), не нарушит исходный контракт?

Например: если у меня есть установщики свойств public, internal или protected, существует риск того, что наследование классов нарушит исходный контракт. То же самое применимо для virtual методов.

Это особенно верно, если владелец родительского и наследующего классов не являются одними и теми же лицами, поскольку это может привести к недостатку знаний в контракте и в намерении исходного разработчика.

Является ли неизменность единственным решением или есть другие способы сделать это? Я склонен считать, что наследование соответствует отношению «ведет себя как» вместо отношения «является». Является ли это правильной логической защитой?

Вот пример для иллюстрации:

public class Foo
{
    public virtual void DummyMethod(int dummyParameter)
    {
        if (dummyParameter > 10) { throw new ArgumentOutOfRangeException(); }
    }
}

public class Bar : Foo
{
    public override void DummyMethod(int dummyParameter)
    {
        if (dummyParameter < 0) { throw new InvalidOperationException(); }
    }
}

Я заметил некоторые другие вопросы, касающиеся этой темы (здесь например), но я ищу общие решения или передовой опыт, чтобы не сталкиваться с этими проблемами заранее.


person Guillaume    schedule 09.09.2012    source источник
comment
Не могли бы вы уточнить, что вы подразумеваете под ведет себя как a? В связи с этим, как вы ожидаете, что классы не нарушат контракт при реализации интерфейса? (Вполне возможно, что такие же ответы применимы и к этому случаю...)   -  person Jon Skeet    schedule 09.09.2012
comment
Например, квадрат является прямоугольником, но не ведет себя одинаково (согласно ссылке на статью в моем вопросе).   -  person Guillaume    schedule 09.09.2012
comment
Что касается реализации интерфейса, я не знаю. На самом деле я не думал об этом, так как интерфейс не имеет никакого контракта, кроме его собственной подписи - по крайней мере, в C # - поэтому он применяется компилятором. Например, в классе я могу генерировать исключение в одном из методов, но ничто не указывает на это извне.   -  person Guillaume    schedule 09.09.2012
comment
Как производный класс может нарушить контракт? Публичные и внутренние методы не меняются (они все еще существуют с той же сигнатурой). И поведение должно меняться с каждым производным классом; это полиморфизм. Что вы хотите предотвратить?   -  person Bob Horn    schedule 09.09.2012
comment
Есть только один хороший способ: использовать ключевое слово sealed.   -  person Hans Passant    schedule 09.09.2012
comment
@BobHorn смотрите пример, который я только что добавил. Исключение, созданное в подтипе, не является подтипом ArgumentOutOfRangeException. Если я не владею родительским классом и исключение в родительском классе не задокументировано, есть ли у меня способ избежать такого нарушения контракта?   -  person Guillaume    schedule 09.09.2012
comment
@HansPassant Итак, вы имеете в виду, что, как только наследование разрешено и поведение класса может быть расширено, у нас нет никакого способа избежать нарушения LSP?   -  person Guillaume    schedule 09.09.2012
comment
@Guillaume Это не нарушение LSP. Подтип по-прежнему можно использовать вместо базового типа. Да, поведение меняется, но это нормально. Это полиморфизм.   -  person Bob Horn    schedule 09.09.2012
comment
Я не знаю Java, но я считаю, что вы можете указать типы исключений, которые будут генерироваться в методе. В С# этого нет.   -  person Bob Horn    schedule 09.09.2012


Ответы (1)


Вы можете предотвратить нарушение LSP, используя ключевое слово sealed.

Это означает, что теперь вы вообще не можете наследовать от этого класса, поэтому вам нужно решить, что вы предпочитаете: устранение риска нарушения LSP или свобода наследования и модификации.

Спасибо @Hans Passant за первое упоминание, запечатанное в комментариях.

person Ed Guiness    schedule 20.09.2012