第十七章 适配器模式
适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能正常工作的两个软件实体可以一起工作。
适配器的别名是包装起(wrapper),这是一种相对简单的模式。在程序开发中有许多这样的场景:当我们试图调用模块或者对象的某个接口时,却发现这个接口的格式并不符合目前的需求。这时候就有两种解决方法,第一种就是修改原有的接口实现。但是如果原来的模块非常复杂,修改原接口就显得不太现实了。
还有一种方法是创建一个适配器,将原接口转化为客户希望的另一个接口,客户只需要跟适配器打交道。
17.1 现实中的适配器
港式插头转换器:港式的电器插头比大陆的插头体积要大一点。如果从香港买了一个 Mac book,我们会发现充电器无法插在家里的插座上,为此我们不可能改造家里的插座,所以我们需要一个适配器
17.2 适配器模式的应用
如果现有的接口可以正常工作,那我们就永远不会用上适配器模式。适配器模式是一种亡羊补牢的模式,没有人会在程序的设计之初就使用它。因为我们不能预料未来会发生的事情,也许今天还好好工作的接口,明天就不适用于新系统。所以我们需要用适配器模式把原来的旧接口包装成一个新接口。
比如现在我们需要使用到地图的插件,现在我们有两个来自谷歌地图和百度地图的插件,它们内部都有一个 show 方法,我们只需要调用它就可以渲染到页面上。我们的代码是这样的:
js1var googleMap = { 2 show: function() { 3 console.log(’开始渲染谷歌地图’); 4 } 5}; 6 7var baiduMap = { 8 show: function() { 9 console.log(’开始渲染百度地图’); 10 } 11}; 12 13var renderMap = function(map) { 14 if (map.show instanceof Function) { 15 map.show(); 16 } 17}; 18 19renderMap(googleMap); // 输出:开始渲染谷歌地图 20renderMap(baiduMap); // 输出:开始渲染百度地图
假设有一天,百度地图插件内部的 show 方法变掉了呢?
js1var baiduMap = { 2 display: function() { 3 console.log(’开始渲染百度地图’); 4 } 5};
这时候,我们的程序遍地都写了 renderMap 方法,而这个方法非常复杂,我们没办法对其修改。
那么我们可以写一个适配器,然后把 renderMap 的参数换掉就可以了
js1var baiduMapAdapter = { 2 show: function () { 3 baiduMap.display() 4 }, 5} 6 7renderMap(baiduMapAdapter)
假设我们正在编写一个渲染广东省地图的页面。目前从第三方资源里获得了广东省的所有城市和它们所对应的 ID,并且成功渲染到页面上:
js1var getGuangdongCity = function() { 2 var guangdongCity = [{ 3 name: 'shenzhen', 4 id: 11, 5 }, { 6 name: 'guangzhou', 7 id: 12, 8 }]; 9 10 return guangdongCity; 11}; 12 13var render = function(fn) { 14 console.log(’开始渲染广东省地图’); 15 document.write(JSON.stringify(fn())); 16}; 17 18render(getGuangdongCity);
目前这个版本的地图已经上线了,但是经过一段时间后发现,render 地图这个插件提供的方法修改了数据接口,最新的数据应该是这样的:
js1var guangdongCity = { 2 shenzhen: 11, 3 guangzhou: 12, 4}
于是为了让老版本的数据也适配,我们可以写一个适配器
js1var getGuangdongCity = function () { 2 var guangdongCity = [ 3 { 4 name: 'shenzhen', 5 id: 11, 6 }, 7 { 8 name: 'guangzhou', 9 id: 12, 10 }, 11 ] 12 return guangdongCity 13} 14 15var render = function (fn) { 16 console.log('开始渲染广东省地图') 17 document.write(JSON.stringify(fn())) 18} 19// 把老版本的数据处理成新的数据 20var addressAdapter = function (oldAddressfn) { 21 var address = {} 22 var oldAddress = oldAddressfn() 23 24 for (var i = 0; i < oldAddress.length; i++) { 25 const c = oldAddress[i] 26 address[c.name] = c.id 27 } 28 29 return function () { 30 return address 31 } 32} 33 34render(addressAdapter(getGuangdongCity))
然后就可以在调用getGuangdongCity的地方用经过 addressAdapter 适配器转换之后的新函数来代替。
17.3 小结
适配器模式是一种相对简单的模式,它的结构和代理模式、装饰者模式等非常相似,都属于包装模式,由一个对象来包装另一个对象。
区别它们的方式是模式的意图
- 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑接口的实现,也不考虑它们未来如何演化。适配器模式不需要改变已有的接口,就可以使它们协同作用。
- 装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象增加功能。装饰者模式常常形成一条长长的装饰链,而适配器模式通常只包装一次。代理模式是为了控制对对象的访问,通常也只包装一次。
- 外观模式的作用和适配器相似,有人把外观者模式看成一组对象的适配器,但外观模式最显著的特点是定义一个新的接口。