📒 享元设计模式(Flyweight Pattern)
🏷 定义:
一种结构型设计模式,它旨在减少内存使用和提高性能。该模式通过共享对象来最小化内存占用,特别是当存在大量相似对象时。
🍳 特点:将对象的属性分成内部状态和外部状态
- 内部状态可以被多个对象共享
- 外部状态则取决于每个对象的特定上下文
🍍 原理:
- 享元模式的核心思想是将共享对象存储在一个称为享元池的数据结构中。
- 在需要创建新对象时,首先检查是否已经存在具有相同内部状态的对象。
- 如果存在,则重用 该对象,否则创建新对象并将其添加到享元池中。
🍔 参与者:
- 享元工厂:包括享元池、内部类;并提供创建对象的方法和查看/操作享元池状态的接口;
- 享元池:缓存有内部类创建的实例对象;
- 内部类:根据构造参数创建实例化对象。
🍕 作用:
通过使用享元模式,可以:
- 显著减少创建对象的数量
- 节省内存
- 实现对对象的高效访问和操作(因为对象的内部状态被共享)
🍃 缺点:
- 对象的外部状态不同,在使用共享对象 时需要注意线程安全性 和对象的一致性
- 共享对象的创建和管理可能会增加代码的复杂性
🥞 举例:
假设有一个名为Circle
的圆形类,每个圆形对象都有半径和颜色属性 。 在没有使用享元模式的情况下,会创建许多相同半径和颜色的圆形对象,这将导致内存浪费。 使用享元模式,将圆形对象的*内部状态(半径)共享,而外部状态(颜色)*可以根据需要进行定制。
javascript
// 创建一个享元工厂函数来管理和提供共享的圆形对象
const CircleFactory = (function () {
// 享元池
const circleCache = {};
// 内部类
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.draw = function (color) {
console.log(
`Drawing a circle with radius ${this.radius} and color ${color}.`
);
};
return {
// 当客户端获取对象的时候首先在享元池中寻找是否已经存在;
// 如果存在就返回缓存的,否则就new一个添加到享元池中并返回
getCircle: function (radius) {
if (!circleCache[radius]) {
circleCache[radius] = new Circle(radius);
}
return circleCache[radius];
},
// 查看享元池中对象数量
getTotalCircles: function () {
return Object.keys(circleCache).length;
},
};
})();
// 实例化享元工厂
const factory = new CircleFactory();
// 使用享元工厂获取对象
const redCircle = factory.getCircle(5);
redCircle.draw("red");
const blueCircle = factory.getCircle(5);
blueCircle.draw("blue");
console.log(`Total circles created: ${factory.getTotalCircles()}`);
/* 输出的结果为:
>>>
Drawing a circle with radius 5 and color red.
Drawing a circle with radius 5 and color blue.
Total circles created: 1
*/
🍚 原生使用:
在 JavaScript 的原生方法中,有几个地方使用了享元设计模式:
-
String.prototype.charAt()
:这个方法返回指定索引位置的字符。由于字符串是不可变的,所以无需为每个索引位置创建新的 String 对象。相反,这个方法可以共享同一个 String 对象。 -
Array.prototype.map()
:这个方法创建一个新数组,其中包含对原始数组中的每个元素应用回调函数的结果。在 map()方法中,如果原始数组中的元素没有被修改或引用,那么可以共享同一个函数对象,而无需为每个元素创建新的函数对象。 -
Object.freeze()
:这个方法用于冻结一个对象,使其成为不可修改的。在内部实现中,会使用到享元模式来共享内部状态并确保对象的不可变性。 -
Math.sqrt()
:这个方法返回一个非负数值的平方根。由于平方根的计算是确定的,因此可以将结果进行缓存以提高性能。这就是使用享元模式的一种方式。
在 浏览器 中,有几个地方使用了享元设计模式:
-
DOM(Document Object Model)
:DOM 是用于表示和操作 HTML 文档的 API。在 DOM 中,元素节点、属性节点、文本节点等都可以被视为享元对象。当创建多个相同类型的节点时,可以共享相同的原型和属性,从而减少内存占用。 -
Event Listener
:浏览器中的事件监听器也使用了享元模式。当多个元素绑定相同类型的事件监听器时,可以共享同一个事件处理函数,避免重复创建。 -
CSS 样式
:在使用 CSS 样式时,如果多个元素具有相同的样式规则,浏览器可以使用享元模式来共享样式对象,以减少内存占用。 -
字体和图标
:浏览器中常见的字体和图标库也可以使用享元模式。如果多个元素需要使用相同的字体或图标,可以共享相同的资源文件,以提高性能和节省带宽。 -
缓存管理
:浏览器通过缓存机制来提高页面加载速度。当浏览器发现某个资源已经在缓存中存在时,可以直接从缓存中获取,而无需重新下载。这种缓存机制也可以看作是一种享元模式,通过共享资源来减少网络请求。
🥗 业务场景使用:
- 缓存管理 浏览器中的缓存机制可以使用享元模式来提高性能。当需要加载或显示大量相同资源(如图片、字体文件等)时,可以通过共享已经加载的资源,避免重复请求和下载。
- 列表渲染 当需要渲染大量相似的列表项时,可以使用享元模式来减少 DOM 节点的创建和操作。将列表项的公共部分抽取出来作为享元对象,在渲染每个列表项时只需修改其外部状态,而无需重新创建整个 DOM 节点。
- 表单处理 在表单处理中,如果有多个输入框具有相同的验证规则或事件处理逻辑,可以使用享元模式来共享验证器或处理函数,以减少代码冗余和内存消耗。
- 数据持久化 当需要在客户端保存大量数据时,可以使用享元模式来共享相同的数据对象。通过使用享元模式,可以减少重复数据的存储和内存占用。
- 资源加载和管理 在前端开发中,经常需要加载和管理各种资源,如图片、音频、视频等。可以使用享元模式来共享已经加载的资源,避免重复请求和下载。
🍭 与其它设计模式之间的关系:
和原型设计模式的不同:
- 目的和使用场景 享元模式旨在通过共享对象来减少内存占用和提高性能,特别适用于存在大量相似对象的情况。而原型模式是为了简化对象的创建过程,通过复制现有对象的原型来创建新对象,适用于需要创建多个相似对象的场景。
- 对象创建方式 享元模式使用一个工厂或管理器来管理和提供共享对象的实例。每次请求对象时,会先检查是否已经存在具有相同内部状态的对象,如果存在则重用,否则创建新对象。原型模式则是通过克隆现有对象的原型来创建新对象,无需显式调用工厂或构造函数。
- 对象的可变性 享元模式中的共享对象通常是不可变的,即其内部状态在创建后不能修改。而原型模式中的对象是可变的,可以根据需要修改其属性和状态。
- 关注点 享元模式关注的是对象的内部状态和外部状态的分离,并通过共享内部状态来节省内存。原型模式关注的是对象的复制和创建过程,通过复制原型对象来创建新对象。
和单例设计模式的关系
- 在某些情况下,内部状态和外部状态可能没有明显的区别,或者外部状态对于对象的行为和属性没有重要影响;
- 此时,使用单例模式可以确保只有一个对象实例存在,无需考虑状态的共享和复用;
- 而享元模式更适合用于共享和复用相同的内部状态,同时根据需要定制外部状态。