问题描述
我正在尝试使用面向对象的方法设计停车场系统。停车场可以有多种类型的停车位,如残疾人、紧凑型、大型、摩托车等。
最初,我想创建一个枚举来对这些不同类型进行建模,如下所示:
public enum ParkingSpottype {
HANDICAPPED,COMPACT,LARGE,MOTORBIKE
}
然后在 ParkingSpot
类中使用它们,如下所示:
public abstract class ParkingSpot {
private ParkingSpottype type;
}
类似地,Vehicle
类也有 VehicleType
,它会映射到停车所需的 ParkingSpottype
public class Vehicle {
private VehicleType vehicleType;
// other fields
}
public enum VehicleType {
CAR (ParkingSpottype.COMPACT),BUS (ParkingSpottype.LARGE),TRUCK (ParkingSpottype.LARGE),BIKE (ParkingSpottype.MOTORBIKE),CYCLE (ParkingSpottype.MOTORBIKE);
private ParkingSpottype parkingSpottype;
VehicleType(ParkingSpottype parkingSpottype) {
this.parkingSpottype = parkingSpottype;
}
}
这使我能够通过执行以下操作从 ParkingSpottype
中找到 Vehicle
:
vehicle.getVehicleType().getParkingSpottype()
然而,有人告诉我这会违反开放/封闭设计原则。任何新类型的添加都可能需要在现有的各个地方更改代码,这将违反开放/封闭设计原则,即在需要引入新功能时不应修改现有和经过良好测试的类。有人建议我为不同的类型创建不同的子类,如下所示:
public class HandicappedSpot extends ParkingSpot {
}
public class CompactSpot extends ParkingSpot {
}
public class LargeSpot extends ParkingSpot {
}
public class MotorbikeSpot extends ParkingSpot {
}
但是使用这种方法,如果我没有相同的枚举建模,我将如何将 Vehicle 映射到 ParkingSpot。如果第一种方法确实是糟糕的面向对象设计,有人可以看看并发表评论。如果是,我将如何解决第二种方法中的上述问题?
解决方法
我不是专家,但我不理解您对枚举解决方案的批评。根据一些观察,我觉得这很好:
开放/封闭原则允许扩展。这不仅包括创建接口和子类的新实现,还包括向现有类添加属性和方法——“扩展”和“修改”之间的界限似乎很模糊,但据我所知,背后的原则是开放的/封闭的是,如果某些客户端、消费者或模块正在使用您的代码库,他们不应该由于您的代码更改而进行重构(在理想情况下)。
为此,我看不出向枚举添加新元素与添加扩展 ParkingSpot 的新类有何不同。 可能是错的。
我想我会问一个例子,如果我们添加一个新的停车位枚举元素,当前代码会在哪里中断,但如果我们向 ParkingSpot 添加一个新的子类,则不会,因为我目前没有看到。
,既然问题是“枚举是否违反了开闭原则”,我的回答是,因为你不能编写枚举类型的子类,并且枚举类型的用户不能创建自己的实例来满足自己的要求,那么在大多数情况下,这会违反开放/封闭原则,但有两个警告:
- 如果枚举类型是本质上是一个详尽的列表(例如一周中的几天),那么出于同样的原因整数,它就不需要“开放扩展”不需要“开放扩展”。
- 如果枚举类型的方法在传递满足某些接口的用户定义类时可以具有不同的行为(例如
Comparable
用于排序函数),那么它在这个意义上是“开放扩展”。请参阅 this other Q&A 以讨论“对扩展开放”并不一定意味着允许子类。
也就是说,开放/封闭原则在这里有点不合时宜,因为您提出的两个替代模型实际上没有对相同的事物进行建模。您的 ParkingSpotType
枚举对停车位类型建模,这样每个枚举值代表停车位类型;而您的 ParkingSpot
类建模一个停车位,这样该类的一个实例(或一个如果其子类,例如 MotorbikeSpot
)代表一个单独的停车位,以及多个实例相同的子类将代表相同类型的不同停车位。考虑:
- 您可以使用枚举
ParkingSpotTypes
来表示不同类型的停车位,也可以使用枚举ParkingSpots
来表示特定停车场的实际停车位(其中有明确的列表,如果程序只需要为一个停车场工作)。 - 您可以使用类
ParkingSpot
来表示一个实际的停车位,以及用于不同类型停车位的子类,或者您可以使用一个类ParkingSpotType
,其每个实例代表一种停车位。
您选择哪个选项取决于您的程序是需要代表实际停车位,还是仅代表停车位类型。例如,如果您销售的停车票只能在票上指定的停车位使用,那么您的代码必须知道停车位是什么;如果您销售的停车票可用于票上指定类型的任何地点,那么您的代码可能只需要知道有多少个地点可用。在后一种情况下,不需要对实际停车位进行建模,只需对停车位类型进行建模。