抽象建立的这层间接性,解除了调用者与实现类之间的具体依赖,使得实现类可以单独变化,而不会影响到调用者。例如,当我们需要为元数据的读取操作定义对象时,好的编码习惯是为其定义一个接口:
public MetadataObject getMetadataObject(String metadataName);
public MetadataField getMetadataField(
String tableName,String fieldName);
public MetadataRelation getMetadatarelation(
long objectId, long relateObjectId);
}
MetadataReaderServiceImpl类实现了MetadataReaderService接口,在实现中通过注入数据访问对象,完成对元数据信息的读取:
MetadataReaderService {
private MetadataObjectDAO metadataObjectDAO;
private MetadataFieldDAO metadataFieldDAO;
private MetadataRelationDAO metadataRelationDAO;
public MetadataObjectDAO getMetadataObjectDAO() {
return metadataObjectDAO;
}
public void setMetadataObjectDAO(
MetadataObjectDAO metadataObjectDAO) {
this.metadataObjectDAO = metadataObjectDAO;
}
public MetadataFieldDAO getMetadataFieldDAO() {
return metadataFieldDAO;
}
public MetadataObject getMetadataObject(String metadataName) {
return this.metadataObjectDAO.getMetadataObject(metadataName);
}
}
为何一定要定义MetadataReaderService接口?因为接口可以使得它与调用者的依赖降到最弱。例如调用者QuerySqlStatementImpl类:
public MetadataReaderService getMetadataReader() {
return this.metadataReaderService;
}
public void setMetadataReader(MetadataReaderService reader) {
this.metadataReaderService = reader;
}
private String generateJoinStatement(TableNode rootTableNode){
String metadataName = rootTableNode.getMetadataName();
MetadataObject rootMetadataObject =
this.getMetadataReader().getMetadataObject(metadataName);
}
}
QuerySqlStatementImpl与MetadataReaderServiceImpl之间不存在任何依赖关系,它只依赖于MetadataReaderService接口,而该接口是抽象的,因而是相对稳定的。例如,我们考虑到系统中的元数据信息较为稳定,但却可能被频繁地读取。为了提高系统的性能,有必要引入元数据信息的缓存。
显然,访问元数据信息的实现发生了变化。它不再是直接通过数据访问对象读取存储在数据库中的元数据信息,而是先读取内存中的缓存。如果缓存中的元数据已经存在,则直接返回,否则,访问数据库。现在,我们可以修改MetadataReaderServiceImpl的实现,却不会影响到调用者QuerySqlStatement,因为MetadataReaderService接口没有发生任何变化。
不过,根据封装的原理,即使没有定义MetadataReaderService接口,在修改MetadataReaderServiceImpl类时,只要我们不改变它所公开的方法,即使调用者依赖于它,似乎也不会受到影响才对。这样的推断并没有错误,错在我们假设的前提。假若我们直接修改MetadataReaderServiceImpl类的实现,如果面对如下两个问题,应该如何应对?
1、 MetadataReaderServiceImpl类的源代码不允许修改;
2、 读取元数据信息的其他调用者不需要缓存功能。
第一个问题锁上了修改的大门,第二个问题又添上了另一把锁。它明确地告诉那些希望走捷径的设计者:此路不通!或许,禁止修改源代码的需求过于苛刻,除非使用第三方代码;然而,第二个问题在实际的项目开发中确实存在。我们在实现元数据信息的编辑器时,同样需要调用MetadataReaderServiceImpl类。由于编辑器主要针对元数据进行CRUD操作,因此,元数据信息的变化是很频繁的,此时使用缓存,无异于画蛇添足。
根据OCP原则,好的设计应该对修改是封闭的,对扩展是开放的。接口的引入使得扩展成为可能。我们可以定义一个缓存对象去实现MetadataReaderService接口,它提供了缓存的功能。缓存对象除了拥有管理和访问缓存的职责之外,它还需要读取数据库,以应付缓存未包含指定信息的情况。我们需要在缓存对象中再提供读取数据库的实现逻辑吗?设计与开发切忌浪费,信奉“拿来主义”。在实现某个功能之前,首先应考察是否已有现成的实现,如果有,直接拿来重用即可。目前,MetadataReaderServiceImpl类已经实现了读取数据库的功能,因此,最简单的办法就是将其传递给缓存对象。考虑到MetadataReaderServiceImpl才是真正实现读取的对象,而缓存对象不过是对其包裹了一层外衣而已,为了更好地表达对象的意图,最好将MetadataReaderServiceImpl更名为RealMetadataReaderService,缓存对象则命名为CachedMetadataReaderService:
MetadataReaderService{
private MetadataReaderService readerService;
private MetadataCacheManager cacheManager;
@Override
public MetadataObject getMetadataObject(String metadataName) {
Serializable obj = cacheManager.getObjectCached(metadataName);
if(obj!=null){
return (MetadataObject) obj;
} else{
MetadataObject metadataObject = readerService.
getMetadataObject(metadataName);
cacheManager.putObjectCached(metadataName,
(Serializable) metadataObject);
return metadataObject;
}
}
}
CachedMetadataReaderService类相当于RealMetadataReaderService的代理,由它决定是从缓存获取,还是从数据库中获取。现在,我们可以让调用者访问CachedMetadataReaderService类。因为它与RealMetadataReaderService同属于一个共同的接口,所以调用者察觉不到变化。为了解除创建具体对象的依赖,我们还可以使用IoC容器,利用依赖注入的方式注入调用者需要的对象。正是因为MetadataReaderService接口的存在,当面对需求发生变化时,我们只做了两件事:扩展增加了CachedMetadataReaderService类;修改Spring的配置文件。对于原有的代码,无论是服务的提供者还是调用者,都没有受到任何影响。接口引入的间接性,无疑是能够拥抱变化的。
本文转自wayfarer51CTO博客,原文链接:http://blog.51cto.com/wayfarer/479634,如需转载请自行联系原作者