学习《JavaScript设计模式与开发实践》- 享元模式

2022-08-10 20:45:17 浏览数 (2)

一个例子

假设你有一个服装工厂,生产了50件不同的男装和50件不同的女装,现在需要找一些模特来拍照,正常情况下,我们会招50个男模特和50个女模特,然后每人穿上1件来拍照,所以代码可能会写成这样。

代码语言:javascript复制
var Model = function( sex, underwear){ 
 this.sex = sex; 
 this.underwear= underwear; 
}; 
Model.prototype.takePhoto = function(){ 
 console.log( 'sex= '   this.sex   ' underwear='   this.underwear); 
}; 
for ( var i = 1; i <= 50; i   ){ 
 var maleModel = new Model( 'male', 'underwear'   i ); 
 maleModel.takePhoto(); 
}; 
for ( var j = 1; j <= 50; j   ){ 
     var femaleModel= new Model( 'female', 'underwear'   j ); 
     femaleModel.takePhoto(); 
}; 

写到这里,我们的性能其实已经有点差了,假如有2000服装,我们的程序可能早就崩溃了,所以我们需要进行优化,不难发现,我们对模特其实并没有要求,我们只想得到最终的照片,所以我们其实只需要找两位模特来拍照就好。

代码语言:javascript复制
//这里不再直接给模特分配服装
var Model = function( sex ){ 
 this.sex = sex; 
}; 
Model.prototype.takePhoto = function(){ 
 console.log( 'sex= '   this.sex   ' underwear='   this.underwear); 
}; 
//分别创建一个男模特对象和一个女模特对象:
var maleModel = new Model( 'male' ), 
 femaleModel = new Model( 'female' ); 
//给男模特依次穿上所有的男装,并进行拍照:
for ( var i = 1; i <= 50; i   ){ 
 maleModel.underwear = 'underwear'   i; 
 maleModel.takePhoto(); 
}; 
//同样,给女模特依次穿上所有的女装,并进行拍照:
for ( var j = 1; j <= 50; j   ){ 
 femaleModel.underwear = 'underwear'   j; 
 femaleModel.takePhoto(); 
}; 

使用场景

享元(flyweight)模式是一种用于性能优化的模式,。享元模式的核心是运用共享技术来有效支持大量细粒度的对象

如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。在 JavaScript 中,浏览器特别是移动端的浏览器分配的内存并不算多,如何节省内存就成了一件非常有意义的事情.

区分内部状态和外部状态

使用享元模式的关键就是区分它的内部状态和外部状态,内部状态就是在对象里通过内部方法管理,而外部信息可以在通过外部删除或者保存。

在上面的例子中,性别是内部状态,内衣是外部状态,通过区分这两种状态,大大减少了系 统中的对象数量。通常来讲,内部状态有多少种组合,系统中便最多存在多少个对象。

如何区分内部状态和外部状态,给出了几种情况

  • 内部状态存储于对象内部。
  • 内部状态可以被一些对象共享
  • 内部状态独立于具体的场景,通常不会改变。
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享

下面我们来实现一下具体的逻辑

借书系统

现在有一堆书籍,每一种书都有多本,我们需要设计一个系统来管理。需要包含以下功能:

  • 借阅图书
  • 返还图书
  • 延长图书借阅时间

抽象它的内部状态和外部状态

虽然每一本书都是独立的,但是它们有自己的类别,我们可以认为,相同类别的书,共享一种内部状态。

而借阅操作则会随着不同用户,不同时间而产生变化,所以我们可以认为它是外部状态。

实现

首先我们实现图书类,这里只需要列出它的信息

代码语言:javascript复制
        function Book(title, author) {
            this.title = title;
            this.author = author;
            this.bookCount = 0;
            //...
        }

然后我们创建一个管理图书的工厂,所有的图书都需要从这里创建,每一个种图书不论多少本,只创建一个对象.这里添加了一个getBookCount的方法,方便获取每一种书的数量。

代码语言:javascript复制
        var bookFactory = (function () {
            var existingBooks = {};
            return {
                createBook: function (title, author) {
                    /*查找之前是否创建*/
                    console.log(existingBooks);
                    var existingBook = existingBooks[title];
                    if (existingBook) {
                        //如果存在则数量加一
                        existingBook.bookCount  = 1;
                        return existingBook;
                    } else {
                        /* 如果没有,就创建一个,然后保存*/
                        var book = new Book(title, author);
                        book.bookCount = 1;
                        existingBooks[title] = book;
                        return book;
                    }
                },
                getBookCount:function(title){
                    return existingBooks[title].bookCount;
                }
            }
        })();

最后就是管理外部状态了,我们还是需要构造一个管理对象,将借阅和退还定义成它的方法,然后将所有外部状态汇集到一起。

代码语言:javascript复制
        //管理外部状态
        var bookManager = (function () {
            var bookRecordDatabase = {};
            return {
                addBookRecord: function (id, title,dateline) {
                    if(bookRecordDatabase[id]){
                        console.log("已经存在借阅了哦");
                        return;
                    }
                    if (this.checkIsBookRest(title)) {
                        var record = {
                            id, title, dateline
                        };
                        bookRecordDatabase[id] = record;
                        console.log("你借阅了",title);
                    } else {
                        console.log("该书已经借阅完了哦");
                    }
                },
                extendDateLine: function (id) {
                    bookRecordDatabase[id].dateline  = 5;
                    console.log("你延长了该书的日期",bookRecordDatabase[id].title);
                },
                //检查该种书被借阅完没有
                checkIsBookRest: function (title) {
                    var count = 0;
                    for (a in bookRecordDatabase) {
                        record = bookRecordDatabase[a]
                        if (record.title == title) {
                            count  ;
                        }
                    }
                    return count < bookFactory.getBookCount(title);
                },
                returnBook: function (id) {
                    delete bookRecordDatabase[id];
                }
            }
        })();

小小的测试一下

代码语言:javascript复制
    var books = [
            {
                title: "JS从入门到精通",
                author: "X",
            },
            {
                title: "JS从入门到精通",
                author: "X",
            },
            {
                title: "JS从入门到精通",
                author: "X",
            },
            {
                title: "JS权威指南",
                author: "Y",
            },
            {
                title: "JS权威指南",
                author: "Y",
            },
        ];
        for(var i = 0 ; i < books.length ; i  ){
            bookFactory.createBook(books[i].title,books[i].author);
        }

        bookManager.addBookRecord(1,"JS权威指南",5);
        bookManager.addBookRecord(2,"JS从入门到精通",5);
        bookManager.addBookRecord(3,"JS权威指南",5);
        bookManager.addBookRecord(4,"JS权威指南",5);
        bookManager.extendDateLine(2);
        bookManager.returnBook(3);
        bookManager.addBookRecord(5,"JS权威指南",4);

结果

享元模式的适用性

使用了享元模式之后,我们需要分别多维护一个 factory 对象和一个 manager 对 象,在大部分不必要使用享元模式的环境下,这些开销是可以避免的。

享元模式带来的好处很大程度上取决于如何使用以及何时使用,一般来说,以下情况发生时 便可以使用享元模式。

  • 一个程序中使用了大量的相似对象。
  • 由于使用了大量对象,造成很大的内存开销。
  • 对象的大多数状态都可以变为外部状态。
  • 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象

最后

本文简单的介绍了享元模式,享元模式是为解决性能问题而生的模式,这跟大部分模式的诞生原因都不一样。在一个存在大量相似对象的系统中,享元模式可以很好地解决大量对象带来的性能问题

0 人点赞