看看用TypeScript怎样实现常见的设计模式,顺便复习一下。
学模式最重要的不是记UML,而是知道什么模式可以解决什么样的问题,在做项目时碰到问题可以想到用哪个模式可以解决,UML忘了可以查,思想记住就好。
这里尽量用原创的,实际中能碰到的例子来说明模式的特点和用处。
特点:在程序的生命周期内只有一个全局的实例,并且不能再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,所以浅拷贝没有问题,如果有对象就需要注意浅拷贝是否能满足要求。
|
请发表评论