【代码重构设计模式之运用】之适配器、中介者、简单工厂、享元模式的示例应用

最近有一个需求,由于涉及调用第三方接口有所改变,而需求所涉及的应用(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
 */
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
 */
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
 */
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
 */
@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
 */
@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
 */
@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);
    }
}

下面的是我的公众号二维码图片,欢迎关注。
秋夜无霜

发布了46 篇原创文章 · 获赞 27 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/shichen2010/article/details/82928925
今日推荐