JavaScript和TypeScript的单例写法(面向对象)

一、单例类的作用

在我们平时开发H5游戏过程中,有某别对象,希望在内存中只有一份实例,其他任何地方想要获取到这个实例,只能通过这个类提供的静态方法来获取到实例,而任何地方进行new来进行构造的话,都会报错。总结一下这个单例类的要求

  1. 这个类只允许进行一个new的调用构造函数行为
  2. 提供静态方法来访问唯一实例
  3. 提供重复new会报错的设计
  4. 具备合理优化的设计模式

二、单例设计模式

后面是借鉴的AS3的经典写法,写出的JavaScript和TypeScript版本的单例类。
这里的Singleton其实是基类的来的,子类只要继承它就自然能够获得唯一实例的判断了。相当的方便。
实际生产环境的单例基类的内部类封装:

/** 存放初始化过的构造函数 **/
private static classMap: Dictionary<any, Object> = new Dictionary();
constructor()
{
    var clazz: any = clazz = ClassUtils.forInstance(this);
    //为空时,表示浏览器不支持这样读取构造函数
    if (!clazz)
        return;
    // 防止重复实例化
    if (Singleton.classMap.hasKey(clazz))
        throw new Error(this + " 只允许实例化一次!");
    else
        Singleton.classMap.put(clazz, this);
}

可以看到把key和value用一个Dictionary对象来封装了。不过在这里就不搞太复杂了。直接给出最完整的代码。

设计原理是通过一个所有实例都共享的一个数组来存放已经初始化过的对象,然后在那个对象的构造函数里检测这个对象是否已经实例话过了。所以在这里我们就采用2个数组来存放。

/** 存放初始化过的构造函数,这里用数组来存放构造函数 **/
private static classKeys:Function[] = [];
private static classValues:any[] = [];

这里设计得比较面向对象,比较适合开发Html5游戏了。

一、TypeScript的单例写法

Singleton.ts

/**
 * 所有单例的基类,做了单例的基础检查。所有子类最好都写一个getInstance的静态方法来获取
 * @author sodaChen
 * Date:2012-10-29
 */
class Singleton
{
    //其实实际的开发项目中,不一定会用到数组,有可能会把数组之类的进行封装
    /** 存放初始化过的构造函数,这里用数组来存放构造函数 **/
    private static classKeys:Function[] = [];
    private static classValues:any[] = [];

    constructor()
    {
        var clazz: any = this["constructor"];
        //为空时,表示浏览器不支持这样读取构造函数
        if (!clazz)
            return;
        // 防止重复实例化
        if (Singleton.classKeys.indexOf(clazz) != -1)
            throw new Error(this + " 只允许实例化一次!");
        else
        {
            Singleton.classKeys.push(clazz);
            Singleton.classValues.push(this);
        }

    }
    //注意,Singleton是要替换成你自己实现的子类 这里没有实际的作用
    private static instance:Singleton;
    /**
     * 获取实例的静态方法实例
     * @return
     *
     */
    public static getInstance():Singleton
    {
        if(!this.instance)
        {
            this.instance = new Singleton();
        }
        return this.instance;
    }
    /**
     * 销毁方法。事实上单例是很少进行销毁的
     */
    destroy(o: any = null): void
    {
        this.onDestroy();
        Singleton.removeInstance(this["constructor"]);
    }

    /**
     * 子类重写的方法
     */
    protected onDestroy(): void
    {

    }
    /**
     * 删除单例的实例(不对单例本身做任何的销毁,只是删除他的引用)
     * @param clazz 单例的Class对象
     *
     */
    static removeInstance(clazz: Function): void
    {
        var index: number = this.classKeys.indexOf(clazz);
        if (index == -1)
        {
            return null;
        }
        this.classKeys.splice(index, 1);
        this.classValues.splice(index, 1);
    }

    /**
     * 是否存放有这个构造函数
     * @param clazz 构造函数
     * @return {boolean}
     */
    static getFunValue(clazz: Function):any
    {
        let funs:Function[] = this.classKeys;
        let length:number = funs.length;
        for(let i:number = 0; i < length; i++)
        {
            if(clazz == funs[i])
                return this.classValues[i];
        }
        return null;
    }

    /**
     * 获取单例类,若不存在则创建.所有的单例创建的时候,都必须使用这个方法来创建,这样可以做到统一管理单例
     * @param clazz 任意需要实现单例效果的类
     * @return
     *
     */
    static getInstanceOrCreate(clazz:any): any
    {
        var obj: any = this.getFunValue(clazz);
        if (obj)
        {
            return obj;
        }
        obj = new clazz();
        //不是Singleton的子类,则手动添加Singleton构造器会自动添加到classMap
        if (!(obj instanceof Singleton))
        {
            this.classKeys.push(clazz);
            this.classValues.push(obj);
        }
        return obj;
    }
}

