日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線(xiàn)溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
前端的設(shè)計(jì)模式系列-裝飾器模式

大部分講設(shè)計(jì)模式的文章都是使用的 Java、C++ 這樣的以類(lèi)為基礎(chǔ)的靜態(tài)類(lèi)型語(yǔ)言,作為前端開(kāi)發(fā)者,js 這門(mén)基于原型的動(dòng)態(tài)語(yǔ)言,函數(shù)成為了一等公民,在實(shí)現(xiàn)一些設(shè)計(jì)模式上稍顯不同,甚至簡(jiǎn)單到不像使用了設(shè)計(jì)模式,有時(shí)候也會(huì)產(chǎn)生些困惑。

下面按照「場(chǎng)景」-「設(shè)計(jì)模式定義」- 「代碼實(shí)現(xiàn)」- 「易混設(shè)計(jì)模式」-「總」的順序來(lái)總結(jié)一下,如有不當(dāng)之處,歡迎交流討論。

場(chǎng)景

微信小程序定義一個(gè)頁(yè)面是通過(guò)微信提供的Page 方法,然后傳入一個(gè)配置對(duì)象進(jìn)去。

Page({
data: { // 參與頁(yè)面渲染的數(shù)據(jù)
logs: []
},
onLoad: function () {
// 頁(yè)面渲染后 執(zhí)行
}
})

如果我們有個(gè)需求是在每個(gè)頁(yè)面加載的時(shí)候上報(bào)一些自定義數(shù)據(jù)。

最直接的當(dāng)然是去每個(gè)頁(yè)面加就好了,但上報(bào)數(shù)據(jù)的邏輯是一致的,一個(gè)一個(gè)加有些傻了,這里就可以用到裝飾器模式了。

裝飾器模式

看下維基百科的定義。

裝飾器(修飾)模式,是面向?qū)ο蟪淌筋I(lǐng)域中,一種動(dòng)態(tài)地往一個(gè)類(lèi)別中添加新的行為的設(shè)計(jì)模式。就功能而言,修飾模式相比生成子類(lèi)別更為靈活,這樣可以給某個(gè)對(duì)象而不是整個(gè)類(lèi)別添加一些功能。

看一下 UML 類(lèi)圖和次序圖。

當(dāng)訪(fǎng)問(wèn) Component1 中的operation 方法時(shí),會(huì)先調(diào)用預(yù)先定義的兩個(gè)裝飾器 Decorator1 和 Decorator2 中的 operation 方法,執(zhí)行一些額外操作,最后再執(zhí)行原始的 operation 方法。

舉一個(gè)簡(jiǎn)單的例子:

買(mǎi)奶茶的話(huà)可以額外加珍珠、椰果等,不同小料有不同的價(jià)格、也可以自由組合,此時(shí)就可以用到裝飾器模式,對(duì)原始奶茶進(jìn)行加料、算價(jià)。

原始的奶茶有一個(gè)接口和類(lèi)。

interface MilkTea {
public double getCost(); // 返回奶茶的價(jià)格
public String getIngredients(); // 返回奶茶的原料
}

class SimpleMilkTea implements MilkTea {
@Override
public double getCost() {
return 10;
}

@Override
public String getIngredients() {
return "MilkTea";
}
}

下邊引入裝飾器,進(jìn)行加料。

// 添加一個(gè)裝飾器的抽象類(lèi)
abstract class MilkTeaDecorator implements MilkTea {
private final MilkTea decoratedMilkTea;

public MilkTeaDecorator(MilkTea c) {
this.decoratedMilkTea = c;
}

@Override
public double getCost() {
return decoratedMilkTea.getCost();
}

@Override
public String getIngredients() {
return decoratedMilkTea.getIngredients();
}
}

// 添加珍珠
class WithPearl extends MilkTeaDecorator {
public WithPearl(MilkTea c) {
super(c); // 調(diào)用父類(lèi)構(gòu)造函數(shù)
}

@Override
public double getCost() {
// 調(diào)用父類(lèi)方法
return super.getCost() + 2;
}

@Override
public String getIngredients() {
// 調(diào)用父類(lèi)方法
return super.getIngredients() + ", 加珍珠";
}
}

