【问题标题】:Parking Lot OO Design - Do enums violate Open/Closed Principle?停车场 OO 设计 - 枚举是否违反开放/封闭原则?
【发布时间】:2021-03-07 06:27:50
【问题描述】:

我正在尝试使用面向对象的方法设计一个停车场系统。停车场可以有多种类型的停车位,如残疾人、紧凑型、大型、摩托车等。

最初,我正在考虑创建一个枚举来对这些不同类型进行建模,如下所示:

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;
  }
}

这使我能够通过以下操作从Vehicle 中找到ParkingSpotType

vehicle.getVehicleType().getParkingSpotType()

但是,有人告诉我,这会违反 Open/Closed 设计原则。任何添加新类型都可能需要在现有的各个地方更改代码,这将违反开放/封闭设计原则,即在需要引入新功能时不应修改现有和经过良好测试的类。有人建议我为不同的类型创建不同的子类如下:

public class HandicappedSpot extends ParkingSpot {
}

public class CompactSpot extends ParkingSpot {
}

public class LargeSpot extends ParkingSpot {
}

public class MotorbikeSpot extends ParkingSpot {
}

但是使用这种方法,如果我没有相同的枚举建模,我将如何将 Vehicle 映射到 ParkingSpot。如果第一种方法确实对OO-Design不好,有人可以看看并发表评论。如果是,我将如何解决第二种方法中的上述问题?

【问题讨论】:

  • 这取决于您对停车位的预期差异。如果您需要问他们的唯一问题是“这里有什么样的车?”,答案是一种车,那么枚举就更合适了。但是,如果您计划制作仅适用于特定类型停车位的功能(也许您有一个具有特殊“加油车”功能的 ElectricCarSpot),那么这些应该是子类,因为它们添加了功能。
  • 不要为了添加子类而添加子类;只有在添加功能时才扩展一个类。即使你是,也值得问是否有比直接实现继承更好的方法,坦率地说,这很少是答案。考虑组合(一个残障点“包含”一个普通停车位,我们可以从中查询普通停车位属性),或者在您的情况下更有可能用一个接口替换 ParkingSpot 并让几个类扩展它。接口继承是实现继承更常见、危害更小的表亲。

标签: java oop object-oriented-analysis


【解决方案1】:

我不是专家,但我不关注您对枚举解决方案的批评。根据一些观察,我觉得这很好:

开放/封闭原则允许扩展。这不仅包括创建接口和子类的新实现,还包括向现有类添加属性和方法——“扩展”和“修改”之间的界限似乎很模糊,但据我了解,背后的原则是开放/关闭的是,如果某些客户端、消费者或模块正在使用您的代码库,它们不应该因为您的代码更改而不得不重构(在理想情况下)。

为此,我看不出向枚举添加新元素与添加扩展 ParkingSpot 的新类有何不同。 可能是错的。

我想我会问一个例子,如果我们添加一个新的停车位枚举元素,当前代码会在哪里中断,但如果我们向 ParkingSpot 添加一个新的子类,则不会,因为我目前没有看到。

【讨论】:

  • “他们不应该因为你的代码更改而重构” 他们可能的原因之一是因为枚举经常在switch 语句中被详尽地使用,如果有如果不匹配,则在 default 块中引发异常,并假定永远不应引发此异常。当然,如果添加新类型,出于同样的原因,需要重构详尽的 if/else ifinstamceof 检查,但枚举鼓励 switch 语句,而 Java 程序员应该知道做很多事情instanceof 检查是不好的做法。
【解决方案2】:

既然问题是“枚举是否违反了开放/封闭原则”,我的回答是因为你不能编写枚举类型的子类,并且枚举类型的用户不能创建自己的实例来满足自己的要求,那么在大多数情况下,这将违反开放/封闭原则,但需要注意两点:

  • 如果枚举类型 本身就是一个详尽的列表(例如一周中的哪几天),那么出于同样的原因,它就没有必要“为扩展开放”了不需要“开放扩展”。
  • 如果枚举类型具有在传递满足某些接口的用户定义类时可能具有不同行为的方法(例如,Comparable 用于排序函数),那么从这个意义上说,它是“对扩展开放”。请参阅this other Q&A,了解“开放扩展”不一定意味着允许子类的讨论。

也就是说,开放/封闭原则在这里有点像红鲱鱼,因为您提出的两个替代模型实际上并不是对相同的事物建模。您的ParkingSpotType 枚举模拟了一个停车位类型,这样每个枚举值都代表停车位的类型;而您的ParkingSpot 类模拟了一个停车位,这样这个类的一个实例(或者一个如果它的子类,比如MotorbikeSpot)代表一个单独的停车位,以及同一个子类的多个实例将代表相同类型的不同停车位。考虑:

  • 您可以使用枚举ParkingSpotTypes 来表示不同类型的停车位,也可以使用枚举ParkingSpots 来表示特定停车场中的实际停车位(其中有一个明确的列表,如果该程序仅适用于一个停车场)。
  • 您可以使用类ParkingSpot 来表示一个实际的停车位,并为不同类型的停车位使用子类,或者您可以使用一个类ParkingSpotType,其实例 每个都代表一种类型停车位。

您选择哪个选项应取决于您的程序是需要表示实际停车位,还是只表示停车位的类型。例如,如果您销售的停车票只能在票上指定的停车位使用,那么您的代码必须知道停车位是什么;如果您销售的停车票可以在票上指定类型的任何地点使用,那么您的代码可能只需要知道有多少个停车位可用。在后一种情况下,无需对实际停车位进行建模,只需对停车位类型进行建模。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    • 2010-09-10
    • 1970-01-01
    • 1970-01-01
    • 2011-01-23
    相关资源
    最近更新 更多