引入间接隔离变化(三)
间接在分层架构中体现尤为明显,引入层实则就是引入间接性。利用间接对变化进行隔离,使得变化只能影响一层。例如在领域层与数据库之间引入数据访问层,就可以隔绝因为数据库发生的变化对领域带来的影响。
在分层架构中,我们应尽量保证在某一层中所有用到的组件都工作在同一个抽象层中,这意味着需要谨慎处理对象之间的协作,尽量避免跨层之间的调用。如果不同的层部署在不同的服务器,就会带来不必要的消息处理,增加了网络跳数与带宽占用。抛开性能不谈,跨层调用产生的依赖,可能破坏层的内聚性。倘若必须依赖于底层,我们也应该减少对底层的依赖点。要减少这种依赖,可以在同一层中提供一个间接接口,用于处理对底层的依赖关系,从而将变化集中于一处。
对于模块和组件中的对象协作,同样遵循这一原则。假设系统定义了报表引擎组件,它需要调用数据引擎组件提供的服务,驱动引擎执行数据库查询,以获得报表所必须的数据。一般采用的设计如下图所示: 这样的设计充分体现了抽象的原则,保证了ReportEngine与DataEngine组件之间的松散耦合。在ReportEngine组件中,与报表相关的许多领域对象都需要调用DataEngineService,以获得报表所需要的数据。然而,我在设计时并未满足于DataEngineService接口引入的间接,而是在ReportEngine组件中再度引入了一层间接,我将其定义为DataEngineRepository,它属于报表引擎的领域范围:
public interface DataEngineRepository {
public List<Map<String, Object>> find(
CommandInfoPreparing commandInfoPreparing);
}
在DataEngineRepository的实现类中,注入了DataEngineService对象:
public class DataEngineRepositoryImpl
implements DataEngineRepository{
DataReadCommandInfoFactory commandInfoFactory = null;
DataEngineService dataEngineService ;
public List<Map<String, Object>> find(
CommandInfoPreparing commandInfoPreparing){
DataReadCommandInfo commandInfo = commandInfoFactory.
create(commandInfoPreparing);
return dataEngineService.queryData(commandInfo);
}
public DataEngineService getDataEngineService() {
return dataEngineService;
}
public void setDataEngineService(
DataEngineService dataEngineService) {
this.dataEngineService = dataEngineService;
}
}
既然DataEngineService接口已经提供了合理的抽象,引入DataEngineRepository接口会否是多余的呢?现在的他,看起来像是一名呆瓜接力选手,刚刚接到接力棒,就惊慌失措地赶紧塞给下一个人手中了。然而,经过仔细分析,我们还是能够看到二者的细微区别。在没有引入DataEngineRepository接口之前,报表引擎中的领域对象都依赖于跨组件的DataEngineService;现在,这些领域对象只需依赖同一个组件中的DataEngineRepository即可。这意味着什么呢?我们可以比较下面的两个组件图:
显然,通过引入DataEngineRepository接口,报表引擎组件中领域对象的依赖关系发生了转移。依赖存在于组件之中,而没有扩散到组件之外,表明这个组件是高内聚的。
间接引入的好处只有在变化时,才能凸显出来。我们的项目确实遭遇了变化。我们不希望看到ReportEngine组件直接依赖于DataEngine,因为报表对数据的访问不应该是直接的,它需要受到权限、安全的限制,同时可能还需要控制相关的业务。为此,我们在ReportEngine与DataEngine之间,引入了FunctionEngine,用以实现功能的控制与分发。现在,ReportEngine应该依赖于FunctionEngine,而不是DataEngine。 由于DataEngineRepository接口隔离了报表引擎中领域对象与数据引擎之间的依赖关系,使得我们可以从容应对功能引擎带来的变化。例如,修改接口的实现类,将原来对DataEngineService的引用,修改为对功能引擎中FunctionExecutor的引用:
public class DataEngineRepositoryImpl
implements DataEngineRepository{
DataReadCommandInfoFactory commandInfoFactory = null;
FunctionExecutor functionExecutor ;
public List<Map<String, Object>> find(
CommandInfoPreparing commandInfoPreparing){
DataReadCommandInfo commandInfo = commandInfoFactory.
create(commandInfoPreparing);
RequestParameter parameter = RequestParameter.newInstance();
parameter.add(commandInfo);
return (List<Map<String,Object>>)functionExecutor.
execute(parameter);
}
public FunctionExecutor getFunctionExecutor() {
return functionExecutor;
}
public void setFunctionExecutor(
FunctionExecutor functionExecutor) {
this.functionExecutor = functionExecutor;
}
}
修改DataEngineRepositoryImpl类的实现,并不会影响报表引擎中的领域对象,数据引擎也维持了自身的稳定。或许有人认为,即使不引入DataEngineRepository接口,功能引擎带来的变化也不会影响原有的设计,因为DataEngineService是抽象的接口,我们只需要修改它的实现即可。可是,切勿忘记这里的变化牵涉到功能引擎,修改DataEngineService的实现类,就意味着需要在它的实现中执行对功能引擎的调用;而功能引擎又必须调用数据引擎,从而带来FunctionEngine与DataEngine之间的循环依赖。没有比循环依赖更糟糕的依赖关系了!