// 添加椰果
class WithCoconut extends MilkTeaDecorator {
public WithCoconut(MilkTea c) {
super(c);
}

@Override
public double getCost() {
return super.getCost() + 1;
}

@Override
public String getIngredients() {
return super.getIngredients() + ", 加椰果";
}
}

讓我們測(cè)試一下,

public class Main {
public static void printInfo(MilkTea c) {
System.out.println("價(jià)格: " + c.getCost() + "; 加料: " + c.getIngredients());
}

public static void main(String[] args) {
MilkTea c = new SimpleMilkTea();
printInfo(c); // 價(jià)格: 10.0; 加料: MilkTea

c = new WithPearl(new SimpleMilkTea());
printInfo(c); // 價(jià)格: 12.0; 加料: MilkTea, 加珍珠

c = new WithCoconut(new WithPearl(new SimpleMilkTea()));
printInfo(c); // 價(jià)格: 13.0; 加料: MilkTea, 加珍珠, 加椰果
}
}

未來(lái)如果需要新增一種小料,只需要新寫(xiě)一個(gè)裝飾器類(lèi),并且可以和之前的小料隨意搭配。

// 添加冰淇淋
class WithCream extends MilkTeaDecorator {
public WithCream(MilkTea c) {
super(c);
}

@Override
public double getCost() {
return super.getCost() + 5;
}

@Override
public String getIngredients() {
return super.getIngredients() + ", 加冰淇淋";
}
}

public class Main {
public static void printInfo(MilkTea c) {
System.out.println("價(jià)格: " + c.getCost() + "; 加料: " + c.getIngredients());
}

public static void main(String[] args) {
c = new WithCoconut(new WithCream(new WithPearl(new SimpleMilkTea())));
printInfo(c); // 價(jià)格: 18.0; 加料: MilkTea, 加珍珠, 加冰淇淋, 加椰果
}
}

讓我們用 js 改寫(xiě)一下,達(dá)到同樣的效果。

const SimpleMilkTea = () => {
return {
getCost() {
return 10;
},

getIngredients() {
return "MilkTea";
},
};
};

// 加珍珠
const WithPearl = (milkTea) => {
return {
getCost() {
return milkTea.getCost() + 2;
},

getIngredients() {
return milkTea.getIngredients() + ", 加珍珠";
},
};
};

// 加椰果
const WithCoconut = (milkTea) => {
return {
getCost() {
return milkTea.getCost() + 1;
},

getIngredients() {
return milkTea.getIngredients() + ", 加椰果";
},
};
};

// 加冰淇淋
const WithCream = (milkTea) => {
return {
getCost() {
return milkTea.getCost() + 5;
},

getIngredients() {
return milkTea.getIngredients() + ", 加冰淇淋";
},
};
};

// test
const printInfo = (c) => {
console.log(
"價(jià)格: " + c.getCost() + "; 加料: " + c.getIngredients()
);
};

let c = SimpleMilkTea();
printInfo(c); // 價(jià)格: 10; 加料: MilkTea

c = WithPearl(SimpleMilkTea());
printInfo(c); // 價(jià)格: 12; 加料: MilkTea, 加珍珠

c = WithCoconut(WithPearl(SimpleMilkTea()));
printInfo(c); // 價(jià)格: 13; 加料: MilkTea, 加珍珠, 加椰果

c = WithCoconut(WithCream(WithPearl(SimpleMilkTea())));
printInfo(c); // 價(jià)格: 18; 加料: MilkTea, 加珍珠, 加冰淇淋, 加椰果

沒(méi)有再定義類(lèi)和接口,js 中用函數(shù)直接表示。

原始的 SimpleMilkTea 方法返回一個(gè)奶茶對(duì)象,然后又定義了三個(gè)裝飾函數(shù),傳入一個(gè)奶茶對(duì)象,返回一個(gè)裝飾后的對(duì)象。

代碼實(shí)現(xiàn)

回到文章最開(kāi)頭的場(chǎng)景,我們需要為每個(gè)頁(yè)面加載的時(shí)候上報(bào)一些自定義數(shù)據(jù)。其實(shí)我們只需要引入一個(gè)裝飾函數(shù),將傳入的 option 進(jìn)行裝飾返回即可。

