看看用TypeScript怎樣實現常見的設計模式,順便復習一下。
學模式最重要的不是記UML,而是知道什么模式可以解決什么樣的問題,在做項目時碰到問題可以想到用哪個模式可以解決,UML忘了可以查,思想記住就好。
這里盡量用原創的,實際中能碰到的例子來說明模式的特點和用處。
單例模式 Singleton
特點:在程序的生命周期內只有一個全局的實例,並且不能再new出新的實例。
用處:在一些只需要一個對象存在的情況下,可以使用單例,比如Cache, ThreadPool等。
注意:線程安全,當然,這在單線程的JavaScript環境里是不存在的。
下面用TypeScript寫一個Cache來看看單例模式:
class Cache{
public static readonly Instance: Cache = new Cache();
private _items: {[key: string]: string} = {};
private Cache(){
}
set(key: string, value: string){
this._items[key] = value;
console.log(`set cache with key: '${key}', value: '${value}'`);
}
get(key: string): string{
let value = this._items[key];
console.log(`get cache value: '${value}' with key: '${key}'`);
return value;
}
}
Cache.Instance.set('name', 'brook');
Cache.Instance.get('name');
輸出:
set cache with key: 'name', value: 'brook'
get cache value: 'brook' with key: 'name'
很簡單, 和C#基本一樣, 設置一個全局只讀的Instance
並且把構造函數設為private
,這樣就確保了單例的特點。
可能有人有疑問:靜態Instance在C#里能確保只有一個是CLR的功勞,這里每次訪問Instance會不會重新new一個呢?
帶着疑問來看看上面代碼編譯成JavaScript ES6的結果:
class Cache {
constructor() {
this._items = {};
}
Cache() {
}
set(key, value) {
this._items[key] = value;
console.log(`set cache with key: '${key}', value: '${value}'`);
}
get(key) {
let value = this._items[key];
console.log(`get cache value: '${value}' with key: '${key}'`);
return value;
}
}
Cache.Instance = new Cache();
Cache.Instance.set('name', 'brook');
Cache.Instance.get('name');
可以看到TypeScript的靜態實例Instance其實是直接加到了Cache本身上面,當然也就確保了不會再new出新的來。
建造者模式 Builder
特點:一步一步來構建一個復雜對象,可以用不同組合或順序建造出不同意義的對象,通常使用者並不需要知道建造的細節,通常使用鏈式調用來構建對象。
用處:當對象像積木一樣靈活,並且需要使用者來自己組裝時可以采用此模式,好處是不需要知道細節,調用方法即可,常用來構建如Http請求、生成器等。
注意:和工廠模式的區別,工廠是生產產品,誰生產,怎樣生產無所謂,而建造者重在組裝產品,層級不一樣。
下面用TypeScript寫一個Http的RequestBuilder來看看建造者模式:
enum HttpMethod{
GET,
POST,
}
class HttpRequest {} //假設這是最終要發送的request
class RequestBuilder{
private _method: HttpMethod;
private _headers: {[key: string]: string} = {};
private _querys: {[key: string]: string} = {};
private _body: string;
setMethod(method: HttpMethod): RequestBuilder{
this._method = method;
return this;
}
setHeader(key: string, value: string): RequestBuilder{
this._headers[key] = value;
return this;
}
setQuery(key: string, value: string): RequestBuilder{
this._querys[key] = value;
return this;
}
setBody(body: string): RequestBuilder{
this._body = body;
return this;
}
build(): HttpRequest {
// 根據上面信息生成HttpRequest
}
}
let getRequest = new RequestBuilder()
.setMethod(HttpMethod.GET)
.setQuery('name', 'brook')
.build();
let postRequest = new RequestBuilder()
.setMethod(HttpMethod.POST)
.setHeader('ContentType', 'application/json')
.setBody('body')
.build();
上面RequestBuilder
可以根據傳進來的參數不同來構建出不同的HttpReqeust
對象,這樣使用者就可以按照自己需求來生成想要的對象。
這里有個問題是RequestBuilder
需不需要抽象出來,個人覺得要看情況而定。
首先是保持簡單,不去套UML,只是一個簡單的構造功能給內部使用也沒必要抽象來增加代碼復雜度,但如果業務上這個Builder是封裝在一個庫里面並且要對外提供服務,那還是需要一個抽象來隱藏細節,消除對實現的依賴。
並且如果業務上還需要不同的RequestBuilder,比如說XmlRequestBuilder
JsonRequestBuilder
之類,那就更需要一個抽象了。
原型模式 Prototype
特點:不需要知道對象構建的細節,直接從對象上克隆出來。
用處:當對象的構建比較復雜時或者想得到目標對象相同內容的對象時可以考慮原型模式。
注意:深拷貝和淺拷貝。
JavaScript對這個應該是太了解了,天生就有Prototype,通過Object.create就可以根據對象原型創建一個新的對象。
class Origin{
name: string
}
let origin = new Origin();
origin.name = 'brook';
let cloneObj = Object.create(origin);
console.log(cloneObj.name); // brook
不過還是用代碼簡單實現一下原型模式
interface Clonable<T>{
clone(): T;
}
class Origin implements Clonable<Origin>{
name: string;
clone(): Origin{
let target = new Origin();
target.name = this.name;
return target;
}
}
let origin = new Origin();
origin.name = 'brook';
let cloneObj = origin.clone();
console.log(cloneObj.name); // brook
實現Clonable接口的都具有Clone功能,通過Clone功能就可以實現對象的快速復制,如果屬性很多,想另外創建屬性值也差不多相同的對象,原型就可以派上用場。
當然,還是要注意深拷貝和淺拷貝的問題,上面的代碼只有string,所以淺拷貝沒有問題,如果有對象就需要注意淺拷貝是否能滿足要求。