본문 바로가기
Study Note/Javascript

javascript #디자인패턴 - 메모이제이션 패턴 (memoization pattern)

by 시뮝 2021. 2. 13.
728x90

메모이제이션(memoization) 패턴

패턴의 이름대로 '메모'를 하는 것이 특징입니다. 메모를 하는 대상은 바로 함수 또는 객체입니다. 이 패턴의 활용 용도는 일반적인 프로그래밍 언어에서 캐시를 사용하는 것처럼 사용한다고 생각하면 됩니다.

 

적용예시1. 메모이제이션 패턴을 이용한 캐시 구현

특정 아이템을 검색하려고 할 때, 검색 결과를 ID 기반으로 캐시 처리하는 메모이제이션 패턴입니다. 메모이제이션 패턴의 메모를 하는 대상은 변수가 될 수도 있고 함수가 될 수도 있습니다. 아래 소스에서 검색하는 함수가 searchItem() 이라면 이 함수 자체에 cache라는 속성을 객체로 추가하고, cache에는 아이템이 있으면 캐시에 저장된 객체를 반환하고 없으면 서버로 요청합니다.

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
       <input type="text" id="itemId">
       <button id="search">Search</button>
        <script>
        (function () {
            let inputItemId = document.getElementById("itemId");
            
            function searchItem(id) {
                let xhr;
                
                if (searchItem.cache.hasOwnProperty(id)) {
                    return searchItem.cache[id];
                }
                xhr = new XMLHttpRequest();
                xhr.open("GET", "/searchItem");
                xhr.onload = function () {
                    let item = JSON.parse(xhr.responseText);
                    searchItem.cache[item.id] = item;
                }
                xhr.send();
            }
            searchItem.cache = {};
            
            document.getElementById("search").addEventListener("click", function () {
                searchItem(searchItem.value);
            });
        }());
        </script>
    </body>
</html>

 

적용예시2. 메모이제이션 패턴으로 피보나치 수열 구현

메모이제이션 패턴은 피보나치 수열의 입력값을 메모에 저장하여 메모에 있으면 다시 불러오는 식으로 동작합니다. 이러한 식으로 이전에 계산된 결과를 토대로 새로운 결과를 계산할 수 있을 때, 캐시 등을 이용하면 그것을 동적 프로그래밍(dynamic programming)이라고 합니다.

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <script>
        (function () {
            function fibonacci(n) {
                if (n === 0 || n ===1) {
                    return n;
                } else {
                    return fibonacci(n - 1) +fibonacci(n - 2);
                }
            }
            
            let fibonacciMemo = (function () {
                return function (n) {
                    let result = fibonacciMemo.memo[n];
                    if (typeof result !== 'number') {
                        result = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);
                        fibonacciMemo.memo[n] = result;
                    }
                    return result;
                };
            }());
            fibonacciMemo.memo = [0, 1];
            
            let testNum = 40,
                start, end;
            
            // 최초 호출
            start = Date.now();
            console.log(fibonacci(testNum)); //==>> 102334155
            end = Date.now();
            console.log(`Elapse time of ${((end-start)/1000).toFixed(2)} seconds for recursive fibonacci(${testNum})`);
            //==>> Elapse time of 1.93 seconds for recursive fibonacci(40) 
            //참고. 소요시간은 조금씩 달라질 수 있습니다.
            
            // 두 번째 호출 : 0초 소요
            start = Date.now();
            console.log(fibonacciMemo(testNum)); //==>> 102334155
            end = Date.now();
            console.log(`Elapsed time of ${((end-start)/1000).toFixed(2)} seconds for recursive fibonacciMemo(${testNum})`);
            //==>> Elapse time of 0.00 seconds for recursive fibonacci(40)
            
            // 세 번째 호출 : 0초 소요
            start = Date.now();
            console.log(fibonacciMemo(testNum)); //==>> 102334155
            end = Date.now();
            console.log(`Elapsed time of ${((end-start)/1000).toFixed(2)} seconds for recursive fibonacciMemo(${testNum})`);
            //==>> Elapse time of 0.00 seconds for recursive fibonacci(40)
        }());
        </script>
    </body>
</html>

구현2. 메모이제이션 패턴으로 피보나치 수열 구현 - 결과

 

적용예시3. Function.prototype 활용

