yanyue404 / blog

Just blog and not just blog.
https://yanyue404.github.io/blog/
Other
87 stars 13 forks source link

重构 —— 适配器模式 #249

Open yanyue404 opened 1 year ago

yanyue404 commented 1 year ago

适配器模式

适配器模式的作⽤是解决两个软件实体间的接⼝不兼容的问题。使⽤适配器模式之后,原本 由于

接⼝不兼容⽽不能⼯作的两个软件实体可以⼀起⼯作。 适配器的别名是包装器(wrapper),这是⼀

个相对简单的模式。在程序开发中有许多这样的 场景:当我们试图调⽤模块或者对象的某个接⼝

时,却发现这个接⼝的格式并不符合⽬前的需求。 这时候有两种解决办法,第⼀种是修改原来的

接⼝实现,但如果原来的模块很复杂,或者我们拿 到的模块是⼀段别⼈编写的经过压缩的代码,

修改原接⼝就显得不太现实了。第⼆种办法是创建 ⼀个适配器,将原接⼝转换为客户希望的另⼀

个接⼝,客户只需要和适配器打交道。

典型例子:

例子1:renderMap

var googleMap = {
  show: function(){
  console.log( '开始渲染⾕歌地图' );
 }
};
var baiduMap = {
  show: function(){
  console.log( '开始渲染百度地图' );
 }
};

var renderMap = function(map) {
  if (map.show instanceof Function) {
    map.show()
  }
}

renderMap( googleMap ); // 输出:开始渲染⾕歌地图
renderMap( baiduMap ); // 输出:开始渲染百度地图
var googleMap = {
  show: function() {
    console.log('开始渲染谷歌地图')
  }
}

var baiduMap = {
  show: function() {
    console.log('开始渲染百度地图')
  }
}

// tencentMap 提供的显示地图的方法不叫 show 而叫 display
// tencentMap 这个对象来源于第三方,正常情况下我们都不应该去改动它。此时我们可以通过增加 tencentMapAdapter 来解决问题
var tencentMap = {
  display: function() {
    console.log('开始渲染腾讯地图')
  }
}

var renderMap = function(map) {
  if (map.show instanceof Function) {
    map.show()
  }
}

var tencentMapAdapter = {
  show: function() {
    return tencentMap.display()
  }
}

renderMap(googleMap) // 输出:开始渲染谷歌地图
renderMap(baiduMap) // 输出:开始渲染百度地图
renderMap(tencentMapAdapter) // 输出:开始渲染百度地图

例子2:renderGuangdongCity

var getGuangdongCity = function() {
  var guangdongCity = [
    {
      name: 'shenzhen',
      id: 11
    },
    {
      name: 'guangzhou',
      id: 12
    }
  ]
  return guangdongCity
}
var render = function(fn) {
  console.log('开始渲染广东省地图')
  console.log(JSON.stringify(fn()))
}
render(getGuangdongCity)
var guangdongCity = {
  shenzhen: 11,
  guangzhou: 12,
  zhuhai: 13
}
var getGuangdongCity = function () {
  var guangdongCity = [
    {
      name: "shenzhen",
      id: 11,
    },
    {
      name: "guangzhou",
      id: 12,
    },
  ];
  return guangdongCity;
};

// 新增一个数据格式转换的适配器
const addressAdapter = (oldAddressfn) => {
  var address = {},
    oldAddress = oldAddressfn();
  for (let i = 0; i < oldAddress.length; i++) {
    let c = oldAddress[i];
    address[c.name] = c.id;
  }
  return function () {
    return address;
  };
};

var render = function (fn) {
  console.log("开始渲染广东省地图");
  console.log(JSON.stringify(fn()));
};

render(addressAdapter(getGuangdongCity));

其他相似的设计模式

适配器模式是一对相对简单的模式。在本书提到的设计模式中,有一些模式跟适配器模式的 结构非常相似,比如装饰者模式、代理模式和外观模式)。这几种模式都属于"包 装模式",都是由一个对象来包装另一个对象。区别它们的关键仍然是模式的意图。

