拆分课后避免耦合

时间:2015-01-12 04:15:22

标签: java delegates refactoring

通常在分割一个类后,我最终会在它们之间产生太多的依赖关系。怎么避免这个?

考虑一个通过糟糕的设计尝试做两件事的课程。我会在这里保持简单,但想象有几种与每个类标识相关的方法。

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类现在需要在接口每次接收到一个新方法时更改,该方法除了在委托中调用等效方法之外什么都不做。对于大量复杂的类,有许多方法正在被拆分,我最终会使用相同的几十个调用委托方法的方法,这似乎会破坏分割类的目的。

我尝试应用一些不同的设计模式来解决这个问题,包括访问者和中介者,但没有一个设法模式比上面的模式更整洁。

所以我的问题是:是否有一种优雅的方法可以将一个类拆分为几个独立的角色,同时将其隐藏为实现细节并避免创建新的维护依赖项?

2 个答案:

答案 0 :(得分:2)

我不知道您的示例代码是否是您问题的良好描述,因为拆分/委托仍然不是最佳设计(稍后会详述)。但假设您的真实代码应该使用这样的委托,我仍然对您的投诉感到困惑

  

我对这种模式的问题是,每当接口接收到一个新方法时,Vehicle类现在需要更改,该方法除了在委托中调用等效项外什么都不做。

如果您从未将方法拆分为2个类,那么您是否仍然必须将新方法添加到Vehicle类中?因此除了要添加的愚蠢的一行方法之外,没有太多改变。

现在进入重新设计解决方案。

我认为委托是您的Drivable和Sellable子接口的正确解决方案。

  1. 这辆车是真的“可销售”还是具有“可销售”?并不是的。虽然从技术上讲,汽车可以出售给另一个车主,但它不是Vehicle的核心部分,不像轮胎或发动机。这意味着所有与转让Vechile所有权相关的方法都应该从Vehicle中完全删除。您可以拥有一个字段,其中包含有关所有者的信息。但是管理/更新所有者的所有代码都不应该在Vehicle
  2. 甚至Drivable也许不是你想要成为车辆的一部分。 driveTo(Destination)会发生什么? Vehicle对象是否具有与当前位置相关的地图相关字段和路径查找算法destination?我们必须计算燃料成本吗?也许其中一些事情也应该从Vehicle移出来?
  3. 或许更好的设计是将这些功能转移到新的Service类中。完全没有Vehicle引用它。这些新服务将Vehicle作为输入,然后调用Vehicle上的相应方法。

    所以Sellable。假设VehicleRegistration字段,该字段是存储当前所有者名称,转移日期等的对象。甚至可能包括过去的所有权历史记录。

    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界面发生变化,我们不需要触摸车辆。