为什么这个 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 方法(保持相同的行为),然后通过在某处记录结果来扩展它。
以下是在线教程 (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 方法(保持相同的行为),然后通过在某处记录结果来扩展它。