通俗的方式理解适配器和装饰器的区别

装饰器:原有功能没有或不完善,提供某种新功能,比如网页新增模块、功能组合,sendTD (包装这个方法作为统一的埋点发送方式)

适配器:原有功能是好的,适配某种新平台,比如网页兼容性适配不通的浏览器,sendTd(方法不变的情况下适配新的神策埋点平台)

应用:insureAdapter

  1. 场景

单一仓库模式的 git 项目,在 prd 文件夹中的多个子项目 p1,p2,p3 共用了 store 里面 insuredForm 模块的业务逻辑(核心业务主流程),在更新迭代子项目的过程中,insuredForm 出现了很多条件判断类的逻辑代码来适配新的(个性化)产品,致使单一仓库后期核心业务维护成本加大。

考虑通用性及个性化的适配场景,下面我们采用设计模式中的适配器模式来重构代码。

暂时无法在文档外展示此内容

  // insuredForm.js
  export const namespaced = true;

  export const state = () => ({
    params: {
      a: 123,
    },
  });

  export const getters = {
    data(state, getters, rootState) {
      let result = state.params;
      let { productId } = rootState.configData;
      if (productId === "p1") {
        // do somethings
      }
      result.a = productId === "p2" ? undefined : state.a;

      result.b = ["p2", "p3"].includes(productId);

      return result;
    },
  };

  export const mutations = {};

  export const actions = {
    fetch({ commit, dispatch, getters, state, rootState }) {
      let { productId } = rootState.configData;
      return new Promise(async (resolve, reject) => {
        let { data: res } = await getPloicyInfoByCriteriaApi(getters.data);
        const result = {};
        if (productId === "001") {
          // do somethings
        } else if (["p2", "p3"].includes(productId)) {
          // do somethings
        } else {
          // default
        }
      });
    },
  };
  1. 抽离为单个适配器

抽离个性化产品的差异为单个的适配器。

/**
 * 通用参数信息
 * @param {Object} context
 * @param {Object} context.state
 * @param {Object} context.getters
 * @param {Object} context.rootState
 * @param {Object} params 请求报文
 * @param {Object} res 响应报文
 * @param {Object} templateConfig 模板配置信息(beforeCalculate 有)
 * @return {Object}
 *
 */

// 测算前修改
export const beforeCalculate = (
  { getters, state, rootState },
  params,
  templateConfig
) => {
  // 服务端
  if (process.server) {
    return params;
  }
  return params;
};

// 测算后保存测算结果
export const afterCalculate = ({ state, rootState }, res) => {
  return res;
};

// 核保前报文修改
export const beforeDispatcher = ({ state, rootState }, params) => {
  return params;
};

export default {
  beforeCalculate,
  afterCalculate,
  beforeDispatcher,
};
  1. 使用类继承的方式抽象父类和子类:

增加父类于子类的继承关系使得子类的适配器模式便于个性与共性的升级迭代。

export default class InsureAdapter {
  constructor() {
    // 测算控制
    this.calculate = {
      before: this.beforeCalculate,
      after: this.afterCalculate,
    };
    // 核保控制
    this.insure = {
      before: this.beforeInsure,
    };
  }
  // 测算前修改
  beforeCalculate({ getters, state, rootState }, params) {
    console.log("测算前", params);
    return params;
  }
  // 测算后保存测算结果
  afterCalculate({ state, rootState }, res) {
    console.log("测算后", res);
    return res;
  }
  // 核保前报文修改
  beforeInsure({ state, rootState }, params) {
    console.log("核保前", params);
    return params;
  }
}
import InsureAdapter from "common/insureAdapter";

class ItemInsureAdapter extends InsureAdapter {
  constructor() {
    super();
  }
  // 测算前修改 (子类覆盖父类)
  beforeCalculate({ getters, state, rootState }, params, templateConfig) {
  }
}

export default new ItemInsureAdapter();