首页 > 解决方案 > 为什么这个类层次结构违反 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 不符合 Liskov 原则,我们需要这样做:

// 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 原则?

标签: c#liskov-substitution-principlesolid

解决方案


version 1与 LSP 不兼容,与您的示例程序无关。

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

version 1中,超类Calculate方法计算所有数字,但在子类中只计算偶数。这使得子类的行为与超类不一致。

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

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


推荐阅读