我正在尝试学习设计实践和OOP。我正在使用停车场问题作为样本开始。
我有一个GeneralParkingLot
接口和一个Vehicle
接口。
GeneralParkingLot
只有一个函数returnParkingLotSize
,Vehicle
接口有多个Vehicle属性。
我创建了一个DowntownParkingLot
类,其中包含GeneralParkingLot
,listOfCars
等其他属性以及availableSlots
类扩展Car
1}} class。
我有一个处理传入命令的Vehicle
,在该类中我已经解析了一个HandlerClass
对象和多个函数来处理命令,因此在每个函数中我只传递{{1}的对象并对其进行操作。
我创建了不同的服务,例如DownTownParkingLot
,DowntownParkingLot
,CreateParkingLotObject
等,这些服务由命令处理程序调用。
我还创建了单元测试来测试我的应用程序。
我的问题是,如果我想扩展我目前的停车场以获得额外的功能,例如让我们说多楼属性或者如果我现在要处理多个停车场而不是一个,那么扩展我的{最佳方式是什么? {1}}类或ParkACar
类。我也读过关于适配器和装饰器模式的内容,但是我认为当我从开始就遵循特定的设计模式时这些是有用的。在我的情况下,我没有遵循任何特定的模式,所以什么是扩展我的代码的最佳方式。我问这个是因为有时我们会遇到一个不是根据任何设计模式制作并且在多个地方使用的类(比如许多API等),所以扩展这样的代码的最佳方法是什么。从重启开始是唯一的选择吗?或者创建从旧类继承的新类?什么是最好的方式?另外,我想尽可能多地使用已经创建的单元测试,而不是再次重写相同的测试用例。
答案 0 :(得分:4)
在您的情况下,最高级的类是接口,因此您不能直接添加属性。
解决问题的一种方法是创建实现GeneralParkingLot
接口的顶级实现类:
public abstract CommonParkingLot implements GeneralParkingLot {
// add your missing attributes here
protected int floorNo;
}
(如果您只想添加属性而没有为GeneralParkingLot
提供某些默认实现,则可以省略implements
部分)
然后让您的实现类扩展此CommonParkingLot
:
public class DowntownParkingLot extends CommonParkingLot implement GeneralParkingLot {
// Now all missing attributes are accessible here
}
对于缺少的功能,default
功能界面应该很方便。 (关键字是指定的语言),您可以根据需要向GeneralParkingLot
界面添加默认功能。
还有一点,您当前的实现有点受限于DowntownParkingLot
,因为您没有向接口添加通用功能。我建议您稍微重构HandlerClass
类:
public interface GeneralParkingLot {
int returnParkingLotSize();
ParkingLotObject createParkingLotObject();
boolean parkACar();
int freeASlot();
default public void parkMultipleCar() {
throws new UnsupportedOperationException();
}
}
现在您的HandlerClass
中的计算机应该在GeneralParkingLot
上而不是DowntownParkingLot
上运行。
这样做,您可以添加更多GeneralParkingLot
而无需更改代码。
答案 1 :(得分:2)
我认为您必须在这里使用Visitor
模式。 visitor
指定的Gang of Four
模式的定义是
表示要在对象结构的元素上执行的操作。访问者可让您定义新操作,而无需更改其所操作元素的类。
假设您保持ParkingLot
层次结构不变,则可以为每个必须添加的新操作定义新的访问者。这样对您现有的类和单元测试的更改很少或没有更改。
答案 2 :(得分:1)
继承对于教科书是有好处的,实际上它是非常严格的。您应该使用接口和许多不同的实现:
interface ParkingLot {
void park(Vehicle v);
}
interface Vehicle {
int size();
boolean isDIsabled();
}
仅公开最高级别操作所需的内容。像停车场一样停放车辆。车辆只能容纳一个停车位或残疾人专用停车位。这就是停车场如何知道将车停在哪里的地方。
然后,每个实现类都会执行使该过程正常运行所需的操作。我认为Floors是内部实现细节,可能不需要在API上公开而是在内部使用。
还建议使用工厂根据楼层,电梯,卫生间等来建造停车场。因此,您可以通过配置而不是硬编码来构建通用停车场。
您可以使用“命令”在停车场之间传递停车命令,直到第一个找到适合您的汽车的停车位。在这种情况下,您将所有停车场链接起来,并在它们之间传递请求。也可以在各层之间内部使用它,因为每层都将尝试找到从底层到屋顶的匹配项。
您可以使用与寻找停车位相关的功能来“装饰”您的汽车。再次,您将添加具有不同功能的通用汽车。停车场设计也一样。
当您通过停车命令时,停车场将读取装饰后的特征,并决定是否要停车。
例如,安全性对网站的用户角色(auth)起作用。
答案 3 :(得分:0)
从开始重构是唯一的选择吗?或创建从旧类继承的新类?最好的方法是什么?
真的取决于您对未来更改的期望。 OO设计是一方面以尽可能低的成本解决眼前的需求,另一方面则是预测未来的变化。
例如,让我们以@Tobias提供的MultiFloorDowntownParkingLot
为例。如果您希望将来会有TollParkingLot
和TollMultiFloorParkingLot
以及各种各样的变体,则首选装饰器,因为在实现这些装饰器时更改代码以支持装饰器的成本将得到补偿各种停车场。但是,如果您怀疑您的停车场只是GeneralParkingLot
的简单变体,那么只需扩展GeneralParkingLot
就足够了。
要考虑的另一件事是composition vs inheritance。您也许可以利用组合来解决某些要求。例如,我可以将收费功能创建为包装(或由其组成)GeneralParkingLost
对象(该收费停车场对象可以很容易地包装MultiFloorParkingLot
对象)的新类。>
这很容易过分考虑。您可能需要确定设计阶段的时间,然后选择最便宜的解决方案来解决问题并注意任何预期的更改。 《代码完整》这本书很好地涵盖了这些主题。在那本书中,它试图列出程序的哪些部分可能随时间变化,然后可以设计这些部分可能会发生变化的事实。
除非您有明确的要求,建议您需要装饰,否则我将使用@Tobias答案和组成来填补任何空白并处理聚合(停车场的集合不应为GeneralParkingLot
)。
答案 4 :(得分:0)
首先让我们在这里研究一些术语,以便以后不会使人们感到困惑。这里我假定上下文是Java,因为它被标记为Java。
请勿混用“ 类”和“ 界面”。类定义对象,接口定义行为。类不能扩展接口,而只能实现它。但是,您可以使用一个接口扩展另一个接口,从而增加更多行为。
请勿混用“ 属性”和“ 字段”。对于一个类,属性通常表示私有访问,而字段表示通过getter / setter进行公共访问。
第二,让我们研究OOP概念。 OOP是现实世界的抽象。您的应用程序设计的好坏完全取决于您对领域的了解程度。
现在让我们来研究您的示例。在您的情况下,主要概念只有一个:停车场。但是,在不同的领域中,它意味着不同的抽象。
在付款应用程序中,它关心两件事:空间和时间。还剩下多少个开放空间,它们在哪里?对于停车场内的每个单元,其停放了多长时间以及离开后将改变多少。
在流量控制应用程序中,它具有完全不同的观点:可以传输多少流量,以及引导流量的最佳途径是什么。
看到,即使在非常简单的概念上,不同的领域也会导致不同的设计。如果您发现自己在设计上绊脚石。尝试忘记类/接口或OOP,而只专注于首先了解您的域,这通常可以解决问题。
让我们现在开始处理一些细节。 (由于我不确定您的领域,我将根据我的理解简单地解释设计过程。您可以吸收这个想法并定制您的设计。)
GeneralParkingLot:
因为它是一个接口,所以它定义了行为。停车场应该有什么行为?好吧,最重要的是:它应该告诉availableParkingSpaces
,它应该能够park
一辆车,charge
离开时。现在,我们定义了3种行为:
int getAvailableParkingSpaces();
int park(Vehicle v);
int charge(Vehicle v);
DowntownParkingLot
:这应该是您的对象,它具有属性并实现行为。
class DowntownParkingLot implements GeneralParkingLot{
private int totalSpaces;
private int[] parking_slots;
private Map<Integer, Vehicle> parkedVehicle; //<park_space_id, vehicle>
//implements of interface methods
}
MultiFloorParkingLot
:我不确定这个多层结构将如何影响您的设计,因为它隐藏在您的类中。
您的多层停车位是否在行为方面提供了额外的东西?如果是这样,它应该是将GeneralParkingLot
扩展为
interface MultiFloorParkingLot extends GeneralParkingLot{
//provide extra behaviours here
}
如果不是,则应为此类,其停车位存储方式不同
class MultiFloorParkingLot implements GeneralParkingLot{
private int[][] parking_slots; // locate parking_id by floor + park_num
//everything else should not changed much.
}
我个人认为多层与单层没有什么不同。多层或单层是建筑结构的概念,仅在您的应用需要将车辆定位/引导至可用空间时才有用。是在“第4楼A列,#24”中,还是在“第1区,B列,#25”中,概念上没有区别,区别在于实现,应将其封装您的班级本身。但同样,这取决于您的域。
Vehicle
:由于它是停车场车辆的概念,因此我们不在乎其品牌,性能或与车辆本身相关的任何内容。
interface Vehicle {
String getLicense();
void park(GeneralParkingLot p);
}
Car
:对于汽车,它至少应有一个用作ID的车牌。
class Car implements Vehicle{
String licensePlate;
//if your domain needs to track how long it parks
long enterTimeStamp, exitTimeStamp;
@Override
public void park(GeneralParkingLot p){
//do something that car needs to do, then register car on parking-lot
p.park(this);
}
}
在这里看到?将汽车停放在停车场的过程是将汽车注册到停车场的过程。他们两个都可能需要做些事情来完成该过程。他们将分别不应该做什么是由外部的课堂完成的。 开闭原则:您可以告诉我该怎么做,但不能告诉我该怎么做。这样可以灵活地更改不同停车场的不同汽车的细节。
HandlerService
:我不确定此处理程序服务将如何工作,但是在OO概念中,该行为应由对象本身执行。让我们编写代码:
class HandlerService {
//let's use park a car as example.
//notice that your using interface instead of class.
//programming on interface, not class
public void parkACar(Vehicle v, GeneralParkingLot p){
v.park(p);
}
}
无论如何,编程仅仅是解决问题的工具。 OOP只是一种抽象方式。一个好的,灵活的设计取决于您的领域知识,而不是编程技能。因此,请善待您的业务分析并与他们成为朋友。 ^-^
希望它会有所帮助。
答案 5 :(得分:-1)
好吧,这是我阅读关于您的问题的评论后的答案:
具有多个楼层的市区停车场显然也是市区停车场。很明显,如果不是一个相同的类,我们将希望有一个类表示一个从MultiFloorDowntownParkingLot
派生的DowntownParkingLot
。
如您所知,您已经有了SingleFloorDowntownParkingLot
的实现,它也是DowntownParkingLot
的实现。
DowntownParkingLot
可以是单层或多层,因此它应该是抽象的并实现GeneralParkingLot
接口,并具有与当前DowntownParkingLot
相同的方法(但它们的实现应在SingleFloorParkingLot
。
MultiFloorDowntownParkingLot
由多个SingleFloorParkingLot
组成,并且对于每种方法都像包装器或多路复用器一样工作。例如,listOfCars
仅连接其所有楼层返回的列表。
可以通过多种方式扩展此模型:
DowntownParkingLot
可以有多个入口。单层实现此目的,多层停车场负责组织单层的哪些入口朝外以及将楼层之间相互连接。can have multiple slot sizes. A
SingleFloorDowntownParkingLot has one slot size. The
MultiFloorDowntownParkingLot`具有其楼层的所有插槽大小。重要的是要注意,对于用户而言,只有DowntownParkingLot
是重要的。用户永远不必知道特定的DowntownParkingLot
具有单个或多个楼层(尽管在某些情况下可能会受益,但这也一样)。仅DowntownParkingLot
的构造函数必须决定他使用哪个子类。
当然,还有许多其他可能性,它们的可用性在很大程度上取决于用例,但是我认为这相当普遍。
我希望能为您提供帮助!