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 条评论
登录 后参与评论

相关文章

来自专栏程序员宝库

精读《高性能 javascript》

本期我来给大家推荐的书是《高性能JavaScript》,在这本书中我们能够了解 javascript 开发过程中的性能瓶颈,如何提升各方面的性能,包括代码的加载...

562
来自专栏技术博文

php设计模式

什么是设计模式 设计模式,是一种解决问题的思维,而并非某种特定的方法。是前人给我们总结的宝贵经验。学习设计模式是为了编写可复用、可拓展、高性能软件。学习设计模式...

2669
来自专栏Kirito的技术分享

JAVA 拾遗 — CPU Cache 与缓存行

最近的两篇文章,介绍了我参加的中间件比赛中一些相对重要的优化,但实际上还存在很多细节优化,出于篇幅限制并未提及,在最近的博文中,我会将他们整理成独立的知识点,并...

601
来自专栏牛客网

腾讯后台开发一面面经

一面大概一个小时,过去扫码专区等待,还有好多霸面的小伙伴。今天是LOL S7 小组赛最后一天,等待的时间赶紧看EDG 对SKT的最后一场关键的比赛,看到中间ED...

6117
来自专栏java一日一条

Java 元编程及其应用

同样是实现一个投票系统,一个是python程序员,基于django-framework,用了半小时就搭建了一个完整系统,另外一个是标准的SSM(Spring-S...

531
来自专栏编程一生

mock打桩之EasyMock

981
来自专栏进击的程序猿

如何组织PHP中的异常

本文的主题是怎么组织php的异常?在大型项目中异常往往被我们忽略,但是如果前期没有很好的规划好,越到项目后期,重构的成本会越大。

621
来自专栏郭霖

Ruby设计模式透析之 —— 模板方法(Template Method)

Java设计模式透析之 —— 模版方法(Template Method) 今天你还是像往常一样来上班,一如既往地开始了你的编程工作。 项目经理告诉你,今天想在...

2047
来自专栏杨建荣的学习笔记

pl/sql中错误的异常处理 (r3笔记第15天)

pl/sql中对于错误的处理是很重要的一个部分,就跟写程序中对于异常的处理一样。可能程序中正常的流程实现部分不是很复杂,但是对于各种可能发生的异常情况都需要面面...

2916
来自专栏Android机器圈

Java设计模式总汇一

PS:首先我们要带着问题读文章 什么是设计模式 为什么要用设计模式 使用设计模式有什么好处   设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设...

33911

扫码关注云+社区