跳转到内容

享元模式

首发于:2022-03-25

基本概念

享元模式 (Flyweight Pattern)运用共享技术来有效地支持大量细粒度对象的复用,以减少创建的对象的数量。

享元模式的主要思想是共享细粒度对象,也就是说如果系统中存在多个相同的对象,那么只需共享一份就可以了,不必每个都去实例化每一个对象,这样来精简内存资源,提升性能和效率。

Fly 意为苍蝇,Flyweight 指轻蝇量级,指代对象粒度很小。

享元模式把一个对象的状态分为内部状态和外部状态,内部状态是不变的,外部状态是变化的,共享的就是内部状态这块儿不变的部分。内部和外部的参照物是被共享的对象。

现实生活中的例子

图书馆:

我们去图书馆借书,这个书本质上就是所有人共享的,如果我借走了一本书《设计模式精粹》,并且借完了库存,那么再来人想借这本书就没得借了。图书馆要解决这个问题有两个选择,一是再去订购一批《设计模式精粹》,二是让之后的借书人等我归还。不过一般来说图书馆都会有第二种方式,因为如果每个人来借书,一旦书不够图书馆就要去买新的,那显然这是图书馆无法承受的,图书馆的藏书毕竟也是有限的。

驾校:

我们去驾校学车或者考试的时候通常都是和别的学员共享一辆车,如果每个人都配一辆车并且车还分成手动挡和自动挡,想必驾校一定是堵得水泄不通吧,而且车也不够用,所以学员们轮换着开车这可以大大降低成本。

应用场景

其实像数据库的连接池、程序的线程池等都是享元模式的典型的应用,通常适合使用享元模式的场景具备下列特定:

  1. 如果一个程序中大量使用了相同或相似对象,那么可以考虑引入享元模式;
  2. 如果使用了大量相同或相似对象,并造成了比较大的内存开销;
  3. 对象的大多数状态可以被转变为外部状态;
  4. 剥离出对象的外部状态后,可以使用相对较少的共享对象取代大量对象;

在一些程序中,如果引入享元模式对系统的性能和内存的占用影响不大时,比如目标对象不多,或者场景比较简单,则不需要引入,以免适得其反。

优缺点

优点:

  1. 由于减少了系统中的对象数量,提高了程序运行效率和性能,精简了内存占用,加快运行速度;
  2. 外部状态相对独立,不会影响到内部状态,所以享元对象能够在不同的环境被共享;

缺点:

  1. 引入了共享对象,使对象结构变得复杂;
  2. 共享对象的创建、销毁等需要维护,带来额外的复杂度(如果需要把共享对象维护起来的话);

实现

下面实现一个图书馆借书的例子,借到书 2 秒之后归还,借不到的人 2.1 秒之后再借。

要借的书就是共享的对象,学生的姓名和班级这些都是外部状态,而书名是内部状态。

js
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。

京ICP备18043750号