이러한 메모이제이션 패턴을 일반적으로 사용할 수 있도록 Function의 프로토타입에 추가해서 사용하면 복잡하지 않고 편리하게 활용할 수 있습니다. Function.prototype에 추가하는 memoize() 함수는 재귀가 아닌 일반적인 산술 처리나 XMLHttpRequest 등과 같이 캐시를 활용할 수 있는 함수에 추가 기능으로 사용하면 좋고, 재귀 함수는 직접 메모이제이션 패턴을 설계하여 사용하는 것이 더 좋은 성능을 보여줍니다. 

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <script>
        (function () {
            Function.prototype.memoize = function () {
                let _this = this,
                    memo = {};
                return function () {
                    let argsString = JSON.stringify(arguments),
                        returnValue;
                    if (memo.hasOwnProperty(argsString)) {
                        return memo[argsString];
                    } else {
                        returnValue = _this.apply(this, arguments);
                        memo[argsString] = returnValue;
                        return returnValue;
                    }
                }
            };
            
            function fibonacci(n) {
                if (n === 0 || n === 1) {
                    return n;
                } else {
                    return fibonacci(n - 1) + fibonacci(n - 2);
                }
            }
            
            let fibonacciMemo = fibonacci.memoize();
            
            let testNum = 40,
                start, end;
            
            // 최초 조회
            start = Date.now();
            console.log(fibonacciMemo(testNum));
            end = Date.now();
            console.log(`Elapsed time of ${((end-start)/1000).toFixed(2)} seconds for recursive fibonacciMemo(${testNum}) for the first time`);
            //==>> Elapsed time of 1.97 seconds for recursive fibonacciMemo(40) for the first time
            
            // 두 번째 조회
            start = Date.now();
            console.log(fibonacciMemo(testNum));
            end = Date.now();
            console.log(`Elapsed time of ${((end-start)/1000).toFixed(2)} seconds for recursive fibonacciMemo(${testNum}) for the first time`);
            //==>> Elapsed time of 0.00 seconds for recursive fibonacciMemo(40) for the first time
        }());
        </script>
    </body>
</html>

#

참고도서 : 속깊은 자바스크립트

이전글 : 

2021/02/10 - [Study Note/Javascript] - javascript #디자인패턴 - 폼 검증을 위한 데커레이터 패턴(decorator pattern)

 

javascript #디자인패턴 - 폼 검증을 위한 데커레이터 패턴(decorator pattern)

데커레이터 패턴 프락시 패턴은 호출되는 객체가 아닌 별도의 중간자 역할을 수행하는 프락시 객체가 추가 기능을 수행하는 패턴이라고 한다면, 데커레이터(decorator) 패턴은 호출 대상이 되는

simuing.tistory.com

2021/02/10 - [Study Note/Javascript] - javascript #디자인패턴 - 객체 기반 데커레이터 패턴(decorator pattern)

 

javascript #디자인패턴 - 객체 기반 데커레이터 패턴(decorator pattern)

하나의 객체에 여러 가지 기능들을 추가함으로써 기존의 객체에 추가로 꾸며진 객체를 만들어낼 수 있습니다. 데커레이터 패턴을 이용해 각기 다른 부품으로 이루어진 컴퓨터의 가격을 알아봅

simuing.tistory.com

2021/02/10 - [Study Note/Javascript] - javascript #디자인패턴 - 함수 기반 데커레이터 패턴(decorator pattern)

 

javascript #디자인패턴 - 함수 기반 데커레이터 패턴(decorator pattern)

프락시 패턴에서 살펴봤던 래퍼 기능을 데커레이터 패턴과 함께 사용하면, 해당 함수가 호출되기 전에 여러 가지 함수가 호출될 수 있도록 응용할 수 있습니다. 이렇게 응용할 수 있는 상황은

simuing.tistory.com

2021/02/10 - [Study Note/Javascript] - javascript #디자인패턴 - Init-time branching 패턴

 

javascript #디자인패턴 - Init-time branching 패턴

Init-time branching 패턴 Init-time branching 패턴은 이름 그대로 초기화 단계에서 분기하여 같은 함수를 환경에 따라 다르게 정의하는 것을 의미합니다. 보통 웹페이지가 처음 열릴 때 실행된다고 해서 I

simuing.tistory.com

2021/02/12 - [Study Note/Javascript] - javascript #디자인패턴 - Self-defining function 패턴

 

javascript #디자인패턴 - Self-defining function 패턴

Self-defining function 패턴 앞서 살펴본 Init-time branching 패턴은 처음 웹페이지 초기화 단계에서 컴퓨팅 자원을 소모하여 향후 어떠한 방법으로 함수가 호출될 지 결정하는 것이었다면, Self-defining funct

simuing.tistory.com

 

728x90

댓글