看看用TypeScript怎樣實現常見的設計模式,順便復習一下。
學模式最重要的不是記UML,而是知道什么模式可以解決什么樣的問題,在做項目時碰到問題可以想到用哪個模式可以解決,UML忘了可以查,思想記住就好。
這里盡量用原創的,實際中能碰到的例子來說明模式的特點和用處。
策略模式 Strategy
特點:用組合的方式調用一些算法或邏輯,並且可以根據狀態不同而選用不同的算法或邏輯。
用處:對象需要運行時切換算法或邏輯可以考慮使用策略模式。
注意:策略的生成方式。
下面用TypeScript簡單實現一個策略模式:
說起策略就想到策略類游戲,年齡大點的可能都玩過War3,人族對獸族時如果偵察到對方不着急升本,用常規萬金油打法,那人族就可以出狗男女來一波流。
如果偵察到獸族跳科技並擺下兩個獸欄,那對方可能是暴飛龍,人族就要家里補個塔防偷農民,然后出點火槍或二本龍鷹。
class Orc{
private _shenKeJi = false;
get shenKeJi(): boolean { // 這里簡單用升科技來判斷是用常規還是飛龍
return this._shenKeJi;
}
set shenKeJi(value: boolean){
this._shenKeJi = value;
}
}
abstract class Stragety{
abstract execute();
}
class RushStragety extends Stragety{
execute(){
console.log('升科技');
console.log('出狗男女');
console.log('一波流');
}
}
class DefendStragety extends Stragety{
execute(){
console.log('補塔防飛龍');
console.log('出火槍');
console.log('升科技');
console.log('出龍鷹');
}
}
class Human{
stragety: Stragety;
checkOrc(orc: Orc){
if(orc.shenKeJi){ //根據獸族情況來決定策略
console.log('偵察到獸族是跳科技打法');
this.stragety = new DefendStragety();
} else {
console.log('偵察到獸族是常規打法');
this.stragety = new RushStragety();
}
}
deal(){
this.stragety && this.stragety.execute();
}
}
let orc = new Orc();
let human = new Human();
orc.shenKeJi = false;
human.checkOrc(orc);
human.deal();
orc.shenKeJi = true;
human.checkOrc(orc);
human.deal();
//輸出
偵察到獸族是常規打法
升科技
出狗男女
一波流
偵察到獸族是跳科技打法
補塔防飛龍
出火槍
升科技
出龍鷹
這樣人族就可以根據獸族的狀態改變來做出不同的應對策略,其實現在游戲的AI基本都是通過決策樹來實現的,也算是策略模式,只是更復雜,通過各種不同的條件最終得到一個決策來做出反應。
另外,有人可能已經發現了,上面生成策略的地方是可以拿出來,用之前講的工廠模式來做,因為實際應用時策略通常比較多,甚至可能同時需要多種相關策略,用工廠模式來生產策略就可以很好的隱藏細節,解除依賴。
模板方法模式 Template Method
特點:通過多態來實現在運行時使用不同的算法或邏輯,通常有一個整體架子,通過抽象方法或虛方法來把細節代碼延遲到子類實現。
用處:當多個類似功能的類有很多相同結構或代碼時,可以抽象出整體架子時可以考慮模板方法。
注意:與策略模式的異同:同樣是細節部分交出去,不同在於策略是對象行為,采用的是組合的方式,而模板方法是類行為,采用的是繼承。
下面用TypeScript簡單實現一個模板方法模式:
比方說發送http請求的代碼,需要向兩台不同的server(A和B)發送請求,兩台server除了url不同,回來的數據格式也不一樣,但由於都是http請求,主體架子是一樣的,所以可以用模板方法來實現下。
class ClassA{} // Server A 返回的數據結構
class ClassB{} // Server B 返回的數據結構
abstract class RequesterBase<T>{
constructor(private url: string){
}
reqeustData(): T{
this.sendReqeust();
return this.handleResponse();
}
protected sendReqeust(){
console.log(`send request, url: ${this.url}`);
}
protected abstract handleResponse(): T; // 不同的server返回的數據交由子類去實現
}
class RequesterForServerA extends RequesterBase<ClassA>{
protected handleResponse(): ClassA{
console.log('handle response for Server A');
return null;
}
}
class RequesterForServerB extends RequesterBase<ClassB>{
protected handleResponse(): ClassB{
console.log('handle response for Server B');
return null;
}
}
let requesterA: RequesterBase<ClassA> = new RequesterForServerA('server A');
let requesterB: RequesterBase<ClassB> = new RequesterForServerB('server B');
requesterA.reqeustData();
requesterB.reqeustData();
//輸出
send request, url: server A
handle response for Server A
send request, url: server B
handle response for Server B
這里可以看到主體功能由基類RequesterBase實現,兩個子類則實現解析數據這些細節,這樣就達到了消除重復代碼的目的。
如果還有個ServerC的request發送部分也不一樣,也沒關系,TypeScript天生虛函數,在子類直接實現reqeustData即可,多態的作用下,運行時還是會調用到子類上。