メインコンテンツへ移動

フライウェイトパターン_JavaScript デザインパターン 12

無料2015-08-14#JS#Design_Pattern#JavaScript享元模式#JavaScript蝇量模式#JavaScript Flyweight#JavaScript代码复用

フライウェイト(flyweight)パターンはメモリ消費を削減するためのものです。一連の類似オブジェクトから共有可能な属性を抽出し、類似オブジェクトの数を削減します。本稿では JavaScript で実装されたフライウェイトパターンを詳しく紹介します

一. フライウェイトパターンの構造

0. 内部状態と外部状態

フライウェイトオブジェクト内部にあり、環境の変化に伴って変化しない共有部分を、フライウェイトオブジェクトの内部状態と呼ぶことができます。逆に、環境の変化に伴って変化する、共有可能でない状態を外部状態と呼びます。

簡単に言えば、内部状態はオブジェクト自体の属性であり、外部状態はこれらのオブジェクトを管理するために必要な追加の属性です。同じオブジェクトの内部状態は同じですが、外部状態は異なる可能性があります(例えば、同じ 2 冊の本が 2 人に貸し出された場合)

1. フライウェイト

フライウェイトは、類似オブジェクト(本の例では全く同じ本を指し、題材が類似した本ではありません)間で共有可能な属性の集合です。例えば、本の名前、著者、ISBN などです。全く同じ本であればこれらの情報はすべて同じであり、複数の類似オブジェクトで同じ属性を複数保存する必要はありません。フライウェイトはこれらの属性を分離して共有できるようにします

共有可能な属性がより複雑な場合、抽象フライウェイトを追加し、それに対応する具象フライウェイトを持たせることもできます。複合フライウェイトを持つこともできます

2. フライウェイトファクトリ

フライウェイトファクトリはフライウェイトの作成と管理を担当し、共有ロジックを実装します(作成時に存在するかを判断し、既に存在する場合は既存のオブジェクトを返し、そうでない場合は新規作成します)

3. クライアント(Client)

Client はフライウェイトファクトリを呼び出し、類似オブジェクトを管理するために必要な追加の属性(例えば本の id、貸出/返却日、在館状況など)を保存して管理します

二. フライウェイトパターンの実例

// 図書管理

// 本の属性
// id
// title
// author
// genre
// page count
// publisher id
// isbn

// 管理に必要な追加属性
// checkout date
// checkout member
// due return date
// availability

// フライウェイト(内部状態を保存)
function Book(title, author, genre, pageCount, publisherId, isbn) {
    this.title = title;
    this.author = author;
    this.genre = genre;
    this.pageCount = pageCount;
    this.publisherId = publisherId;
    this.isbn = isbn;
}

// フライウェイトファクトリ(フライウェイトを作成/管理)
var BookFactory = (function() {
    var existingBooks = {};
    var existingBook = null;

    return {
        createBook: function(title, author, genre, pageCount, publisherId, isbn) {
            // 書籍が既に作成されている場合、見つけて返す
            // !!強制的に bool 型を返す
            existingBook = existingBooks[isbn];
            if (!!existingBook) {
                return existingBook;
            }
            else {
                // 存在しない場合、その書籍の新しいインスタンスを作成して保存
                var book = new Book(title, author, genre, pageCount, publisherId, isbn);
////
console.log('new book');
                existingBooks[isbn] = book;
                return book;
            }
        }
    }
})();

// クライアント(外部状態を保存)
var BookRecordManager = (function() {
    var bookRecordDatabase = {};

    return {
        // 新しい書籍をデータベースに追加
        addBookRecord: function(id, title, author, genre, pageCount, publisherId, isbn,
                checkoutDate, checkoutMember, dueReturnDate, availability) {
            var book = BookFactory.createBook(title, author, genre, pageCount, publisherId, isbn);

            bookRecordDatabase[id] = {
                checkoutMember: checkoutMember,
                checkoutDate: checkoutDate,
                dueReturnDate: dueReturnDate,
                availability: availability,
                book: book
            }
        },
        updateCheckStatus: function(bookId, newStatus, checkoutDate, checkoutMember, newReturnDate) {
            var record = bookRecordDatabase[bookId];

            record.availability = newStatus;
            record.checkoutDate = checkoutDate;
            record.checkoutMember = checkoutMember;
            record.dueReturnDate = newReturnDate;
        },
        extendCheckoutPeriod: function(bookId, newReturnDate) {
            bookRecordDatabase[bookId].dueReturnDate = newReturnDate;
        },
        isPastDue: function(bookId) {
            var currDate = new Date();

            return currDate.getTime() > Date.parse(bookRecordDatabase[bookId].dueReturnDate);
        }
    };
})();

// test
// isbn 番号は書籍の一意の識別子、以下の 3 つは 1 つの book オブジェクトのみを作成
BookRecordManager.addBookRecord(1, 'x', 'x', 'xx', 300, 10001, '100-232-32');   // new book
BookRecordManager.addBookRecord(1, 'xx', 'xx', 'xx', 300, 10001, '100-232-32');
BookRecordManager.addBookRecord(1, 'xxx', 'xxx', 'xxx', 300, 10001, '100-232-32');

管理する必要がある書籍の数が非常に大きい場合、フライウェイトパターンを使用して削減できるメモリは可观な数目になります

三. jQuery とフライウェイトパターン

例は JAMES PADOLSEY の jQuery.single です。以下の通り:

jQuery.single = (function(o){

    var collection = jQuery([1]); // Fill with 1 item, to make sure length === 1
 
    return function(element) {
 
        // Give collection the element:
        collection[0] = element;
 
        // Return the collection:
        return collection;
 
    };
 
}());

// window.$_ = jQuery.single;   // 別名を定義

// test
jQuery('a').click(function(){

    var html = jQuery.single(this).next().html(); // Method chaining works!
 
    alert(html);
 
    // etc. etc.
 
});

単例 collection を維持し、同じ DOM オブジェクトを何度も $() でラップすることによるメモリ消費を回避します(複数の jQuery オブジェクトが作成されます)。jQuery.single を使用すると、常に 1 つの jQuery オブジェクトのみが作成され、追加の jQuery オブジェクトを作成する時間の節約になり、メモリオーバーヘッドも削減されます。しかし、これを行う最大の課題は:jQuery.single が返すオブジェクトはキャッシュできないことです。内部は単例実装であるため、キャッシュされたオブジェクトは次に jQuery.single を呼び出した後に変更される可能性があるため、$() のようにいつでもキャッシュすることはできません。しかし 据说 単例を直接使用する jQuery.single は、通常の jQuery オブジェクトをキャッシュするよりも高速ですが、混乱を避けるために、DOM オブジェクトを jQuery オブジェクトにラップする必要がある場合にのみ jQuery.single メソッドを使用すること を推奨します

四. フライウェイトパターンの長所と短所

長所

  • メモリオーバーヘッドを削減

  • 大量の類似オブジェクトを管理する便利な方法を提供

短所

  • フライウェイトパターンは内部状態と外部状態を分離する必要があり、ロジックをより複雑にします

  • 外部状態が分離された後、アクセスすると軽微な追加の時間消費が発生します(時間と空間のトレードオフ?)

P.S. プロジェクトで大量のインスタンスオブジェクトを作成する必要がある場合、フライウェイトパターンが適用可能かどうかを検討すべきです

参考資料

コメント

コメントはまだありません

コメントを書く