引入间接隔离变化(二)

简介:

抽象建立的这层间接性,解除了调用者与实现类之间的具体依赖,使得实现类可以单独变化,而不会影响到调用者。例如,当我们需要为元数据的读取操作定义对象时,好的编码习惯是为其定义一个接口:

public  interface MetadataReaderService {
public MetadataObject getMetadataObject(String metadataName);
public MetadataField getMetadataField(
String tableName,String fieldName);
public MetadataRelation getMetadatarelation(
long objectId, long relateObjectId); 

MetadataReaderServiceImpl类实现了MetadataReaderService接口,在实现中通过注入数据访问对象,完成对元数据信息的读取:

public  class MetadataReaderServiceImpl  implements 
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  class 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);
}
}

imageQuerySqlStatementImpl与MetadataReaderServiceImpl之间不存在任何依赖关系,它只依赖于MetadataReaderService接口,而该接口是抽象的,因而是相对稳定的。例如,我们考虑到系统中的元数据信息较为稳定,但却可能被频繁地读取。为了提高系统的性能,有必要引入元数据信息的缓存。

显然,访问元数据信息的实现发生了变化。它不再是直接通过数据访问对象读取存储在数据库中的元数据信息,而是先读取内存中的缓存。如果缓存中的元数据已经存在,则直接返回,否则,访问数据库。现在,我们可以修改MetadataReaderServiceImpl的实现,却不会影响到调用者QuerySqlStatement,因为MetadataReaderService接口没有发生任何变化。

不过,根据封装的原理,即使没有定义MetadataReaderService接口,在修改MetadataReaderServiceImpl类时,只要我们不改变它所公开的方法,即使调用者依赖于它,似乎也不会受到影响才对。这样的推断并没有错误,错在我们假设的前提。假若我们直接修改MetadataReaderServiceImpl类的实现,如果面对如下两个问题,应该如何应对? 
1、 MetadataReaderServiceImpl类的源代码不允许修改; 
2、 读取元数据信息的其他调用者不需要缓存功能。

第一个问题锁上了修改的大门,第二个问题又添上了另一把锁。它明确地告诉那些希望走捷径的设计者:此路不通!或许,禁止修改源代码的需求过于苛刻,除非使用第三方代码;然而,第二个问题在实际的项目开发中确实存在。我们在实现元数据信息的编辑器时,同样需要调用MetadataReaderServiceImpl类。由于编辑器主要针对元数据进行CRUD操作,因此,元数据信息的变化是很频繁的,此时使用缓存,无异于画蛇添足。

根据OCP原则,好的设计应该对修改是封闭的,对扩展是开放的。接口的引入使得扩展成为可能。我们可以定义一个缓存对象去实现MetadataReaderService接口,它提供了缓存的功能。缓存对象除了拥有管理和访问缓存的职责之外,它还需要读取数据库,以应付缓存未包含指定信息的情况。我们需要在缓存对象中再提供读取数据库的实现逻辑吗?设计与开发切忌浪费,信奉“拿来主义”。在实现某个功能之前,首先应考察是否已有现成的实现,如果有,直接拿来重用即可。目前,MetadataReaderServiceImpl类已经实现了读取数据库的功能,因此,最简单的办法就是将其传递给缓存对象。考虑到MetadataReaderServiceImpl才是真正实现读取的对象,而缓存对象不过是对其包裹了一层外衣而已,为了更好地表达对象的意图,最好将MetadataReaderServiceImpl更名为RealMetadataReaderService,缓存对象则命名为CachedMetadataReaderService:image

public  class CachedMetadataReaderService  implements 
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,如需转载请自行联系原作者

相关文章
|
3月前
|
缓存 前端开发 数据格式
构建前端防腐策略问题之保证组件层的代码不受到接口版本变化的问题如何解决
构建前端防腐策略问题之保证组件层的代码不受到接口版本变化的问题如何解决
|
3月前
|
监控 安全 网络安全
内部隔离
【8月更文挑战第18天】
50 5
|
4月前
软件复用问题之在复用组件降低成本和复用组件引入依赖之间取得平衡,如何解决
软件复用问题之在复用组件降低成本和复用组件引入依赖之间取得平衡,如何解决
|
4月前
软件复用问题之复用性风险是如何定义的
软件复用问题之复用性风险是如何定义的
|
敏捷开发 Java
如何降低类之间的耦合
如何降低类之间的耦合
65 0
|
存储 小程序 JavaScript
再也不用担心组件跨层级的数据共享和方法驱动了
再也不用担心组件跨层级的数据共享和方法驱动了
127 0
|
机器学习/深度学习 分布式计算 自动驾驶
按需求构建架构才是正确之举,过度工程只会“劳民伤财”
按需求构建架构才是正确之举,过度工程只会“劳民伤财”
|
安全 Java API
了解程序运行逻辑的必要性及应用和硬件的关系
了解程序运行逻辑的必要性及应用和硬件的关系
112 0