自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面向前端工程師的設(shè)計(jì)模式-適配器模式

開發(fā) 后端
適配器模式(Adapter Pattern)又稱包裝器模式,將一個(gè)類(對(duì)象)的接口(方法、屬性)轉(zhuǎn)化為用戶需要的另一個(gè)接口,解決類(對(duì)象)之間接口不兼容的問題。

1. 介紹

1.1 定義?

適配器模式(Adapter Pattern)又稱包裝器模式,將一個(gè)類(對(duì)象)的接口(方法、屬性)轉(zhuǎn)化為用戶需要的另一個(gè)接口,解決類(對(duì)象)之間接口不兼容的問題。

  • 舊的接口和使用者不兼容
  • 中間加一個(gè)適配器轉(zhuǎn)換接口

1.2 主要功能?

主要功能是進(jìn)行轉(zhuǎn)換匹配,目的是復(fù)用已有的功能,而不是來實(shí)現(xiàn)新的接口。也就是說,訪問者需要的功能應(yīng)該是已經(jīng)實(shí)現(xiàn)好了的,不需要適配器模式來實(shí)現(xiàn),適配器模式主要是負(fù)責(zé)把不兼容的接口轉(zhuǎn)換成訪問者期望的格式而已。

2. 生活中的例子?

  • 電源接口的轉(zhuǎn)接頭、Type-C 轉(zhuǎn) HDMI 等視頻轉(zhuǎn)接頭。
  • 同聲傳譯,充當(dāng)兩國友人互相交流的中間人

在類似場(chǎng)景中,這些例子有以下特點(diǎn):

  • 舊有接口格式已經(jīng)不滿足現(xiàn)在的需要。
  • 通過增加適配器來更好地使用舊有接口。

3. 通用實(shí)現(xiàn)?

3.1 角色?

  • Target:目標(biāo)抽象類
  • Adapter:適配器類
  • Adaptee:適配者類
  • Client:客戶類

3.2 代碼?

class Socket {
output() {
return '輸出220V';
}
}

abstract class Power {
abstract charge(): string;
}
class PowerAdapter extends Power {
constructor(public socket: Socket) {
super();
}
//轉(zhuǎn)換后的接口和轉(zhuǎn)換前不一樣
charge() {
return this.socket.output() + ' 經(jīng)過轉(zhuǎn)換 輸出24V';
}
}
let powerAdapter = new PowerAdapter(new Socket());
console.log(powerAdapter.charge());

4. 場(chǎng)景?

當(dāng)你想用已有對(duì)象的功能,卻想修改它的接口時(shí),一般可以考慮一下是不是可以應(yīng)用適配器模式。

  • 如果你想要使用一個(gè)已經(jīng)存在的對(duì)象,但是它的接口不滿足需求,那么可以使用適配器模式,把已有的實(shí)現(xiàn)轉(zhuǎn)換成你需要的接口。
  • 如果你想創(chuàng)建一個(gè)可以復(fù)用的對(duì)象,而且確定需要和一些不兼容的對(duì)象一起工作,這種情況可以使用適配器模式,然后需要什么就適配什么。

4.1 axios?

  • Axios
  • dispatchRequest

axios源碼中采用了process和XMLHttpRequest。 通過宿主環(huán)境的特有對(duì)象識(shí)別當(dāng)前環(huán)境,適配出不同環(huán)境下如:客戶端瀏覽器和nodejs的請(qǐng)求方式。

  • defaults

/adapters 目錄中包含如下這些文件

├─adapters
│ http.js
│ README.md
│ xhr.js
  • xhr
  • http

適配器的入?yún)⒍际莄onfig,返回的都是promise

//let axios = require('axios');
let url = require('url');

function axios(config: any): any {
let adaptor = getDefaultAdapter();
return adaptor(config);
}

axios({
method: 'GET',
url: 'http://localhost:8080/api/user?id=1'
}).then(function (response: any) {
console.log(response);
}, function (error: any) {
console.log(error);
})

function xhr(config: any) {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open(config.method, config.url, true);
request.onreadystatechange = function () {
if (request.readyState == 4) {
if (request.status == 200) {
resolve(request.response);
} else {
reject('請(qǐng)求失敗');
}
}
}
})
}

function http(config: any) {
let http = require('http');
let urlObject = url.parse(config.url);
return new Promise(function (resolve, reject) {
const options = {
hostname: urlObject.hostname,
port: urlObject.port,
path: urlObject.pathname,
method: config.method
};
var req = http.request(options, function (res: any) {
let chunks: any[] = [];
res.on('data', (chunk: any) => {
chunks.push(chunk);
});
res.on('end', () {
resolve(Buffer.concat(chunks).toString());
});
});
req.on('error', (err: any) => {
reject(err);
});
req.end();
})
}
function getDefaultAdapter(): any {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
adapter = xhr;
} else if (typeof process !== 'undefined') {
adapter = http;
}
return adapter;
}

