如何正确实现这个设计?

How to correctly implement this design?

我在一次采访中遇到了这个问题,当时我被要求为所有车辆设计一个简单的车辆基础设施架构。

问题陈述类似于 - 考虑各种车辆(汽车、卡车、自行车、汽车、自行车......)的基础 class 'Vehicle'。现在我们对不同类型的车辆有多种要求- 例如。汽车、吉普车……有空调,而自行车、自行车、卡车没有空调。

我们还有一长串车辆(所有车辆),我们特别想根据这辆车是否有空调来执行一些操作。我们如何设计这个。

我想出了一种方法,我为 AC 功能创建了一个接口,并让具有 ac 的车辆实现了这些。

class Vehicle
{
}

inteface IACFunctionality
{
void PerformSomeOperation();
}

class Car : Vehicle, IACFunctionality
{
//Implementation
}

class Bike : Vehicle
{
//No implementation for ac
}

public void UpdateAllACDevices(List<Vehicle> vehicles)
{
// Check if vehicle type implements IACFunctionality and perform operation
foreach(var vehicle in vehicles)
{
IACFunctionality acVehicle = vehicle as IACFunctionality;
if(acVehicle != null)
   acVehicle.PerformSomeOperation();
}
}

这一切都很好,直到他问我 - 考虑是否很少有汽车没有空调。您将如何整合此更改?

我决定继续使用有点阴暗的方法,为 Car 设置 2 个单独的 classes - 一个实现了 IACFunctionality,一个没有实现。

class ACCar : Vehicle, IACFunctionality 
{}

class NonACCar : Vehicle
{}

现在他进一步问,如果有多个这样的要求,比如空调 - 音乐系统,天窗等。 我意识到这不符合我的方法,但基于接口隔离原则,我不想为它不需要的 class 定义一些东西。

进行此设计的正确方法是什么?

我的方法将从为每个功能创建一个接口 IXxxFunctionality 开始。仅在 可能 实现它的 class 上添加接口,不包括接口完全没有意义的 class 。

除了公开自己的方法和属性外,每个接口还会公开一个布尔值 属性 IsXxxSupported(或 CanXxx),表示该功能是否实际受支持。通过查询这样的 属性,您知道调用接口的任何成员都是安全的(它们不会抛出 NotSupportedException 或其他适当的异常)。

当您需要在使用基 class 静态类型化的对象上调用所需的功能时,您可以使用以下模式:

foreach (var vehicle in vehicles)
{
    if (vehicle is IACFunctionality acVehicle && acVehicle.IsACSupported)
    {
        // do stuff with acVehicle without worries!
    }
}

从理论的角度来看,这样你既尊重接口隔离原则又尊重 Liskov 替换原则,因为:

  • 每个接口都没有应用到不需要的地方,你也没有污染基础 class。
  • 接口成员抛出NotSupportedException 当且仅当 IsXxxSupported returns false。事实上,你并没有违反 superclass in a subclass 的约定(除非你故意提供一个错误的实现),因为约定允许在特定条件下出现异常。

一般来说,在真正的“可操作”成员旁边显示 IsXxxSupported 属性 是一种在其他情况下也可以利用的方法。这包括提供一些基本功能的抽象 classes 以及委托给其具体子 classes 的其他可选功能。

这种模式可以在 .NET 和广泛使用的库中的许多地方找到。我想到的第一个例子是 System.IO.Stream 摘要 class,我们可以在其中找到 CanReadCanWriteCanSeekCanTimeout特性。我敢肯定,只要稍微注意一下,就会发现这种模式出现在比预期更多的地方。