js 设计模式 第十三章 Flyweight Pattern

why?

当一大堆类似的复杂对象被创建,导致内存被大量占用,浏览性能大大降低时,我们应该有所行动,需要优化这些对象。Flyweight Pattern 就是用来做大量对象优化工作的。


how ?

Flyweight Pattern 将大量的这些复杂对象,转换为少许的一些共享对象,这将大大减少内存的占有


what?

Flyweight Pattern 目标是减少你需要创造的对象数目。这是通过将对象的内部状态分拆为两个部分:intrinsic data 和 extrinsic data。intrinsic data 是对象类的方法需要用到的数据,没有这些数据,类将不能完成功能。extrinsic data 是可以从类中移除的,并保存在外面。

这样,我们可以将拥有相同intrinsic data 的对象用一个共享对象代替,最后,一大推的类似对象,将变为少许几个拥有不同intrinsic data 的对象

需要factory:没有简单的通过构造函数创造对象,通过factory 创造这些共享对象,因为这样,我们可以跟踪记录有哪些对象被创建,并且只创造有不同intrinsic data 的对象。

需要manager:manager 对象用来存储对象的extrinsic data。每当涉及到一个对象的方法被调用时,manager 需要传递extrinsic data 过去作为参数


案例分析:

1 car registration

现在需要登记城市里面的所有车的情况,你需要存储的内容有车的生产厂家、型号、出产日期,还有车的拥有关系,如车主姓名、车牌号、登记日期)。

刚开始,我们用一个对象表示一辆车:

/* 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);
		}
};

如果城市有几百万量车,你也将创造几百万个对象,这将导致严重的内存问题。so,flyweight pattern 出现了,通过他来优化这个结构

首先分离intrinsic data 和 extrinsic data

分离这些数据,有点随意。主要依据原则是:尽可能多的把数据放在extrinsic data中,同时还保留对象的完整性。

这个例子中,我们将车的生产厂家、型号、出产日期作为intrinsic data ,车主姓名、车牌号、登记日期作为extrinsic data。这意味着,拥有相同生产厂家、型号、出产日期的车,将共享一个对象。虽然生产厂家、型号、出产日期的不同组合,还是可以参数很多对象,但是比起为每一个车产生一个对象,少得很多。

/* 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;
    }
};

同之前的car 类相比,这个类,把所有的extrinsic data 移除了。所有与注册相关的方法都移到manager object 中去了(虽然可以依然留下这些方法,并通过参数传递的方式,将信息传递进去)。


2 通过factory 来实例化car 对象

不能通过简单的factory 函数来生成对象,因为函数内部无法对历史生成对象进行保存管理,so,需要用一个singleton来达到保存的目的

/* CarFactory singleton. */
var CarFactory = (function() {
		var createdCars = {};
		return {
				createCar: function(make, model, year) {
				// Check to see if this particular combination has been created before.
				    if(createdCars[make + '-' + model + '-' + year]) {
				       return createdCars[make + '-' + model + '-' + year];
				    }
				    // Otherwise create a new instance and save it.
				    else {
				        var car = new Car(make, model, year);
				        createdCars[make + '-' + model + '-' + year] = car;
				        return car;
				    }
				}
		};
})();

createdCars 中保存了已经生成的对象,在factory 对象中,如果以前没有生成对应的对象,则,生成;如果以前生成过,则用以前的就行。


3 完成优化,还需要最后一个对象--manager 对象

需要manager 对象保存extrinsic data 。这样原来一个car 对象的数据分成了两部分,拥有相同instrinsic data 的共享对象 以及 extrinsic data。这两部分在manager 对象中结合。

/* CarRecordManager singleton. */
var CarRecordManager = (function() {
		var carRecordDatabase = {};
		return {
				// Add a new car record into the city's system.
				addCarRecord: function(make, model, year, owner, tag, renewDate) {
				    var car = CarFactory.createCar(make, model, year);
				    carRecordDatabase[tag] = {
				        owner: owner,
				        renewDate: renewDate,
				        car: car
				    };
				},
				// Methods previously contained in the Car class.
				transferOwnership: function(tag, newOwner, newTag, newRenewDate) {
				    var record = carRecordDatabase[tag];
				    record.owner = newOwner;
				    record.tag = newTag;
				    record.renewDate = newRenewDate;
				},
				renewRegistration: function(tag, newRenewDate) {
				    carRecordDatabase[tag].renewDate = newRenewDate;
				},
				isRegistrationCurrent: function(tag) {
				    var today = new Date();
				    return today.getTime() < Date.parse(carRecordDatabase[tag].renewDate);
				}
		};
})();

最后,所有的车辆数据都保存在manager 对象的私有变量:carRecordDatabase 中。所有与extrinsic data 有关的操作都封装在manager 中。

从这个例子中,我们可以看到,为了提高性能,程序增加了复杂性。原来只有一个class,现在有一个class,还有两个singleton 对象;原来的数据放在一个地方,现在数据放在了两个地方。但是对于提高性能,这个还是值得的

如果管理extrinsic data,这里介绍了一种,即,在manager 中,统一管理共享对象和extrinsic data。另一个管理方式是通过composite pattern 。利用树状继承关系来保存extrinsic data。叶子节点是在继承中共享的对象


案例2:tooltip object

鼠标移动到某个按钮上时,弹出浮动框,提示按钮的作用。

/* Tooltip class, un-optimized. */
var Tooltip = function(targetElement, text) {
    this.target = targetElement;
    this.text = text;
    this.delayTimeout = null;
    this.delay = 1500; // in milliseconds.
    // Create the HTML.
    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;
    // Attach the events.
    var that = this; // Correcting the scope.
    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';
    }
};

如果页面上有100个按钮,我们就要生成100个浮动框。太多的dom ,将严重影响性能。其实浮动框的样式都一样,仅仅是内容不一样而已

利用flyweight pattern,移除extrinsic data ,浮动框类将如下:

/* Tooltip class, as a flyweight. */
var Tooltip = function() {
    this.delayTimeout = null;
    this.delay = 1500; // in milliseconds.
    // Create the HTML.
    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';
    }
};

这个例子中,与extinsic data 有关的方法,并没有移动到manager中,这些方法通过参数得到extrinsic data

Tooltip 的声明将放在TooltipManager 中,这样,他将不会再别的地方实例化

/* TooltipManager singleton, a flyweight factory and manager. */
var TooltipManager = (function() {
	var storedInstance = null;
	/* Tooltip class, as a flyweight. */
	var Tooltip = function() {
	    ...
    };
    Tooltip.prototype = {
        ...
    };
    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;
        }
    };
})();

getTooltip 即产生共享对象的factory 方法。

通过flyweight pattern,我们用一个浮动框就解决问题了,大大的提高的性能。


你可能感兴趣的:(js 设计模式 第十三章 Flyweight Pattern)