测试代码:

var singleton1 = Singleton.getInstance();
console.log("通过Singleton.getInstance实例:" + singleton1);
var singleton2 = Singleton.getInstance();
console.log("singleton1 == singleton2:" + (singleton1 == singleton2));
new Singleton();

测试输出结果:

通过Singleton.getInstance实例:[object Object]
singleton1 == singleton2:true
Uncaught Error: [object Object] 只允许实例化一次!
  Singleton 
  main  
  onload

实际使用中,可以直接继承Singleton,再增加一个静态方法就可以了,比如TickMgr.ts

private static instance:TickMgr;
/**
 * 获取实例的静态方法实例
 * @return
 *
 */
public static getInstance():TickMgr
{
    if(!this.instance)
    {
        this.instance = new TickMgr();
    }
    return this.instance;
}

二、JavaScript的单例写法

原理是一样的,因为TypeScript本身就是编译成js代码的。

/**
 * 所有单例的基类,做了单例的基础检查。所有子类最好都写一个getInstance的静态方法来获取
 * @author sodaChen
 * Date:2012-10-29
 */
var Singleton = (function () {
    function Singleton() {
        var clazz = this["constructor"];
        //为空时,表示浏览器不支持这样读取构造函数
        if (!clazz)
            return;
        // 防止重复实例化
        if (Singleton.classKeys.indexOf(clazz) != -1)
            throw new Error(this + " 只允许实例化一次!");
        else {
            Singleton.classKeys.push(clazz);
            Singleton.classValues.push(this);
        }
    }
    /**
     * 获取实例的静态方法实例
     * @return
     *
     */
    Singleton.getInstance = function () {
        if (!this.instance) {
            this.instance = new Singleton();
        }
        return this.instance;
    };
    /**
     * 销毁方法。事实上单例是很少进行销毁的
     */
    Singleton.prototype.destroy = function (o) {
        if (o === void 0) { o = null; }
        this.onDestroy();
        Singleton.removeInstance(this["constructor"]);
    };
    /**
     * 子类重写的方法
     */
    Singleton.prototype.onDestroy = function () {
    };
    /**
     * 删除单例的实例(不对单例本身做任何的销毁,只是删除他的引用)
     * @param clazz 单例的Class对象
     *
     */
    Singleton.removeInstance = function (clazz) {
        var index = this.classKeys.indexOf(clazz);
        if (index == -1) {
            return null;
        }
        this.classKeys.splice(index, 1);
        this.classValues.splice(index, 1);
    };
    /**
     * 是否存放有这个构造函数
     * @param clazz 构造函数
     * @return {boolean}
     */
    Singleton.getFunValue = function (clazz) {
        var funs = this.classKeys;
        var length = funs.length;
        for (var i = 0; i < length; i++) {
            if (clazz == funs[i])
                return this.classValues[i];
        }
        return null;
    };
    /**
     * 获取单例类,若不存在则创建.所有的单例创建的时候,都必须使用这个方法来创建,这样可以做到统一管理单例
     * @param clazz 任意需要实现单例效果的类
     * @return
     *
     */
    Singleton.getInstanceOrCreate = function (clazz) {
        var obj = this.getFunValue(clazz);
        if (obj) {
            return obj;
        }
        obj = new clazz();
        //不是Singleton的子类,则手动添加Singleton构造器会自动添加到classMap
        if (!(obj instanceof Singleton)) {
            this.classKeys.push(clazz);
            this.classValues.push(obj);
        }
        return obj;
    };
    return Singleton;
}());
//其实实际的开发项目中,不一定会用到数组,有可能会把数组之类的进行封装
/** 存放初始化过的构造函数,这里用数组来存放构造函数 **/
Singleton.classKeys = [];
Singleton.classValues = [];

测试代码是一样的。

扫描二维码关注公众号,回复: 2641250 查看本文章

三、JavaScript更简单和比较传统的单例写法

这个写法其实就是比较偏向做js前端,而不是h5游戏的。
一般是创建一个命名空间来存放,其他地方使用就不会有new的写法了
比如

var asf = {};  

asf.singleton = {} ;  
asf.TickMgr = {};  

外面则是直接调用

asf.singleton.pay("mc");
asf.TickMgr.tick();

猜你喜欢

转载自blog.csdn.net/sujun10/article/details/73065742