• js享元模式


    享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。

    内部状态与外部状态

    1. 内部状态存储于对象内部。
    2. 内部状态可以被一些对象共享
    3. 内部状态独立于具体的场景,通常不会改变
    4. 外部状态取决于具体的场景,并根据场景的变化,外部状态不能被共享。

    享元模式是一种用时间换空间的优化模式

    • 可以被对象共享的属性通常被划分为内部状态
    • 外部状态取决于具体场景,并根据场景而变化

    下面给出上面实现代码中的类图,如下所示:

    在上图中,涉及的角色如下几种角色:

    抽象享元角色(Flyweight):此角色是所有的具体享元类的基类,为这些类规定出需要实现的公共接口。那些需要外部状态的操作可以通过调用方法以参数形式传入。

    具体享元角色(ConcreteFlyweight):实现抽象享元角色所规定的接口。如果有内部状态的话,可以在类内部定义。

    享元工厂角色(FlyweightFactory):本角色复杂创建 和管理享元角色。本角色必须保证享元对象可以被系统适当地共享,当一个客户端对象调用一个享元对象的时候,享元工厂角色检查系统中是否已经有一个符合要求 的享元对象,如果已经存在,享元工厂角色就提供已存在的享元对象,如果系统中没有一个符合的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。

    客户端角色(Client):本角色需要存储所有享元对象的外部状态。

    注:上面的实现只是单纯的享元模式,同时还有复合的享元模式,由于复合享元模式较复杂,这里就不给出实现了。

    C#享元模式:

    namespace 享元模式
    {
        class Program
        {
            static void Main(string[] args)
            {
    
                WebSiteFactory f = new WebSiteFactory();
    
                WebSite fx = f.GetWebSiteCategory("产品展示");
                fx.Use(new User("小菜"));
    
                WebSite fy = f.GetWebSiteCategory("产品展示");
                fy.Use(new User("大鸟"));
    
                WebSite fz = f.GetWebSiteCategory("产品展示");
                fz.Use(new User("娇娇"));
    
                WebSite fl = f.GetWebSiteCategory("博客");
                fl.Use(new User("老顽童"));
    
                WebSite fm = f.GetWebSiteCategory("博客");
                fm.Use(new User("桃谷六仙"));
    
                WebSite fn = f.GetWebSiteCategory("博客");
                fn.Use(new User("南海鳄神"));
    
                Console.WriteLine("得到网站分类总数为 {0}", f.GetWebSiteCount());
    
                //string titleA = "大话设计模式";
                //string titleB = "大话设计模式";
    
                //Console.WriteLine(Object.ReferenceEquals(titleA, titleB));
    
    
                Console.Read();
            }
        }
    
        //用户
        public class User
        {
            private string name;
    
            public User(string name)
            {
                this.name = name;
            }
    
            public string Name
            {
                get { return name; }
            }
        }
    
    
        //网站工厂
        class WebSiteFactory
        {
            private Hashtable flyweights = new Hashtable();
    
            //获得网站分类
            public WebSite GetWebSiteCategory(string key)
            {
                if (!flyweights.ContainsKey(key))
                    flyweights.Add(key, new ConcreteWebSite(key));
                return ((WebSite)flyweights[key]);
            }
    
            //获得网站分类总数
            public int GetWebSiteCount()
            {
                return flyweights.Count;
            }
        }
    
        //网站
        abstract class WebSite
        {
            public abstract void Use(User user);
        }
    
        //具体的网站
        class ConcreteWebSite : WebSite
        {
            private string name = "";
            public ConcreteWebSite(string name)
            {
                this.name = name;
            }
    
            public override void Use(User user)
            {
                Console.WriteLine("网站分类:" + name + " 用户:" + user.Name);
            }
        }
    }

    文件上传的例子

    var  id = 0;
    
    window.startUpload = function(uploadType,files){   //uploadType区分是控件还是flash
        for(var i = 0, file; file=files[i++];){
            var uploadObj = new  Upload(uploadType,file.fileName,file.fileSize);
            uploadObj.init(id++);   //给upload对象设置一个唯一的id
        }
    };
    
    var Upload = function(uploadType,fileName,fileSize){
        this.uploadType = uploadType;
        this.fileName = fileName;
        this.fileSize = fileSize;
        this.dom = null;
    };
    
    Upload.prototype.init = function(id){
        var that = this;
        this.id = id;
        this.dom = document.createElement('div');
        this.dom.innerHTML =
            '<span>文件名称:'+ this.fileName + ', 文件大小: ' + this.fileSize + '</span>' +
            '<button class="delFile">删除</button>';
    
        this.dom.querySelector('.delFile').onclick = function(){
            that.delFile();
        }
        document.body.addendChild(this.dom);
    };
    
    Upload.prototype.delFile = function(){
        if(this.fileSize < 3000){
            return this.dom.parentNode.removeChild(this.dom);
        }
    
        if(window.confirm('确定要删除该文件吗?' + this.fileName)){
            return this.dom.parentNode.removeChild(this.dom);
        }
    };

    接下来分别创建3个插件上传对象和3个Flash上传对象:

    startUpload('plugin',[
        {
            fileName: '1.txt',
            fileSize: 1000
        },
        {
            fileName: '2.htm',
            fileSize: 3000
        },
        {
            fileName: '3.txt',
            fileSize: 5000
        }
    ]);
    
    
    startUpload('flash',[
        {
            fileName: '4.txt',
            fileSize: 1000
        },
        {
            fileName: '5.htm',
            fileSize: 3000
        },
        {
            fileName: '6.txt',
            fileSize: 5000
        }
    ]); 

    享元模式重构文件上传 

    上面的代码里有多少个需要上传的文件,就一共创建了多少个upload对象,接下来我们用享元模式重构它。

    首先,我们需要确认插件类型upload是内部状态,那为什么单单uploadType是内部状态呢?前面已经讲过。
    一 旦明确了uploadType,无论我们使用什么方式上传,这个上传对象都是可以被任何文件共用的。而fileName和fileSize是根据场景而变 化的,每个文件的fileName和fileSize都不一样,fileName和fileSize没有办法被共享,它们只能被划分为外部状态。

    明确了uploadType作为内部状态后,我们再把其他的外部状态从构造函数中抽离出来,Upload构造函数中只保留uploadType参数:

    var Upload = function(uploadType){
        this.uploadType = uploadType;
    };

    Upload.prototype.init函数也不再需要,因为upload对象初始化的工作被放在了uploadManager.add函数里, 接下来只需要定义Upload.prototype.del函数即可:

    Upload.prototype.delFile = function(id){
        uploadManager.setExternalState(id,this);   //(1)
        
        if(this.fileSize < 3000){
            return this.dom.parentNode.removeChild(this.dom);
        }
    
        if(window.confirm('确定要删除该文件吗?' + this.fileName)){
            return this.dom.parentNode.removeChild(this.dom);
        }
    };

    在 开始删除文件之前,需要读取文件的实际大小,而文件的实际大小被存储在外部管理器uploadManager中,所以在这里需要通过 uploadManager.setExternalState方法给共享对象设置正确的fileSize,上段代码中的(1)处表示把当前id对应的对 象的外部状态都组装到共享对象中。

    接下来定义一个工厂来创建upload对象,如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新的对象:

    var UploadFactory = (function(){
        var createFlyWeightObjs = {};
    
        return{
            create:function(uploadType){
                if(createFlyWeightObjs[uploadType]){
                    return createFlyWeightObjs[uploadType];
                }
                return createFlyWeightObjs[uploadType] = new Upload(uploadType);
            }
        }
    })();

    现在我们来完善前面提到的uploadManager对象,它负责向UploadFactory提交创建对象的请求,并用一个uploadDatabase对象保存所有upload对象的外部状态,以便在程序运行过程中给upload共享对象设置外部状态,代码如下:

    var uploadManager = (function(){
        var uploadDatabase = {};
    
        return{
            add:function(){id,uploadType,fileName,fileSize){
                var flyWeightObj = UploadFactory.create(uploadType);
    
                var dom = document.createElement('div');
                dom.innerHTML =
                    '<span>文件名称:'+ fileName + ', 文件大小: ' + fileSize + '</span>' +
                    '<button class="delFile">删除</button>';
    
                dom.querySelector('.delFile').onclick = function(){
                    flyWeightObj.delFile(id);
                }
                document.body.appendChild(dom);
    
                uploadDatabase[id] = {
                    fileName:fileName,
                    fileSize:fileSize,
                    dom:dom
                };
    
                return flyWeightObj;
            },
            setExternalState:function(id,flyWeightObj){
                var uploadData = uploadDatabase[id];
                for(var i in uploadData){
                    flyWeightObj[i] = uploadData[i];
                }
            }
        }  
    })();

    然后是开始触发上传动作的startUpload函数:

    var id = 0;
    
    window.startUpload = function(uploadType,files){
        for(var i=0,file;file=files[i++];){
            var uploadObj = uploadManager.add(++id,uploadType,file.fileName,file.fileSize);
        }
    };

    最后是测试时间,运行下面的代码后,可以发现运行结果跟用享元模式重构之前一致:

    startUpload('plugin',[
        {
            fileName: '1.txt',
            fileSize: 1000
        },
        {
            fileName: '2.htm',
            fileSize: 3000
        },
        {
            fileName: '3.txt',
            fileSize: 5000
        }
    ]);
    
    
    startUpload('flash',[
        {
            fileName: '4.txt',
            fileSize: 1000
        },
        {
            fileName: '5.htm',
            fileSize: 3000
        },
        {
            fileName: '6.txt',
            fileSize: 5000
        }
    ]); 

    享元模式重构之前的代码里一共创建了6个upload对象,而通过享元模式重构后,对象的数量减少为2,更幸运的是,就算现在同时上传2000个文件,而需要创建的upload对象数量依然是2。

    享元模式的适用性

    享元模式是一种很好的性能优化方案,但它也会带来一些复杂的问题,从前面两组代码的比较可以看到,使用了享元模式之后,我们需要分别多维护一个factory对象和一个manager对象,在大部分不必要使用享元模式的环境下,这些开销是可以避免的。

    享元模式带来的好处很大程度上取决于如何使用以及何时使用,一般来说,以下情况发生时便可以使用享元模式。

    1.一个程序中使用了大量的相似对象
    2.由于使用了大量对象,造成很大的内存开销。
    3.对象的大多数状态都可以变为外部状态
    4.剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。

    没有内部状态的享元

    在继续使用享元模式的前提下,构造函数Upload就变成了无参数的形式:

    var Upload = function(){};

    其 他属性如 fileName、fileSize、dom依然可以作为外部状态保存在共享对象外部。在uploadType作为内部状态的时候,它可能为控件,也可能 为Flash,所以当时最多可以组合出两个共享对象。而现在已经没有了内部状态,这意味着只需要唯一的一个共享对象。现在我们要改写创建享元对象的工厂, 代码如下:

    var UploadFactory = (function(){
        var uploadObj;
        return{
            create:function(){
                if(uploadObj){
                    return uploadObj;
                }
                return uploadObj = new Upload();
            }
        }
    })();

    管理器部分的代码不需要改动,还是负责剥离和组装外部状态。可以看到,当对象没有内部状态的时候,生产共享对象的工厂实际上变成了一个单列工厂。虽然这时候的共享对象没有内部状态的区分,但还是有剥离外部状态的过程,我们依然倾向于称之为享元模式。

  • 相关阅读:
    completable 用法
    spring 纯注解方式 与AOP
    springIOC原理加载过程
    多线程手写Future模式
    springMVC 实现redis分布式锁
    java线程池学习
    本地跑 spark ui 报错
    九度oj 题目1452:搬寝室
    九度oj 题目1456:胜利大逃亡
    九度oj 题目1455:珍惜现在,感恩生活
  • 原文地址:https://www.cnblogs.com/gongshunkai/p/6640054.html
Copyright © 2020-2023  润新知