zoukankan      html  css  js  c++  java
  • The Flyweight Pattern in Javascript

         Flyweight Pattern,中文可译作享元模式。它的核心是分离对象的:内在属性和外部属性,然后共享内在属性,组装外在属性。看一个汽车的例子: 

    /* Car class, un-optimized. */
    var Car = function (make, model, year, owner, tag, renewDate) {
        this.make = make;
        this.model = model;
        this.year = year;
        this.owner = owner;
        this.tag = tag;
        this.renewDate = renewDate;
    };
    Car.prototype = {
        getMake: function () {
            return this.make;
        },
        getModel: function () {
            return this.model;
        },
        getYear: function () {
            return this.year;
        },
        transferOwnership: function (newOwner, newTag, newRenewDate) {
            this.owner = newOwner;
            this.tag = newTag;
            this.renewDate = newRenewDate;
        },
        renewRegistration: function (newRenewDate) {
            this.renewDate = newRenewDate;
        },
        isRegistrationCurrent: function () {
            var today = new Date();
            return today.getTime() < Date.parse(this.renewDate);
        }
    };

    很OOP,但是当需要很多Car的实例时,浏览器可能就慢了。 分析Car的属性,前3者终生不变,并且可以大量复制,作为内在属性。改进如下:

    /* Car class, optimized as a flyweight. */
    var Car = function (make, model, year) {
        this.make = make;
        this.model = model;
        this.year = year;
    };
    Car.prototype = {
        getMake: function () {
            return this.make;
        },
        getModel: function () {
            return this.model;
        },
        getYear: function () {
            return this.year;
        }
    };

    /* CarFactory singleton. */
    var CarFactory = (function () {
        var createdCars = {};
        return {
            createCar: function (make, model, year) {
                if (createdCars[make + '-' + model + '-' + year]) {
                    return createdCars[make + '-' + model + '-' + year];
                }else {
                    var car = new Car(make, model, year);
                    createdCars[make + '-' + model + '-' + year] = car;
                    return car;
                }
            }
        };
    })();

    /* CarRecordManager singleton. */
    var CarRecordManager = (function () {
        var carRecordDatabase = {};
        return {
            addCarRecord: function (make, model, year, owner, tag, renewDate) {
                var car = CarFactory.createCar(make, model, year);
                carRecordDatabase[tag] = {
                    owner: owner,
                    renewDate: renewDate,
                    car: car
                };
                return carRecordDatabase[tag];
            },
            getCar: function (tag) {
                return carRecordDatabase[tag];
            },
            transferOwnership: function (tag, newOwner, newTag, newRenewDate) {
                var record = this.getCar(tag);
                record.owner = newOwner;
                record.tag = newTag;
                record.renewDate = newRenewDate;
            },
            renewRegistration: function (tag, newRenewDate) {
                this.getCar(tag).renewDate = newRenewDate;
            },
            isRegistrationCurrent: function (tag) {
                var today = new Date();
                return today.getTime() < Date.parse(this.getCar(tag).renewDate);
            }
        };
    })();


    // test
    (function () {
        var car = CarRecordManager.addCarRecord("test make", "test model", "2011", "Ray", "JB001", "2012-09-29");
        var car2 = CarRecordManager.addCarRecord("test make", "test model", "2011", "Tina", "JB002", "2011-08-27");
        var car1 = CarRecordManager.getCar("JB001");
        console.log(car == car1);               // true
        console.log(car1.car == car2.car);      // true
        console.log(car2.owner);                // tina
    })();   

    可以看到,即便需要很多的car,有CarFactory的控制,每个make + model + year的组合的Car实例实质上分别只会存在一个。 而对于Car实例的全部操作均通过CarRecordManager来实现,它通吃汽车的内外在全部属性。 


         基于js的灵活性,将Car类的声明迁移到CarFactory类中,将CarFactory类声明迁移到CarRecordManager中: 

    /* CarRecordManager singleton. */
    var CarRecordManager = (function () {
        var carRecordDatabase = {};

        /* CarFactory singleton. */
        var CarFactory = (function () {
            var createdCars = {};

            /* Car class, optimized as a flyweight. */
            var Car = function (make, model, year) {
                this.make = make;
                this.model = model;
                this.year = year;
            };
            Car.prototype = {
                getMake: function () {
                    return this.make;
                },
                getModel: function () {
                    return this.model;
                },
                getYear: function () {
                    return this.year;
                }
            };

            return {
                createCar: function (make, model, year) {
                    if (createdCars[make + '-' + model + '-' + year]) {
                        return createdCars[make + '-' + model + '-' + year];
                    } else {
                        var car = new Car(make, model, year);
                        createdCars[make + '-' + model + '-' + year] = car;
                        return car;
                    }
                }
            };
        })();

        return {
            addCarRecord: function (make, model, year, owner, tag, renewDate) {
                var car = CarFactory.createCar(make, model, year);
                carRecordDatabase[tag] = {
                    owner: owner,
                    renewDate: renewDate,
                    car: car
                };
                return carRecordDatabase[tag];
            },
            getCar: function (tag) {
                return carRecordDatabase[tag];
            },
            transferOwnership: function (tag, newOwner, newTag, newRenewDate) {
                var record = this.getCar(tag);
                record.owner = newOwner;
                record.tag = newTag;
                record.renewDate = newRenewDate;
            },
            renewRegistration: function (tag, newRenewDate) {
                this.getCar(tag).renewDate = newRenewDate;
            },
            isRegistrationCurrent: function (tag) {
                var today = new Date();
                return today.getTime() < Date.parse(this.getCar(tag).renewDate);
            }
        };
    })();


    // test
    (function () {
        var car = CarRecordManager.addCarRecord("test make", "test model", "2011", "Ray", "JB001", "2012-09-29");
        var car2 = CarRecordManager.addCarRecord("test make", "test model", "2011", "Tina", "JB002", "2011-08-27");
        var car1 = CarRecordManager.getCar("JB001");
        console.log(car == car1);               // true
        console.log(car1.car == car2.car);      // true
        console.log(car2.owner);                // tina

        //var cc = new Car("test make", "test model", "2012");  //error
    })();   

    这时候,还是Flyweight,但是封装性更好了。 Car的用户看不到CarFactory,更看不到Car。即便你如倒数第二行那样声明var car = new Car(...),得到的也唯有error。同时,它完全不影响原有的正常使用。

        关于Flyweight,它的内在属性比较好管理,因为实例对象比较少。而外在属性怎么管理? Car的Flyweight实现,是将它们存储在一个内部的类数组对象:carRecordDataBase中。 发散一下,想到没有,一棵树,它利用Composite Pattern也可以完美的存储很多对象实例。 我们用它来实现一下Flyweight。看如下Calendar的例子: 

    /* CalendarItem interface. */
    var CalendarItem = new Interface('CalendarItem', ['display']);

    /* CalendarYear class, a composite. */
    var CalendarYear = function (year, parent) {
        this.year = year;
        this.element = document.createElement('div');
        this.element.style.display = 'none';
        parent.appendChild(this.element);
        function isLeapYear(y) {
            return (y > 0) && !(y % 4) && ((y % 100) || !(y % 400));
        }
        this.months = [];
        this.numDays = [31, isLeapYear(this.year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        for (var i = 0, len = 12; i < len; i++) {
            this.months[i] = new CalendarMonth(i, this.numDays[i], this.element);
        }
    };
    CalendarYear.prototype = {
        display: function () {
            for (var i = 0, len = this.months.length; i < len; i++) {
                this.months[i].display(); 
            }
            this.element.style.display = 'block';
        }
    };

    /* CalendarMonth class, a composite. */
    var CalendarMonth = function (monthNum, numDays, parent) {
        this.monthNum = monthNum;
        this.element = document.createElement('div');
        this.element.style.display = 'none';
        parent.appendChild(this.element);
        this.days = [];
        for (var i = 0, len = numDays; i < len; i++) {
            this.days[i] = new CalendarDay(i, this.element);
        }
    };
    CalendarMonth.prototype = {
        display: function () {
            for (var i = 0, len = this.days.length; i < len; i++) {
                this.days[i].display();
            }
            this.element.style.display = 'block';
        }
    };

    /* CalendarDay class, a leaf. */
    var CalendarDay = function (date, parent) {
        this.date = date;
        this.element = document.createElement('div');
        this.element.style.display = 'none';
        parent.appendChild(this.element);
    };
    CalendarDay.prototype = {
        display: function () {
            this.element.style.display = 'block';
            this.element.innerHTML = this.date;
        }
    };

    CalendarYear、CalendarMonth、CalendarDay均实现了CalendarItem接口,CalendarMonth中引用和组装CalendarDay,CalendarYear引用和组装CalendarMonth。初看起来很不多,但是想一想一年得有365天,我连续加载个10年,页面估计就得挂了。 

        利用Singleton Pattern将CalendarDay一步到位改进如下: 

    /* CalendarDay class, a leaf. */
    var CalendarDay = (function () {
        var innerDay = null;
        var InnerCalendarDay = function () { };
        InnerCalendarDay.prototype = {
            display: function (date, parent) {
                var element = document.createElement('div');
                element.innerHTML = date;
                element.style.display = "block";
                parent.appendChild(element);
            }
        };

        return {
            getInstance: function () {
                if (innerDay == null) {
                    innerDay = new InnerCalendarDay();
                }

                return innerDay;
            }
        };
    })();

    可以看到,我们将它的date和parent作为外在属性,在调用display方法时通过参数传入。这时候CalendarDay内部实例仅有1份。基于这个变化,修改CalendarMonth如下: 

    /* CalendarMonth class, a composite. */
    var CalendarMonth = function (monthNum, numDays, parent) {
        this.monthNum = monthNum;
        this.element = document.createElement('div');
        this.element.style.display = 'none';
        parent.appendChild(this.element);
        this.days = [];
        for (var i = 0, len = numDays; i < len; i++) {
            this.days[i] = CalendarDay.getInstance();
        }
    };
    CalendarMonth.prototype = {
        display: function () {
            for (var i = 0, len = this.days.length; i < len; i++) {
                this.days[i].display(i, this.element);
            }
            this.element.style.display = 'block';
        }
    };

    至此,CalendarDay级别已经优化。 你可以参考这个思路继续。 另外,创建的那些DOM元素其实也可以根据需要来进行共享。 这里暂不进行。

        这里再列举Tooltip的例子。一般性的代码:


    var Tooltip = function(targetElement, text) {
        this.target = targetElement;
        this.text = text;
        this.delayTimeout = null;
        this.delay = 1500;

        this.element = document.createElement('div');
        this.element.style.display = 'none';
        this.element.style.position = 'absolute';
        this.element.className = 'tooltip';
        document.getElementsByTagName('body')[0].appendChild(this.element);
        this.element.innerHTML = this.text;

        var that = this
        addEvent(this.target, 'mouseover', function(e) { that.startDelay(e); });
        addEvent(this.target, 'mouseout', function(e) { that.hide(); });
    };
    Tooltip.prototype = {
        startDelay: function (e) {
            if (this.delayTimeout == null) {
                var that = this;
                var x = e.clientX;
                var y = e.clientY;
                this.delayTimeout = setTimeout(function () {
                    that.show(x, y);
                }, this.delay);
            }
        },
        show: function (x, y) {
            clearTimeout(this.delayTimeout);
            this.delayTimeout = null;
            this.element.style.left = x + 'px';
            this.element.style.top = (y + 20) + 'px';
            this.element.style.display = 'block';
        },
        hide: function () {
            clearTimeout(this.delayTimeout);
            this.delayTimeout = null;
            this.element.style.display = 'none';
        }
    };

    //test
    var linkElement = $('link-id');
    var tt = new Tooltip(linkElement, 'Lorem ipsum...');

     同样可能会导致N对类似对象被大量创建。改进如下: 

    /* TooltipManager singleton, a flyweight factory and manager. */
    var TooltipManager = (function () {
        var storedInstance = null;

        /* Tooltip class, as a flyweight. */
        var Tooltip = function () {
            this.delayTimeout = null;
            this.delay = 1500;
            this.element = document.createElement('div');
            this.element.style.display = 'none';
            this.element.style.position = 'absolute';
            this.element.className = 'tooltip';
            document.getElementsByTagName('body')[0].appendChild(this.element);
        };
        Tooltip.prototype = {
            startDelay: function (e, text) {
                if (this.delayTimeout == null) {
                    var that = this;
                    var x = e.clientX;
                    var y = e.clientY;
                    this.delayTimeout = setTimeout(function () {
                        that.show(x, y, text);
                    }, this.delay);
                }
            },
            show: function (x, y, text) {
                clearTimeout(this.delayTimeout);
                this.delayTimeout = null;
                this.element.innerHTML = text;
                this.element.style.left = x + 'px';
                this.element.style.top = (y + 20) + 'px';
                this.element.style.display = 'block';
            },
            hide: function () {
                clearTimeout(this.delayTimeout);
                this.delayTimeout = null;
                this.element.style.display = 'none';
            }
        };

        return {
            addTooltip: function (targetElement, text) {
                // Get the tooltip object.
                var tt = this.getTooltip();
                // Attach the events.
                addEvent(targetElement, 'mouseover', function (e) { tt.startDelay(e, text); });
                addEvent(targetElement, 'mouseout', function (e) { tt.hide(); });
            },
            getTooltip: function () {
                if (storedInstance == null) {
                    storedInstance = new Tooltip();
                }
                return storedInstance;
            }
        };
    })();

    /* Tooltip usage. */
    TooltipManager.addTooltip($('link-id'), 'Lorem ipsum...');

    全部源码download

          

  • 相关阅读:
    spring学习(一)--spring简介
    The Evolved Packet Core
    Git版本控制入门学习
    分家后中国移动运营商2G/3G频率分配
    English name
    中国互联网五大势力竞争力报告
    ping
    mysql架构
    MySQL存储引擎
    79款 C/C++开发工具开源软件
  • 原文地址:https://www.cnblogs.com/Langzi127/p/2708005.html
Copyright © 2011-2022 走看看