前言
这是本人的设计模式学习笔记,把自己学习过程中的一些总结和认识记录下来,与诸君共勉。本日为大家带来享元模式。
基本概念
享元模式(Flyweight):运用共享的技术有效地支持大量细粒度的对象。
抽象享元角色(Flyweight):此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口或抽象类。那些需要外部状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。
具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内部状态的话,必须负责为内部状态提供存储空间。享元对象的内部状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。
复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。这个角色一般很少使用。
享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。
客户端(Client)角色:本角色还需要自行存储所有享元对象的外部状态。
内部状态与外部状态:在享元对象内部并且不会随着环境改变而改变的共享部分,可以称之为享元对象的内部状态,反之随着环境改变而改变的,不可共享的状态称之为外部状态。
其中重点就是内部数据和外部数据的区分,这是享元模式存在的前提,所谓内部数据是指类的内部方法所需要的信息,没有这种数据就不能正常运转,而外在数据则是可以从类身上剥离而存储在外部的信息。我们将内在状态相同的所有对象替换成同一个共享对象以减少开销。
下面给出享元模式的结构示意图:
实现
- 将所有外在数据从目标类剥离,也就是尽可能多地删除类的属性,但删除的应该是那些因实例而异的属性。外在数据不再保存在类内部,而由管理器提供给类方法,数据来源发生变化。
- 创建一个用来控制该类的实例化的工厂,或者使用一个对象池。
- 创建一个用来保存外在数据的管理器,将数据提供给对象实例。
下面我们给出一个实现,实现一个汽车登记管理的系统,先给出木有使用享元模式时的设计:
使用享元模式之后,我们认为同一品牌同一型号同一天出厂的车是一样的,把这样一个组合作为键值,一辆真实的车还需要用户的信息,组合起来就是一辆实际的汽车对象。咋一听,我们会觉得很像桥接模式,但记住,桥接模式会创造出新的对象种类,而享元模式只是把外部数据放在另一处管理,它的逻辑结构木有改变。
新的车类:
js
var Car=function(make,model,year){ this.make=make; this.model=model; this.year=year; }; Car.prototype={ get**:function(){ return this.**; }, };
接下来我们构造一个工厂来创建Car实例,并查询是否已有这样的对象:
js
var CarFactory=(function(){ var createCars={}; return { createCar:function(make,model,year){ //check & create or retrun ... } }; })();
最后为大家带来管理器:
管理器管理和存储外部数据。
使用场景
在以下情况中应该使用享元模式:
- 一个应用程序使用了大量的对象。
- 完全由于使用大量的对象,造成很大的存储开销。
- 对象的大多数状态都可变为外部状态。
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
- 应用程序不依赖对象标识。
下面分析享元模式的利弊:
- 降低资源负荷,且实现它并不需要修改大量代码
- 提高了系统的复杂程度,增加了维护的困难,可能出错的由原来的一个对象变成三个:管理器,工厂,享元。
- 数据的结构没有以前清晰,本来逻辑上同一处的数据现在分两处存储
再举一个比较好的例子,就是工具栏,为每一个需要的dom元素创建一个实例显然不划算,那么我们就可以考虑将一些数据作为外部数据,以采用享元模式,对话框的管理也同此理。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。