TypeScript設計模式之裝飾、代理


看看用TypeScript怎樣實現常見的設計模式,順便復習一下。
學模式最重要的不是記UML,而是知道什么模式可以解決什么樣的問題,在做項目時碰到問題可以想到用哪個模式可以解決,UML忘了可以查,思想記住就好。
這里盡量用原創的,實際中能碰到的例子來說明模式的特點和用處。

裝飾模式 Decorator

特點:在不改變接口的情況下,裝飾器通過組合方式引用對象,並由此在保持對象原有功能的基礎上給對象加上新功能。

用處:當需要不影響現有類並增加新的功能時,可以考慮裝飾模式,它可以動態透明的給對象增加功能。

注意:與繼承的優劣。

下面用TypeScript簡單實現一下裝飾模式:
現在有一輛小轎車,加速到100km/h需要10秒:

interface Movable{
    accelerate();
}

class Car implements Movable{
    accelerate(){
        console.log('加速到100km/h需要10秒');
    }
}

現在想改裝下,提高加速度,加個渦輪增壓器。

class TurboCharger{
    use(){
        console.log('使用渦輪增壓');
    }
}

class RefittedCar implements Movable{
    construct(private car: Car){
    }

    turboCharger = new TurboCharger();

    accelerate(){
        this.car.accelerate();
        this.turboCharger.use();
        console.log('加速到100km/h需要5秒'); 
    }
}

let refitterCar: Movable = new RefittedCar(new Car());

refitterCar.accelerate();

//輸出:
加速到100km/h需要10秒
使用渦輪增壓
加速到100km/h需要5秒

這樣改裝后的車用的還是原來的接口,但新對象卻可以在保持原對象的功能基礎上添加新的加速功能。
代碼實現的特點主要還是在於使用同樣接口,並在新對象里有對原對象的引用,這樣才能使用原對象的功能。

上面的功能其實通過繼承也很容易做到:

class TurboCharger{
    use(){
        console.log('使用渦輪增壓');
    }
}

class RefittedCar extends Car{
    turboCharger = new TurboCharger();

    accelerate(){
        super.accelerate();
        this.turboCharger.use();
        console.log('加速到100km/h需要5秒'); 
    }
}

let refitterCar: Movable = new RefittedCar();
refitterCar.accelerate();

//輸出同樣是:
加速到100km/h需要10秒
使用渦輪增壓
加速到100km/h需要5秒

用哪個好呢?對比看下各自的優缺點:(裝飾器用的是組合)
繼承的優點是 直觀,容易理解,缺點是繼承鏈太長的話將很難維護,並且耦合度較高,相對死板,不夠靈活。
組合的優點是 靈活,但如果裝飾器本身也比較多且復雜時,代碼的復雜度也會增加不少。

就我個人來說在這種場景下還是用組合比較舒服,不是很喜歡在已經使用的類上繼承出子類。
接上面的例子,有個皮卡也要增加加速功能,繼承的話又得再實現一個皮卡子類,而用裝飾模式的話使用時傳進來就好了,本身不需要做任何更改。

當然,如果是從新設計,改下接口的話可能會選繼承,畢竟直觀很多,以后的需求有改動時再重構就好了,代碼保持簡單,怎么簡單怎么來,所謂組合優於繼承也要根據實際情況來定奪。

代理模式 Proxy

特點:在對象前面加了一層,外部需要通過這層代理來操作原對象,代理可以加一些控制來過濾或簡化操作。

用處:當對象不希望被外部直接訪問時可以考慮代理模式,典型的有遠程代理、保護代理、透明代理、虛擬代理等。

注意:與外觀、裝飾器模式的區別。

遠程代理:在Visual Studio里很容易用到,Web Reference,直接把web service當本地引用使用。
保護代理:通常用來對一個對象做權限控制。
虛擬代理:做web頁面時對圖像經常使用虛擬代理,看不到的圖像先用個統一的圖像替代,頁面滑到哪就加載到哪,省資源。

下面用TypeScript簡單實現一下遠程代理模式:
數據接口:

interface DataService{
    getData(): string | Promise<string>;
}

在server端的遠程服務:

class RemoteService implements DataService{
    getData(): string{
        return 'get remote data';
    }
}

假設一個Reqeuster類,可以取server端數據:

class Requester{
    Request(): Promise<string>{
        return Promise.resolve(new RemoteService().getData()); //這里本來應該從網絡取,現在只是演示一下
    }
}

本地代理:

class DataProxy implements DataService{

    async getData(): string{
        return await new Requester().Request();
    }
}

function isPromise(p: string | Promise<string>): p is Promise<string> { //用來判斷是否是promise
    return (<Promise<string>>p).then !== undefined;
}

let dataService: DataService = new DataProxy();
let data = dataService.getData();

if(isPromise(data)){
    data.then(o=>console.log(o));
} else {
    console.log(data);
}

//輸出
get remote data

DataProxy和遠程的RemoteService使用同樣的接口,client像調用本地功能一樣通過DataProxy使用遠程功能。

代理模式與外觀模式的差別在於:
代理和被代理使用同一抽象,並且代理着重於訪問控制。
外觀則着重於簡化原本復雜的操作,並在此基礎上提取新抽象。

代理模式與裝飾器模式的差別在於:
代理的目的一般不是為了增加新功能而在於訪問控制,同時代理通常是自己來創建被代理對象。
裝飾器則着重於增加新功能,且被裝飾對象通常是作為引用傳給裝飾器的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM