为什么这个 class 层次结构违反了 LSP?

Why does this class hierarchy violate LSP?

以下是在线教程 (https://code-maze.com/liskov-substitution-principle/) 中的代码:

// version 1
public class SumCalculator
{
    protected readonly int[] _numbers;

    public SumCalculator(int[] numbers)
    {
        _numbers = numbers;
    }

    public virtual int Calculate() => _numbers.Sum();
}

public class EvenNumbersSumCalculator: SumCalculator
{
    public EvenNumbersSumCalculator(int[] numbers)
        :base(numbers)
    {
    }

    public override int Calculate() => _numbers.Where(x => x % 2 == 0).Sum();
}

那么我们可以这样做:

class Program
{
    static void Main(string[] args)
    {
        var numbers = new int[] { 5, 7, 9, 8, 1, 6, 4 };

        SumCalculator sum = new SumCalculator(numbers);
        Console.WriteLine($"The sum of all the numbers: {sum.Calculate()}");

        Console.WriteLine();

        SumCalculator evenSum = new EvenNumbersSumCalculator(numbers);
        Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}");
    }
}

所以我们可以将子实例(new EvenNumbersSumCalculator(numbers))存储到父变量(SumCalculator evenSum )中,所以上面的代码符合里氏原则,不是吗?

但是教程说版本1不符合里氏原理,我们需要做的是:

// version 2
public abstract class Calculator
{
    protected readonly int[] _numbers;

    public Calculator(int[] numbers)
    {
        _numbers = numbers;
    }

    public abstract int Calculate();
}

public class SumCalculator : Calculator
{
    public SumCalculator(int[] numbers)
        :base(numbers)
    {
    }

    public override int Calculate() => _numbers.Sum();
}

public class EvenNumbersSumCalculator: Calculator
{
    public EvenNumbersSumCalculator(int[] numbers)
       :base(numbers)
    {
    }

    public override int Calculate() => _numbers.Where(x => x % 2 == 0).Sum();
}

 class Program
{
    static void Main(string[] args)
    {
        var numbers = new int[] { 5, 7, 9, 8, 1, 6, 4 };

        Calculator sum = new SumCalculator(numbers);
        Console.WriteLine($"The sum of all the numbers: {sum.Calculate()}");

        Console.WriteLine();

        Calculator evenSum = new EvenNumbersSumCalculator(numbers);
        Console.WriteLine($"The sum of all the even numbers: {evenSum.Calculate()}");
    }
}

我不明白为什么版本 1 不符合 Liskov 原则?

您的 version 1 与 LSP 不兼容,独立于您的示例程序。

您可以“将子实例存储到父变量中”这一事实是 C# 支持的 句法 子类型概念。 LSP 提供了子类型化的 行为 概念,坚持认为超类型的 含义 (语义)在任何子 class 中都得到保留。

version 1中,superclassCalculate方法计算所有个数字,但在subclass中只有偶数。这使得 subclass 的行为与 superclass.

不一致

在符合 LSP 的 version 2 中,通过添加一个没有任何行为的单独 class 并在两个独立的子类型中扩展它两次来避免这种情况。这是符合 LSP 标准的。

如果您正在寻找一个您希望保持超级class 行为的示例,请考虑 class 除了计算之外还做一些额外的事情,例如 LoggingCalculator其中 Calculate 方法首先调用 superclass 方法(保持相同的行为),然后通过在某处记录结果来扩展它。