重构优化设计模式应用~适配器、中介者、简单工厂、享元模式
最近有一个需求,由于涉及调用第三方接口有所改变,而需求所涉及的应用(lyqc-cas)由于涉及一个基于暴露dubbo服务的应用(lyqc-data),而目前又不想再修改这个应用,只好在一个新应用(gps-provider)中提供基于Eureka服务注册。但是对于该需求,新提供的接口又不兼容老的代码调用,而又希望通过开关开启关闭,以防上线新接口有问题,可以进行降级。鉴于此,认真想了一下,通过设计模式(适配器+中介者)实现。
1、类图关系介绍
- GpsDeviceApprove:接口,提供对老代码调用的兼容,包含了两个业务场景调用,单一调用和批量查询。
- AbstractGpsApproveAdapter:实现了上述接口,并提供了一个抽象方法call(),由子类去实现,同时提供了一个dingTalk()方法,实现钉钉通知左右。
- GpsDubboApproveAdapter 和 GpsProviderApproveAdapter:该子类继承了上述抽象类,并实现各自调用接口实现,如GpsDubboApproveAdapter 提供了基于dubbo接口依赖服务,GpsProviderApproveAdapter提供了基于eurake @feign声明式注解服务。这两个子类是基于适配器模式的应用实现。
- GpsProviderApproveMediator:该类是一个中介者模式的应用,依然实现了GpsDeviceApprove接口,同时,通过一个GpsApproveContext上下文对象进行透传,该类的主要作用时,封装了不同调用接口对原调用方的直接耦合依赖,同时并封装了开关开启关闭降级策略。这样以来,原先直接调用基于dubbo提供的接口服务,现在通关通过这个中介者包装相应调用方接口,而对于调用方无需关注开关,该类直接暴露给调用方调用即可。
2、类源码介绍
2.1、适配器模式
GpsDeviceApprove接口定义
import com.lyqc.data.dto.GpsDataInfoDTO;
import com.yooli.car.cargps.context.GpsApproveContext;
import java.util.List;
/**
* @description: GPS设备商认证接口
* @Date : 2018/9/26 下午4:46
* @Author : 石冬冬-Seig Heil([email protected])
*/
public interface GpsDeviceApprove {
/**
* 认证,一次认证一个
* @param context
* @return
*/
GpsDataInfoDTO only(GpsApproveContext context);
/**
* 批量认证
* @param context
* @return
*/
List<GpsDataInfoDTO> multi(GpsApproveContext context);
}
抽象适配器类AbstractGpsApproveAdapter
import com.lyqc.data.dto.GpsDataInfoDTO;
import com.mljr.ding.auth.DingAuth;
import com.mljr.ding.client.DingRobotClientFactory;
import com.mljr.ding.client.DingRobotService;
import com.mljr.ding.dto.req.MarkdownDingRobotReq;
import com.yooli.car.cargps.context.GpsApproveContext;
import com.yooli.car.cargps.mediator.GpsDeviceApprove;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* @description: 抽象 GPS设备商认证适配器
* @Date : 2018/9/27 下午12:11
* @Author : 石冬冬-Seig Heil([email protected])
*/
public abstract class AbstractGpsApproveAdapter implements GpsDeviceApprove {
protected final Logger LOG = LoggerFactory.getLogger(this.getClass());
private final String MONITOR_LOG_TITLE = "大数据GPS设备号状态接口";
/**
* 钉钉机器人客户端
*/
protected final DingRobotService dingRobotService = DingRobotClientFactory.create("GpsApprove");
/**
* 抽象调用方法
* @param context
* @return
*/
abstract List<GpsDataInfoDTO> call(GpsApproveContext context);
/**
* 钉钉报警
* @param agencyType GPS设备商类型
* @param gpsIds GPS设备号
* @param log 内容
*/
protected void dingTalk(List<String> gpsIds, String agencyType, String log){
try {
MarkdownDingRobotReq robotReq = new MarkdownDingRobotReq();
robotReq.setTitle(MONITOR_LOG_TITLE);
StringBuffer content = new StringBuffer("### ").append(MONITOR_LOG_TITLE);
content.append("\n\n ### gpsIds:\n\n > ").append(gpsIds.toString());
content.append("\n\n ### agencyType:\n\n > ").append(agencyType);
content.append("\n\n ### 报警内容:\n\n > ").append(log);
robotReq.setText(content.toString());
dingRobotService.sendMarkdown(DingAuth.TOKEN, robotReq);
} catch (Exception e) {
LOG.error("[dingTalk]调用异常",e);
}
}
}
这里特别介绍一下这个DingRobotService,由于该接口实现没有在Spring的Bean容器管理,而为了提高对象实例的重用,减少对象实例的创建销毁,使用了简单工厂+享元模式,提供了一个DingRobotClientFactory工厂类,提供对DingRobotService实例的创建。
2.2、简单工厂+享元模式
源码如下:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @description: 钉钉工厂类
* @Date : 2018/9/27 下午2:14
* @Author : 石冬冬-Seig Heil([email protected])
*/
public final class DingRobotClientFactory {
/**
* 享元Map,存储DingRobotFactory的实例容器
*/
public static final Map<String,DingRobotService> SHARE_MAP = new ConcurrentHashMap<>();
/**
* client创建
* @param key
* @return
*/
public static DingRobotService create(String key){
if (null == key || "".equals(key)) {
throw new RuntimeException("参数key不能为空");
}
DingRobotService client = SHARE_MAP.get(key);
if(null == client){
client = new DingRobotClient();
SHARE_MAP.put(key,client);
}
return client;
}
private DingRobotClientFactory(){}
}
GpsDubboApproveAdapter
import com.alibaba.fastjson.JSONObject;
import com.lyqc.data.dto.GpsDataInfoDTO;
import com.lyqc.data.dubbo.GpsDubboService;
import com.yooli.car.cargps.context.GpsApproveContext;
import com.yooli.framework.util.CollectionsTools;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
/**
* @description: 基于GpsProvider提供的Eureka服务
* @Date : 2018/9/26 下午4:49
* @Author : 石冬冬-Seig Heil([email protected])
*/
@Component("gpsDubboApproveAdapter")
public class GpsDubboApproveAdapter extends AbstractGpsApproveAdapter {
@Autowired
private GpsDubboService gpsDubboService;
@Override
public GpsDataInfoDTO only(GpsApproveContext context) {
List<GpsDataInfoDTO> resultList = call(context);
return CollectionsTools.isEmpty(resultList) ? null : resultList.get(0);
}
@Override
public List<GpsDataInfoDTO> multi(GpsApproveContext context) {
return call(context);
}
@Override
List<GpsDataInfoDTO> call(GpsApproveContext context) {
List<GpsDataInfoDTO> resultList = Collections.emptyList();
try {
resultList = gpsDubboService.getGpsInfos(context.getDeviceList());
} catch (Exception e) {
LOG.error("[JSData Dubbo]GPS设备商接口失败,context={}", JSONObject.toJSON(context),e);
super.dingTalk(context.getDeviceList(),context.getAgencyType(),"[JSData Dubbo]GPS设备商接口失败"+e);
}
return resultList;
}
}
GpsProviderApproveAdapter
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.lyqc.base.common.Result;
import com.lyqc.data.dto.GpsDataInfoDTO;
import com.lyqc.gpsprovider.dto.GpsDeviceAgencyDTO;
import com.lyqc.gpsprovider.re.GpsDeviceInfoRe;
import com.yooli.car.cargps.context.GpsApproveContext;
import com.yooli.rpc.GpsProviderClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @description: 基于GpsProvider提供的Eureka服务
* @Date : 2018/9/26 下午4:49
* @Author : 石冬冬-Seig Heil([email protected])
*/
@Component("gpsProviderApproveAdapter")
public class GpsProviderApproveAdapter extends AbstractGpsApproveAdapter {
@Autowired
private GpsProviderClient gpsProviderClient;
@Override
public GpsDataInfoDTO only(GpsApproveContext context) {
return call(context).get(0);
}
@Override
public List<GpsDataInfoDTO> multi(GpsApproveContext context) {
return call(context);
}
/**
* 调用eureka提供的接口
* @param context
* @return
*/
@Override
List<GpsDataInfoDTO> call(GpsApproveContext context){
List<GpsDataInfoDTO> resultList = Collections.emptyList();
List<String> deviceList = context.getDeviceList();
String agencyType = context.getAgencyType();
try {
GpsDeviceAgencyDTO deviceAgencyDTO = GpsDeviceAgencyDTO.builder().deviceList(deviceList).agencyType(context.getAgencyType()).build();
String callResult = gpsProviderClient.approve(deviceAgencyDTO);
if(null == callResult){
super.dingTalk(deviceList,agencyType,"[GpsProvider]GPS设备商接口失败");
}
Result<List<GpsDeviceInfoRe>> result = JSONObject.parseObject(callResult,new TypeReference<Result<List<GpsDeviceInfoRe>>>(){});
if(null != result && result.isSuccess()){
List<GpsDeviceInfoRe> gpsDeviceInfoReList = result.getData();
resultList = new ArrayList<>(gpsDeviceInfoReList.size());
GpsDataInfoDTO gpsDataInfoDTO = null;
for(GpsDeviceInfoRe deviceInfo : gpsDeviceInfoReList){
gpsDataInfoDTO = new GpsDataInfoDTO();
gpsDataInfoDTO.setApplyTime(deviceInfo.getApplyTime());
gpsDataInfoDTO.setGpsId(deviceInfo.getGpsId());
gpsDataInfoDTO.setCity(deviceInfo.getCity());
gpsDataInfoDTO.setResult(deviceInfo.getResult());
gpsDataInfoDTO.setStatus(deviceInfo.getStatusCode());
gpsDataInfoDTO.setStatusV(deviceInfo.getStatusDesc());
resultList.add(gpsDataInfoDTO);
}
}
} catch (Exception e) {
super.dingTalk(deviceList,agencyType,"[GpsProvider]GPS设备商接口异常"+e);
LOG.error("[GpsProvider]GPS设备商接口异常,context={}", JSONObject.toJSON(context),e);
}
return resultList;
}
}
2.3、中介者模式
GpsProviderApproveMediator
import com.lyqc.data.dto.GpsDataInfoDTO;
import com.yooli.car.cargps.context.GpsApproveContext;
import com.yooli.car.cargps.mediator.adapter.GpsDubboApproveAdapter;
import com.yooli.car.cargps.mediator.adapter.GpsProviderApproveAdapter;
import com.yooli.car.product.component.PdConfigParamComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @description: GPS设备商认证 中介者
* @Date : 2018/9/26 下午5:22
* @Author : 石冬冬-Seig Heil([email protected])
*/
@Component("gpsProviderApproveMediator")
public class GpsProviderApproveMediator implements GpsDeviceApprove {
@Autowired
private PdConfigParamComponent pdConfigParamComponent;
@Autowired
@Qualifier("gpsDubboApproveAdapter")
private GpsDubboApproveAdapter gpsDubboApproveAdapter;
@Autowired
@Qualifier("gpsProviderApproveAdapter")
private GpsProviderApproveAdapter gpsProviderApproveAdapter;
/**
* 调用GpsProvider提供Eureka服务
*/
protected boolean gpsProviderEnable;
/**
* 初始化
*/
protected void init(){
gpsProviderEnable = pdConfigParamComponent.getSwitchValue("GpsProviderEnableForCas");
}
@Override
public GpsDataInfoDTO only(GpsApproveContext context) {
init();
return gpsProviderEnable ? gpsProviderApproveAdapter.only(context) : gpsDubboApproveAdapter.only(context);
}
@Override
public List<GpsDataInfoDTO> multi(GpsApproveContext context) {
init();
return gpsProviderEnable ? gpsProviderApproveAdapter.multi(context) : gpsDubboApproveAdapter.multi(context);
}
}
下面的是我的公众号二维码图片,欢迎关注。