享元模式
首发于:2022-03-25
基本概念
享元模式 (Flyweight Pattern)运用共享技术来有效地支持大量细粒度对象的复用,以减少创建的对象的数量。
享元模式的主要思想是共享细粒度对象,也就是说如果系统中存在多个相同的对象,那么只需共享一份就可以了,不必每个都去实例化每一个对象,这样来精简内存资源,提升性能和效率。
Fly 意为苍蝇,Flyweight 指轻蝇量级,指代对象粒度很小。
享元模式把一个对象的状态分为内部状态和外部状态,内部状态是不变的,外部状态是变化的,共享的就是内部状态这块儿不变的部分。内部和外部的参照物是被共享的对象。
现实生活中的例子
图书馆:
我们去图书馆借书,这个书本质上就是所有人共享的,如果我借走了一本书《设计模式精粹》,并且借完了库存,那么再来人想借这本书就没得借了。图书馆要解决这个问题有两个选择,一是再去订购一批《设计模式精粹》,二是让之后的借书人等我归还。不过一般来说图书馆都会有第二种方式,因为如果每个人来借书,一旦书不够图书馆就要去买新的,那显然这是图书馆无法承受的,图书馆的藏书毕竟也是有限的。
驾校:
我们去驾校学车或者考试的时候通常都是和别的学员共享一辆车,如果每个人都配一辆车并且车还分成手动挡和自动挡,想必驾校一定是堵得水泄不通吧,而且车也不够用,所以学员们轮换着开车这可以大大降低成本。
应用场景
其实像数据库的连接池、程序的线程池等都是享元模式的典型的应用,通常适合使用享元模式的场景具备下列特定:
- 如果一个程序中大量使用了相同或相似对象,那么可以考虑引入享元模式;
- 如果使用了大量相同或相似对象,并造成了比较大的内存开销;
- 对象的大多数状态可以被转变为外部状态;
- 剥离出对象的外部状态后,可以使用相对较少的共享对象取代大量对象;
在一些程序中,如果引入享元模式对系统的性能和内存的占用影响不大时,比如目标对象不多,或者场景比较简单,则不需要引入,以免适得其反。
优缺点
优点:
- 由于减少了系统中的对象数量,提高了程序运行效率和性能,精简了内存占用,加快运行速度;
- 外部状态相对独立,不会影响到内部状态,所以享元对象能够在不同的环境被共享;
缺点:
- 引入了共享对象,使对象结构变得复杂;
- 共享对象的创建、销毁等需要维护,带来额外的复杂度(如果需要把共享对象维护起来的话);
实现
下面实现一个图书馆借书的例子,借到书 2 秒之后归还,借不到的人 2.1 秒之后再借。
要借的书就是共享的对象,学生的姓名和班级这些都是外部状态,而书名是内部状态。
class Library {
constructor() {
this.books = [
{ name: '设计模式精粹', total: 2, inventory: [] },
{ name: '高等数学', total: 10, inventory: [] },
];
this.initBook();
}
initBook() {
for (let i = 0; i < this.books.length; i++) {
for (let j = 0; j < this.books[i].total; j++) {
this.books[i].inventory.push(new Book(this.books[i].name, this));
}
}
}
getBook(bookName) {
for (let i = 0; i < this.books.length; i++) {
if (this.books[i].name === bookName) {
if (this.books[i].inventory.length > 0) {
return this.books[i].inventory.splice(0, 1)[0];
}
return `《${this.books[i].name}》没库存了等等吧。`;
}
}
return '图书馆没这书!';
}
returnBook(book, name) {
for (let i = 0; i < this.books.length; i++) {
if (this.books[i].name === book.name) {
this.books[i].inventory.push(book);
return console.log(`${name}已归还《${this.books[i].name}》,当前库存${this.books[i].inventory.length}。`);
}
}
}
}
class Book {
constructor(bookName, library) {
this.name = bookName;
this.libraryFrom = library;
}
}
class Student {
constructor(name, CnG) {
this.name = name;
this.CnG = CnG;
this.book = null;
}
borrowBook(library, bookName) {
const res = library.getBook(bookName);
if (res instanceof Book) {
this.book = res;
console.log(`${this.name}借到《${res.name}》`);
setTimeout(() => {
this.returnBook();
}, 2000);
} else {
console.log(res);
setTimeout(() => {
this.borrowBook(library, bookName);
}, 2100);
}
}
returnBook() {
this.book.libraryFrom.returnBook(this.book, this.name);
this.book = null;
}
}
const lib = new Library();
const std1 = new Student('小明', '三年二班');
const std2 = new Student('张三', '三年二班');
const std3 = new Student('李四', '三年二班');
const std4 = new Student('王五', '三年二班');
std1.borrowBook(lib, '设计模式精粹');
std2.borrowBook(lib, '设计模式精粹');
std3.borrowBook(lib, '高等数学');
std4.borrowBook(lib, '设计模式精粹');
执行结果如下:
小明借到《设计模式精粹》
张三借到《设计模式精粹》
李四借到《高等数学》
《设计模式精粹》没库存了等等吧。
小明已归还《设计模式精粹》,当前库存1。
张三已归还《设计模式精粹》,当前库存2。
李四已归还《高等数学》,当前库存10。
王五借到《设计模式精粹》
王五已归还《设计模式精粹》,当前库存2。