Является ли переопределение метода единственным способом нарушить LSP?

Я искал признаки в коде, которые могли бы сказать, что принцип подстановки Лискова может быть потенциально нарушен. Сначала я сделал простой класс и еще один класс, унаследованный от него:

public class Adder
{
    public virtual int Add(int operand1, int operand2)
    {
        return operand1 + operand2;
    }
}

public class AdvancedAdder : Adder
{
}

Затем я создал UnitTest, обнаруживающий нарушение LSP:

public abstract class AdderUnitTestsBase
{
    protected static Adder Adder;

    [DataTestMethod]
    [DataRow(2, 2, 4)]
    [DataRow(-1, 2, 1)]
    [DataRow(2, -3, -1)]
    [DataRow(0, 0, 0)]
    public void Add_ReturnsCorrectResult(
        int operand1, int operand2, int result)
    {
        Assert.AreEqual(result, Adder.Add(operand1, operand2));
    }
}


[TestClass]
public class AdderUnitTests : AdderUnitTestsBase
{
    [ClassInitialize]
    public static void ClassInit(TestContext context)
    {
        Adder = new Adder();
    }
}

[TestClass]
public class AdvancedAdderUnitTests : AdderUnitTestsBase
{
    [ClassInitialize]
    public static void ClassInit(TestContext context)
    {
        Adder = new AdvancedAdder();
    }
}

Затем я пробовал разные вещи, такие как изменение типа параметров или изменение количества параметров:

public class AdvancedAdder : Adder
{
    public int Add(int operand1, int operand2, int operand3)
    {
        return operand1 + operand2 + operand3;
    }

    public uint Add(uint operand1, uint operand2)
    {
        return operand1 + operand2;
    }
}

Это не нарушило LSP, потому что методы имеют разные сигнатуры. Это просто расширенный функционал. Единственное, что сработало, это использование ключевого слова «переопределить»:

public class AdvancedAdder : Adder
{
    public override int Add(int operand1, int operand2)
    {
        return operand1 - operand2;
    }
}

Похоже, что если я избегаю "переопределения" в C#, мне не нужно беспокоиться о возможном нарушении принципа подстановки Лискова. Как вы думаете, это правильно?


person Dmitry Kh    schedule 18.12.2018    source источник
comment
Да, если вы ничего не заменяете, вы не можете нарушить принцип замещения.   -  person Ralf    schedule 18.12.2018


Ответы (2)


Переопределение — это просто техническое решение переопределения метода. LSP больше касается семантики, чем техники.

Похоже, что если я избегаю «переопределения» в С#, мне не нужно беспокоиться о возможном нарушении принципа подстановки Лискова.

Возможно, вы имеете в виду полиморфизм, потому что override — это всего лишь один из способов изменить поведение объектов (динамическое связывание). Вы также можете использовать new для методов, отличных от virtual (статическая привязка). Вы даже можете использовать проверку (как частный случай отражения) и изменить поведение на основе этого:

public void method()
{
    switch (this.getType().Name)
    {
        case "Square":
            // do the square thing
            break;
        case "Rectangle":
            // do the rectangle thing
            break;
    }
}

Определенный таким образом метод вполне может сломать LSP без наследования или ключевых слов override/new.

Это действительно сложнее, чем простой синтаксис, и больше касается семантики (значения).

Даже классический пример LSP с квадратами/прямоугольниками основан на значении. Если вы думаете, что квадрат/прямоугольник — это просто слова без смысловой подложки, у вас нет никаких ожиданий относительно их поведения. В результате у вас нет оснований полагать, что вы можете заменить одно на другое и не сможете сломать LSP.

С другой стороны, наследование — это кодовая конструкция, которая говорит вам:

PARENT
  ^
  |
CHILD

Ребенок может заменить родителя, потому что он наследует все поведение.

person pid    schedule 18.12.2018

Также стоит упомянуть, что LSP не следует рассматривать как строгое правило, которое вы должны соблюдать в любой ситуации.

Например, составной шаблон (https://en.wikipedia.org/wiki/Composite_pattern) может предоставить вам механизм для одинаковой обработки всех узлов (что хорошо). Но если вы интерпретируете LSP строго, это нарушит LSP.

Как и большинство объектно-ориентированных правил и шаблонов проектирования, это всего лишь рекомендация, которая должна указать вам правильное направление, но может быть отвергнута другими требованиями. В конце концов, общая сложность архитектуры должна уменьшаться, а не увеличиваться. Здесь составной шаблон, например, позволит вам обрабатывать объекты более единообразным образом и предоставлять более простые алгоритмы.

По самому вашему вопросу. Если вы также рассматриваете обмен сообщениями для объектно-ориентированного дизайна, то у вас также будут категории объектов, которые обрабатывают определенное сообщение/событие, тогда это также способ сломать LSP (вместо использования только виртуального и переопределения).

В конце концов, вы должны учитывать, что истина всегда где-то посередине.

person Jürgen    schedule 19.12.2018