TypeScript设计模式之单例、建造者、原型

看看用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,所以浅拷贝没有问题,如果有对象就需要注意浅拷贝是否能满足要求。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

ASP.NET Web API中的Controller

虽然通过Visual Studio向导在ASP.NET Web API项目中创建的 Controller类型默认派生与抽象类型ApiController,但是A...

18110
来自专栏林德熙的博客

win10 uwp unix timestamp 时间戳 转 DateTime

有时候需要把网络的 unix timestamp 转为 C# 的 DateTime ,在 UWP 可以如何转换?

441
来自专栏WindCoder

Java设计模式学习笔记—装饰器模式

文章最后“Java设计模式笔记示例代码整合”为本系列代码整合,所有代码均为个人手打并运行测试,不定期更新。本节内容位于其Decorator包(package)中...

791
来自专栏冷冷

【微信开发】 使用单例设计模式 提供AccessToken 和Jsapi_ticket缓存支持

上一篇 是使用ecache 做的缓存, 有些简单的微信项目并不需要这么复杂,所以就想到单例设计模式  首先,我们先定义一个单例对象 import java.ut...

1957
来自专栏技术博客

编写高质量代码改善C#程序的157个建议[4-9]

  本文首先亦同步到http://www.cnblogs.com/aehyok/p/3624579.html。本文主要来学习记录一下内容:

975
来自专栏WindCoder

Java设计模式学习笔记—工厂模式

想学习设计模式很久了,趁现在有时间边学习边记录一下。目前设计模式学习主要基于菜鸟教程的设计模式,后期不排除会追加从其他地方学来内容。

551
来自专栏菩提树下的杨过

再谈Silverlight中的对象序列化/反序列化

曾经发过一篇如何在Silveright中利用XmlSerializer序列化对象的文章“Silverlight中的序列化”,限于当时的认识有限,一度以为silv...

1808
来自专栏blackheart的专栏

[C#6] 8-异常增强

0. 目录 C#6 新增特性目录 1. 在catch和finally块中使用await 在C#5中引入一对关键字await/async,用来支持新的异步编程模型...

1785
来自专栏刘君君

MAT内存分析工具使用

2646
来自专栏逍遥剑客的游戏开发

关于Singleton

1122

扫码关注云+社区