server.js

let express = require('express');
let app = express();
app.get('/api/user', (req, res) => {
res.json({ id: req.query.id, name: 'zhufeng' });
});
app.listen(8080);

4.2 jQuery.ajax 適配 Axios?

有的使用 jQuery 的老項(xiàng)目使用 $.ajax 來發(fā)送請(qǐng)求,現(xiàn)在的新項(xiàng)目一般使用 Axios,那么現(xiàn)在有個(gè)老項(xiàng)目的代碼中全是 $.ajax,如果逐個(gè)修改,無疑工作量巨大而且很容易引發(fā)各種亂七八糟 bug,這時(shí)可以采用適配器模式來將老的使用形式適配到新的技術(shù)棧上:

/* 適配器 */
function ajax2AxiosAdapter(ajaxOptions) {
return axios({
url: ajaxOptions.url,
method: ajaxOptions.type,
responseType: ajaxOptions.dataType,
data: ajaxOptions.data
})
.then(ajaxOptions.success)
.catch(ajaxOptions.error)
}

/* 經(jīng)過適配器包裝 */
$.ajax = function(options) {
return ajax2AxiosAdapter(options);
}

// 測(cè)試:用 jQuery 的方式發(fā)送一個(gè) Ajax 請(qǐng)求
$.ajax({
url: '/demo-url',
type: 'POST',
dataType: 'json',
data: {
name: '張三',
id: '13'
},
success: function(data) {
console.log('請(qǐng)求成功!')
},
error: function(err) {
console.error('請(qǐng)求失?。?)
}
})

可以看到老的代碼表現(xiàn)形式依然不變,但是真正發(fā)送請(qǐng)求是通過新的發(fā)送方式來進(jìn)行的。當(dāng)然你也可以把 Axios 的請(qǐng)求適配到 $.ajax 上,就看你如何使用適配器了。

4.3 promisify?

  • 作用:將callback形式轉(zhuǎn)換為Promise對(duì)象
  • Node中異步回調(diào)中有個(gè)約定:Error first,回調(diào)函數(shù)中的第一個(gè)參數(shù)一定是Error對(duì)象,其余參數(shù)才是正確的數(shù)據(jù)。
let fs = require('fs');
var Bluebird = require("bluebird");
let readFile = Bluebird.promisify(fs.readFile);

(async function () {
let content = await readFile('./1.txt', 'utf8');
console.log(content);
})()
function promisify(readFile: any) {
return function (filename: any, encoding: any) {
return new Promise(function (resolve, reject) {
readFile(filename, encoding, function (err: any, data: any) {
if (err)
reject(err);
else
resolve(data);
})
});
}
}

4.4 業(yè)務(wù)數(shù)據(jù)適配?

在實(shí)際項(xiàng)目中,我們經(jīng)常會(huì)遇到樹形數(shù)據(jù)結(jié)構(gòu)和表形數(shù)據(jù)結(jié)構(gòu)的轉(zhuǎn)換,比如全國省市區(qū)結(jié)構(gòu)、公司組織結(jié)構(gòu)、軍隊(duì)編制結(jié)構(gòu)等等。以公司組織結(jié)構(gòu)為例,在歷史代碼中,后端給了公司組織結(jié)構(gòu)的樹形數(shù)據(jù),在以后的業(yè)務(wù)迭代中,會(huì)增加一些要求非樹形結(jié)構(gòu)的場(chǎng)景。比如增加了將組織維護(hù)起來的功能,因此就需要在新增組織的時(shí)候選擇上級(jí)組織,在某個(gè)下拉菜單中選擇這個(gè)新增組織的上級(jí)菜單?;蛘咴黾恿藢⑷藛T歸屬到某一級(jí)組織的需求,需要在某個(gè)下拉菜單中選擇任一級(jí)組織。

在這些業(yè)務(wù)場(chǎng)景中,都需要將樹形結(jié)構(gòu)平鋪開,但是我們又不能直接將舊有的樹形結(jié)構(gòu)狀態(tài)進(jìn)行修改,因?yàn)樵陧?xiàng)目別的地方已經(jīng)使用了老的樹形結(jié)構(gòu)狀態(tài),這時(shí)我們可以引入適配器來將老的數(shù)據(jù)結(jié)構(gòu)進(jìn)行適配:

/* 原來的樹形結(jié)構(gòu) */
const oldTreeData = [
{
name: '總部',
place: '一樓',
children: [
{ name: '財(cái)務(wù)部', place: '二樓' },
{ name: '生產(chǎn)部', place: '三樓' },
{
name: '開發(fā)部', place: '三樓', children: [
{
name: '軟件部', place: '四樓', children: [
{ name: '后端部', place: '五樓' },
{ name: '前端部', place: '七樓' },
{ name: '技術(shù)支持部', place: '六樓' }]
}, {
name: '硬件部', place: '四樓', children: [
{ name: 'DSP部', place: '八樓' },
{ name: 'ARM部', place: '二樓' },
{ name: '調(diào)試部', place: '三樓' }]
}]
}
]
}
]

