본문으로 건너뛰기

플라이웨이트 패턴_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 번호는 책의 고유 식별자, 아래 세 개는 하나의 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 을 사용하면 항상 하나의 jQuery 객체만 생성되며, 추가 jQuery 객체 생성 시간을 절약하고 메모리 오버헤드도 줄입니다. 그러나 이렇게 하는 가장 큰 문제는: jQuery.single 이 반환하는 객체는 캐시할 수 없다는 것입니다. 내부는 단일 인스턴스 구현이므로, 캐시된 객체는 다음에 jQuery.single 을 호출한 후 변경될 수 있으므로, $() 처럼 언제든지 캐시할 수 없습니다. 하지만 据说 단일 인스턴스를 직접 사용하는 jQuery.single 이 일반 jQuery 객체를 캐시하는 것보다 빠르지만, 혼란을 피하기 위해, DOM 객체를 jQuery 객체로 감싸야 할 때만 jQuery.single 메서드를 사용할 것 을 권장합니다

사. 플라이웨이트 패턴의 장단점

장점

  • 메모리 오버헤드 감소

  • 대량의 유사한 객체를 관리하는 편리한 방법 제공

단점

  • 플라이웨이트 패턴은 내부 상태와 외부 상태를 분리해야 하므로 로직이 더 복잡해집니다

  • 외부 상태가 분리된 후, 액세스하면 경미한 추가 시간 소비가 발생합니다 (시간으로 공간을 바꾸는 것?)

P.S. 프로젝트에서 대량의 인스턴스 객체를 생성해야 한다면, 플라이웨이트 패턴이 적합한지 고려해봐야 합니다

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성