看看用TypeScript怎樣實現常見的設計模式,順便復習一下。
學模式最重要的不是記UML,而是知道什么模式可以解決什么樣的問題,在做項目時碰到問題可以想到用哪個模式可以解決,UML忘了可以查,思想記住就好。
這里盡量用原創的,實際中能碰到的例子來說明模式的特點和用處。
職責鏈模式 Chain of Responsibility
特點:可以讓一個請求被不同的對象處理多次,請求像經過管道一樣, 一路上都可以被攔下處理。
用處:當請求需要被鏈式處理時,可以考慮職責鏈模式,比如事件的冒泡,WebApi的管道Handler等。
注意:鏈的實現。
WebApi的handler可能大家有用過,對發出去的請求和請求回來的數據都可以用自定義handler在發出前或最終回來前進行處理,非常方便,下面用TypeScript來簡單實現一個HttpHandler:
先建立一個抽象Handler類,包含一個發送請求的sendReqeust以及用來鏈式處理的innerHandler:
abstract class HttpHandler{
constructor(protected innerHandler: HttpHandler){}
async sendRequest(req: string): Promise<string>{
if(this.innerHandler){
return await this.innerHandler.sendRequest(req);
} else {
let res = `response`;
console.log(res);
return res;
}
}
}
實現第一個Handler類:
class FirstHttpHandler extends HttpHandler{
async sendRequest(req: string): Promise<string>{
req = `<req1>${req}</req1>`; // 把請求包一下
console.log(req);
let res = await super.sendRequest(req);
res = `<res1>${res}</res1>`; // 把結果包一下
console.log(res);
return res;
}
}
再實現第二個Handler類:
class SecondHttpHandler extends HttpHandler{
async sendRequest(req: string): Promise<string>{
req = `<req2>${req}</req2>`; // 把請求包一下
console.log(req);
let res = await super.sendRequest(req);
res = `<res2>${res}</res2>`; // 把結果包一下
console.log(res);
return res;
}
}
把兩個HttpHandler連起來
let httpHandler = new FirstHttpHandler(new SecondHttpHandler(undefined));
console.log('start')
httpHandler.sendRequest('request').then(res=>console.log('finish'));
輸出:
start
<req1>request</req1> // 發請求前先在FirstHttpHandler里處理request
<req2><req1>request</req1></req2> // 在SecondHttpHandler里再次處理request
response // 返回數據
<res2>response</res2> // SecondHttpHandler對返回數據的第一次處理
<res1><res2>response</res2></res1> // FirstHttpHandler對返回數據的第二次處理
finish
處理的順序就是 1221,中間是真正取數據的,這就是管道處理最基本的代碼,用到的就是職責鏈模式。
當然職責鏈的形成有很多方式,這里采用的是裝飾手段,保存下一個的引用的方式來形成一個鏈表,還可以采用隊列或棧方式保存所有handler,按順序執行。
狀態模式 State
特點:通過狀態來改變對象的行為。
用處:當對象的行為取決於它的狀態或者有很多if else之類的是由狀態控制的時候可以考慮狀態模式,如常見的狀態機。
注意:狀態是由誰來轉換。
下面用TypeScript簡單實現一下狀態模式:
大家都玩過游戲,控制游戲的主角時鼠標左鍵可以是移動,遇到怪時點擊左鍵是攻擊,遇到NPC時是對話。
下面就以這點簡單實現個狀態模式:
角色和狀態的接口,狀態只需要處理當前狀態需要做的事:
interface Role{
name: string;
click();
changeState(state: State);
}
interface State{
handle(role: Role);
}
角色的具體實現:
class Player implements Role{
private state: State;
constructor(public name: string){
}
click(){
if(this.state){
this.state.handle(this);
}
}
changeState(state: State){
this.state = state;
console.log(`change to ${this.state.constructor.name}`);
}
}
狀態的具體實現,分為移動狀態,攻擊狀態,對話狀態:
class MoveState implements State{
static readonly instance = new MoveState();
handle(role: Role){
console.log(`${role.name} is moving`);
}
}
class AttackState implements State{
static readonly instance = new AttackState();
handle(role: Role){
console.log(`${role.name} is attacking`);
}
}
class TalkState implements State{
static readonly instance = new TalkState();
handle(role: Role){
console.log(`${role.name} is talking`);
}
}
使用:
let player = new Player('brook');
player.changeState(MoveState.instance);
player.click();
player.changeState(AttackState.instance);
player.click();
player.changeState(TalkState.instance);
player.click();
//輸出:
change to MoveState
brook is moving
change to AttackState
brook is attacking
change to TalkState
brook is talking
這樣隨着狀態的變化,點擊左鍵做不同的事。
對於由誰來驅動狀態變化可以根據實際情況來考慮,簡單的話直接放角色里面就行,由角色自己決定自己的狀態,復雜的話可以考慮用表來驅動狀態機,通過表過實現狀態的跳轉。