const Base = (option) => {
const { onLoad ...rest } = option;
return {
...rest,
// 重寫(xiě) onLoad 方法
onLoad(...args) {
// 增加路由字段
this.reportData(); // 上報(bào)數(shù)據(jù)

// onLoad
if (typeof onLoad === 'function') {
onLoad.call(this, ...args);
}
}
reportData() {
// 做一些事情
}
}

然后回到原始頁(yè)面增加 Base 的調(diào)用即可。

Page(Base({
data: { // 參與頁(yè)面渲染的數(shù)據(jù)
logs: []
},
onLoad: function () {
// 頁(yè)面渲染后 執(zhí)行
}
})

同理,利用裝飾器模式我們也可以對(duì)其它生命周期統(tǒng)一插入我們需要做的事情,而不需要業(yè)務(wù)方自己再寫(xiě)一遍。

在大團(tuán)隊(duì)的話(huà),每個(gè)業(yè)務(wù)方可能都需要在小程序生命周期做一些事情,此時(shí)只需要利用裝飾器模式,編寫(xiě)一個(gè)裝飾函數(shù),然后在業(yè)務(wù)代碼中調(diào)用即可。

最終的業(yè)務(wù)代碼可能會(huì)裝飾很多層,最終才傳給小程序 Page 函數(shù)。

Page(Base(Log(Ce({
data: { // 參與頁(yè)面渲染的數(shù)據(jù)
logs: []
},
onLoad: function () {
// 頁(yè)面渲染后 執(zhí)行
}
})

易混設(shè)計(jì)模式

如果之前看過(guò)代理模式,到這里可能會(huì)有一些困惑,因?yàn)楹痛砟J降淖饔煤芟?,都是?duì)原有對(duì)象進(jìn)行包裝,增強(qiáng)原有對(duì)象。

但還是有很大的不同點(diǎn):

代理模式中,我們是直接將原對(duì)象封裝到代理對(duì)象之中,對(duì)于業(yè)務(wù)方并不關(guān)系原始對(duì)象,直接使用代理對(duì)象即可。

裝飾器模式中,我們只提供了裝飾函數(shù),輸入原始對(duì)象,輸出增強(qiáng)對(duì)象。輸出的增強(qiáng)對(duì)象,還可以接著傳入到新的裝飾器函數(shù)中繼續(xù)增強(qiáng)。對(duì)于業(yè)務(wù)方,可以隨意組合裝飾函數(shù),但得有一個(gè)最最開(kāi)始的原始對(duì)象。

再具體點(diǎn):

代理模式的話(huà),對(duì)象之間的依賴(lài)關(guān)系已經(jīng)寫(xiě)死了,原始對(duì)象 A,新增代理對(duì)象A1, A1 的基礎(chǔ)上再新增代理對(duì)象A2。如果我們不想要 A1 新增的功能了,我們并不能直接使用 A2 ,因?yàn)锳2 已經(jīng)包含了 A1 的功能,我們只能在 A 的基礎(chǔ)上再新寫(xiě)一個(gè)代理對(duì)象A3。

而裝飾器模式,我們只提供裝飾函數(shù)A1,裝飾函數(shù) A2,然后對(duì)原始對(duì)象進(jìn)行裝飾 A2(A1(A))。如果不想要 A1新增的功能,只需要把 A1 這個(gè)裝飾器去掉,調(diào)用 A2(A) 即可。

所以使用代理模式還是使用裝飾器模式,取決于我們是要把所有功能包裝后最終產(chǎn)出一個(gè)對(duì)象給業(yè)務(wù)方使用,還是提供許多功能,讓業(yè)務(wù)方自由組合。

裝飾器模式同樣踐行了「單一職責(zé)原則」,可以把對(duì)象/函數(shù)的各個(gè)功能獨(dú)立出來(lái),降低它們之間的耦合性。

業(yè)務(wù)開(kāi)發(fā)中,如果某個(gè)對(duì)象/函數(shù)擁有了太多功能,可以考慮使用裝飾器模式進(jìn)行拆分。


本文標(biāo)題:前端的設(shè)計(jì)模式系列-裝飾器模式
網(wǎng)頁(yè)鏈接:http://www.dlmjj.cn/article/cdgdheh.html