/* 樹形結(jié)構(gòu)平鋪 */
function treeDataAdapter(treeData, lastArrayData = []) {
treeData.forEach(item {
if (item.children) {
treeDataAdapter(item.children, lastArrayData)
}
const { name, place } = item
lastArrayData.push({ name, place })
})
return lastArrayData
}

// 測(cè)試:返回平鋪的組織結(jié)構(gòu)
treeDataAdapter(oldTreeData)

增加適配器后,就可以將原先狀態(tài)的樹形結(jié)構(gòu)轉(zhuǎn)化為所需的結(jié)構(gòu),而并不改動(dòng)原先的數(shù)據(jù),也不對(duì)原來使用舊數(shù)據(jù)結(jié)構(gòu)的代碼有所影響。

4.5 Vue 計(jì)算屬性?

Vue 中的計(jì)算屬性也是一個(gè)適配器模式的實(shí)例,以官網(wǎng)的例子為例,我們可以一起來理解一下:

<template>
<div id="example">
<p>Original message: "` message `"</p> <!-- Hello -->
<p>Computed reversed message: "` reversedMessage `"</p> <!-- olleH -->
</div>
</template>

<script type='text/javascript'>
export default {
name: 'demo',
data() {
return {
message: 'Hello'
}
},
computed: {
reversedMessage: function() {
return this.message.split('').reverse().join('')
}
}
}
</script>

舊有 data 中的數(shù)據(jù)不滿足當(dāng)前的要求,通過計(jì)算屬性的規(guī)則來適配成我們需要的格式,對(duì)原有數(shù)據(jù)并沒有改變,只改變了原有數(shù)據(jù)的表現(xiàn)形式。

4.6 Sequelize?

基于promise的Node.js ORM工具

Sequelize

Sequelize支持MySQL、MariaDB、SQLite等數(shù)據(jù)庫方言的適配

方言

//cnpm i sequelize sqlite3 -S
const { Sequelize, Model, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');

class User extends Model { }
User.init({
username: DataTypes.STRING
}, { sequelize, modelName: 'user' });

sequelize.sync()
.then(() User.create({
username: 'zhufeng'
}))
.then(result {
console.log(result.toJSON());
});

5. 設(shè)計(jì)原則驗(yàn)證?

  • 將舊接口和使用者進(jìn)行分離
  • 符合開放封閉原則

6. 優(yōu)缺點(diǎn)?

6.1 優(yōu)點(diǎn)?

  • 已有的功能如果只是接口不兼容,使用適配器適配已有功能,可以使原有邏輯得到更好的復(fù)用,有助于避免大規(guī)模改寫現(xiàn)有代碼。
  • 可擴(kuò)展性良好,在實(shí)現(xiàn)適配器功能的時(shí)候,可以調(diào)用自己開發(fā)的功能,從而方便地?cái)U(kuò)展系統(tǒng)的功能。
  • 靈活性好,因?yàn)檫m配器并沒有對(duì)原有對(duì)象的功能有所影響,如果不想使用適配器了,那么直接刪掉即可,不會(huì)對(duì)使用原有對(duì)象的代碼有影響。

6.2 缺點(diǎn)?

會(huì)讓系統(tǒng)變得零亂,明明調(diào)用 A,卻被適配到了 B,如果系統(tǒng)中這樣的情況很多,那么對(duì)可閱讀性不太友好。如果沒必要使用適配器模式的話,可以考慮重構(gòu),如果使用的話,可以考慮盡量把文檔完善。

7. 其他相關(guān)模式?

7.1 適配器模式與代理模式?

  • 適配器模式:提供一個(gè)不一樣的接口,由于原來的接口格式不能用了,提供新的接口以滿足新場(chǎng)景下的需求。
  • 代理模式:提供一模一樣的接口,由于不能直接訪問目標(biāo)對(duì)象,找個(gè)代理來幫忙訪問,使用者可以就像訪問目標(biāo)對(duì)象一樣來訪問代理對(duì)象。

7.2 適配器模式、裝飾者模式與代理模式?

  • 適配器模式:功能不變,只轉(zhuǎn)換了原有接口訪問格式。
  • 裝飾者模式:擴(kuò)展功能,原有功能不變且可直接使用。
  • 代理模式:原有功能不變,但一般是經(jīng)過限制訪問的。

?文章出自:??前端餐廳ReTech??,如有轉(zhuǎn)載本文請(qǐng)聯(lián)系前端餐廳ReTech今日頭條號(hào)。

github:https://github.com/zuopf769

責(zé)任編輯:武曉燕 來源: 今日頭條
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)