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

相关文章

来自专栏更流畅、简洁的软件开发方式

我的数据访问类(第二版)—— for .net2.0 (一)

asp.net2.0已经出来好久了,由于许多的原因一直没有使用,一个月前才开始使用VS2005写东西。 这一个月里又重新学习了一下基础知识,比如多态、接口了什么...

21790
来自专栏hbbliyong

依赖注入(IOC)二

上一章我们讲了构造注入与设值注入,这一篇我们主要讲接口注入与特性注入。 接口注入 接口注入是将抽象类型的入口以方法定义在一个接口中,如果客户类型需要获得这个...

36170
来自专栏学习力

《Java从入门到放弃》JavaSE入门篇:文件操作

20260
来自专栏流柯技术学院

关于lr调用jar在vuser中可以运行,但是controller中却报错的问题

如题,错误如下:javax.xml.parsers.FactoryConfigurationError: Provider org.apache.xerces....

12520
来自专栏逸鹏说道

C# 温故而知新: 线程篇(二) 下

首先介绍下Classic Async Pattern: 其实Classic Async Pattern指的就是我们常见的BeginXXX和EndXXX IAsy...

29770
来自专栏JackieZheng

照虎画猫写自己的Spring

从细节跳出来 看了部分Spring的代码,前面用了四篇内容写了一些读书笔记。 回想起来,论复杂度,Spring够喝上好几壶的。他就像一颗枝繁叶茂的大树,远处看...

21560
来自专栏NetCore

一个让人遗忘的角落—Exception(二)

在上一篇中"一个被人遗忘的角落--Exception(一)"中,跟大家简单介绍了一下Exception,也使大家充分的了解了Exception管理在一个项目中的...

20290
来自专栏java学习

Java基础总结大全(4)

最新通知 ●回复"每日一练"获取以前的题目! ●【新】Android视频更新了!(回复【安卓视频】获取下载链接) ●【新】Ajax知识点视频更新了!(回复【学习...

383130
来自专栏影子

Java解析OFFICE(word,excel,powerpoint)以及PDF的实现方案及开发中的点滴分享

706160
来自专栏Script Boy (CN-SIMO)

文件随机读写专用类——RandomAccessFile

 RandomAccessFile类可以随机读取文件,但是在测试中并不好用; File类可以测试文件存不存在,不存在可以创建文件; FileWriter类可...

25200

扫码关注云+社区

领取腾讯云代金券