通常在分割一个类后,我最终会在它们之间产生太多的依赖关系。怎么避免这个?
考虑一个通过糟糕的设计尝试做两件事的课程。我会在这里保持简单,但想象有几种与每个类标识相关的方法。
class Vehicle {
public void driveTo(Destination destination);
public void sellTo(Person purchaser);
}
在这种情况下,我倾向于为每个扮演的角色创建单独的界面,然后更改原始类以将其指定为implements
。然后将实际实现移动到从原始类调用的单独委托类。
interface Drivable {
void driveTo(Destination destination);
}
interface Sellable {
void sellTo(Person purchaser);
}
class Vehicle implements Drivable, Sellable {
private final Drivable drivableVehicle;
private final Sellable sellableVehicle;
public void driveTo(Destination destination) {
drivableVehicle.driveTo(destination);
}
public void sellTo(Person purchaser) {
sellableVehicle.sellTo(purchaser);
}
}
这很好地将所有相关方法收集在一起,并将驾驶和销售的细节分成他们自己的班级。都好。
我对这种模式的问题是,Vehicle
类现在需要在接口每次接收到一个新方法时更改,该方法除了在委托中调用等效方法之外什么都不做。对于大量复杂的类,有许多方法正在被拆分,我最终会使用相同的几十个调用委托方法的方法,这似乎会破坏分割类的目的。
我尝试应用一些不同的设计模式来解决这个问题,包括访问者和中介者,但没有一个设法模式比上面的模式更整洁。
所以我的问题是:是否有一种优雅的方法可以将一个类拆分为几个独立的角色,同时将其隐藏为实现细节并避免创建新的维护依赖项?
答案 0 :(得分:2)
我不知道您的示例代码是否是您问题的良好描述,因为拆分/委托仍然不是最佳设计(稍后会详述)。但假设您的真实代码应该使用这样的委托,我仍然对您的投诉感到困惑
我对这种模式的问题是,每当接口接收到一个新方法时,Vehicle类现在需要更改,该方法除了在委托中调用等效项外什么都不做。
如果您从未将方法拆分为2个类,那么您是否仍然必须将新方法添加到Vehicle
类中?因此除了要添加的愚蠢的一行方法之外,没有太多改变。
现在进入重新设计解决方案。
我认为委托是您的Drivable和Sellable子接口的正确解决方案。
Vehicle
的核心部分,不像轮胎或发动机。这意味着所有与转让Vechile所有权相关的方法都应该从Vehicle
中完全删除。您可以拥有一个字段,其中包含有关所有者的信息。但是管理/更新所有者的所有代码都不应该在Vehicle
。driveTo(Destination)
会发生什么? Vehicle
对象是否具有与当前位置相关的地图相关字段和路径查找算法destination
?我们必须计算燃料成本吗?也许其中一些事情也应该从Vehicle
移出来?或许更好的设计是将这些功能转移到新的Service
类中。完全没有Vehicle
引用它。这些新服务将Vehicle
作为输入,然后调用Vehicle
上的相应方法。
所以Sellable
。假设Vehicle
有Registration
字段,该字段是存储当前所有者名称,转移日期等的对象。甚至可能包括过去的所有权历史记录。
VehicleRegistrationService
将获得当前注册,并使用新所有者的信息进行更新:
公共类VehicleRegistrationService {
public void sellTo(Vehicle v, Person newOwner){
Registration prevRegistration = v.getRegistration();
//..complex business logic to update DMV records etc
Registration newRegistration = new Registration(newId(), prevRegistration);
newRegistration.setCurrentOwner(newOwner);
//update vehicle
v.setRegistration(newRegistration);
}
}
类似于Drivable
相关方法。驾车到目的地可能有地图和路径查找算法,更新可用的燃料......其中大部分不应该在Vehicle
。相反,可以使用某种NavigationService
来处理大部分内容,并且只告诉车辆行驶的速度,何时转弯以及在特定方向上行驶多远:
public class NavigationService{
private Map map; // a real map with distances between drivable locations
...
public void navigateTo(Vehicle v, Destination to){
Location from = v.getLocation();
...//complex business logic to find best path from current to destination.
Directions directions = computeDirections(from, to);
//for each step in the directions call low level methods
//on vehicle to tell it how to get to destination
for(Step step : directions){
v.turn(step.getCardinalDirection()); // i.e. north south east west
v.travelFor(step.getDistance()); //perhaps vehicle tracks fuel consumption
}
}
private Directions computeDirections(Location from, Destination to){
..//use map to find best path
}
}
车辆仍可以跟踪燃油
public class Vehicle{
...
private double fuelLeft;
public void travelFor(Distance distance){
double spent = distance.asMiles() * this.getMpg();
fuelLeft -= spent;
if(fuelLeft < 0){
throw new OutOfFuelException();
}
//update current location by moving the distance in the given direction
//(which was set by turn()
this.currentLocation = currentLocation.move(direction, distance);
}
}
现在车辆仅包含有关实际车辆的信息。与实际车辆无关的方法,例如如何从一个地方到另一个地方,已经被考虑到新的类别。这些类在将来可以自由更改,而不会影响Vechile
的API。
答案 1 :(得分:1)
如何使用复合而不是继承。
interface Drivable {
void driveTo(Destination destination);
}
interface Sellable {
void sellTo(Person purchaser);
}
class DriveFunction implement Drivable{
}
class SellInformation implement Sellable{
}
class Vehicle {
private final Drivable drivableVehicle;
private final Sellable sellableVehicle;
public Vehicle(Drivable drivable,Sellable sellable){
}
public void driveTo(Destination destination) {
drivableVehicle.driveTo(destination);
}
public void sellTo(Person purchaser) {
sellableVehicle.sellTo(purchaser);
}
}
如果向Drivable添加新功能,则只需向DriveFunction类添加一个函数即可。仅当您需要从Vehicle公开方法时,才应将委托方法添加到Vehicle中。
<强>更新强>
也许反向授权是正确答案
将车辆保留为原件。
class DriveFunction implement Drivable{
DriveFunction(Vehicle vehicle){
}
void driveTo(Destination destination){
vehicle.driveTo(destination);
}
}
然后,如果Drivacle界面发生变化,我们不需要触摸车辆。