如何正确实现这个设计?
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,我们可以在其中找到 CanRead
、CanWrite
、CanSeek
和 CanTimeout
特性。我敢肯定,只要稍微注意一下,就会发现这种模式出现在比预期更多的地方。
我在一次采访中遇到了这个问题,当时我被要求为所有车辆设计一个简单的车辆基础设施架构。
问题陈述类似于 - 考虑各种车辆(汽车、卡车、自行车、汽车、自行车......)的基础 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,我们可以在其中找到 CanRead
、CanWrite
、CanSeek
和 CanTimeout
特性。我敢肯定,只要稍微注意一下,就会发现这种模式出现在比预期更多的地方。