2016년 6월 20일 월요일

flyweight 패턴

■ 정의

[GOF]
 - 공유를 통한 많은 수의 소립 객체들을 효과적으로 지원

[ 실용주의 디자인패턴 ]
- 메모리 사용량을 줄이기 위해 외부 상태(상태정보 저장, 계산)를 사용하거나
  공유(객체의 복사본 대신 단일 객체에 대한 다중 참조를 이용)한다.
- 무엇을 하느냐에 의해 정의, 즉 메소드에 의해 정의됨

▶ 즉 공유하는 객체를 이용하되 상태 정보를 외부 객체가 따로 관리



■ 사용 예
- game of life를 단순하게 구현할때 '셀'이 살아있는가?는 주변 상태를 참조를 지니고 있어야된다. 1024*1024를 표현하려해도 40mb가 필요함
flywieght를 사용하면 동일한 상태를 지닌 객체가 단 하나의 객체를 통해 표현



■ 장/단점
- 장점 : flyweight를 사용하지 않으면 객체지향적으로 구현하기가 힘듬
- 장점 : flyweight 풀을 이용하면 자바의 == 연산자로 객체의동일성 여부를 결정 할 수 있다.

- 단점 : 외부상태가 계산되는 값이라면(매번 데이터베이스에 접근) 하면 접근이 느릴 수 있다.
- 단점 : 코드가 복잡해짐, 유지보수를 어렵게하고 코드가 커질 수 있음


■ 코드 예제
쉬운 예>
ElementStyle e = new ElementStyle;
e.setMargin(ElementStyleFactory.createMargin(10,10,20,10)); -> 객체는 공유하되 외부 객체가 따로 관리 되도록

게임프로그래밍 패턴예제(js로 구현)>
[origin]
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>flyweight origin</title>
</head>
<body>
경량패턴??<br>
어떤 객체의 수가 너무 많아 좀 가볍게 만들고 싶을때 사용<br>
고유상태와 외부상태로 나눔<br>
<br>
예제 : 지형 만들기<br>
타일(고유상태)과 각 타일들이 갖고 있는 정보(외부상태)<br>
<script>
    function Terrain(kind){
        return {
            "TERRAIN_CLASS" : "TERRAIN_CLASS",
            "TERRAIN_HILL" : "TERRAIN_HILL",
            "TERRAIN_RIVER" : "TERRAIN_RIVER"        }[kind];
    }

    function World(){
        this.tiles = {};
    }
    World.prototype.getMovementCost = function(x,y){
        return {
            "TERRAIN_CLASS" : 1,
            "TERRAIN_HILL" : 2,
            "TERRAIN_RIVER" : 3        }[this.tiles[x][y]];
    };

    World.prototype.getIsWater = function(x,y){
        return {
            "TERRAIN_CLASS" : false,
            "TERRAIN_HILL" : false,
            "TERRAIN_RIVER" : true        }[this.tiles[x][y]];
    };

    World.prototype.setTile = function(x,y, value){
        this.tiles = this.tiles||{};

        this.tiles[x] = this.tiles[x]||{};
        this.tiles[x][y] = this.tiles[x][y]||"";
        this.tiles[x][y] = value;

    };


    function Main(){
        var world = new World;
        var terrain_class = Terrain("TERRAIN_CLASS");
        var terrain_hill = Terrain("TERRAIN_HILL");
        var terrain_river = Terrain("TERRAIN_RIVER");

        world.setTile(1,1,terrain_class) ;
        world.setTile(1,2,terrain_hill) ;
        world.setTile(1,3,terrain_river) ;

        console.log(world.getIsWater(1,3));
        console.log(world.getMovementCost(1,2));
    }

    window.onload = function(){
        Main();
    }
</script>
</body>
</html>

[fly weight]
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>flyweight </title>
</head>
<body>
경량패턴??<br>
어떤 객체의 수가 너무 많아 좀 가볍게 만들고 싶을때 사용<br>
고유상태와 외부상태로 나눔<br>
<br>
예제 : 지형 만들기<br>
타일(고유상태)과 각 타일들이 갖고 있는 정보(외부상태)<br>
<script>
    function Terrain(movementCost, isWater, texture){

        var movementCost_;
        var isWater_;
        var texture_;

        function _Terrain(){
            this.movementCost_ = movementCost;
            this.isWater_ = isWater;
            this.texture_ = texture;

        }

        _Terrain.prototype.getMovementCost = function(){
            return this.movementCost_;
        };
        _Terrain.prototype.isWater = function(){
            return this.isWater_;
        };
        _Terrain.prototype.getTexture = function(){
            return this.texture_;
        };


        if(!(this instanceof _Terrain )){
            return new _Terrain();
        }

    }

    var CONST_GLASS_TEXTURE = "GLASS_TEXTURE";
    var CONST_HILL_TEXTURE = "HILL_TEXTURE";
    var CONST_RIVER_TEXTURE = "RIVER_TEXTURE";
    var CONST_WIDTH = 10;
    var CONST_HEIGHT = 12;

    function World(){

        var tiles = {};


        function _World(){
            this.glassTerrain_ = [1,false,CONST_GLASS_TEXTURE];
            this.hillTerrain_ = [2,false,CONST_HILL_TEXTURE];
            this.riverTerrain_ = [3,true,CONST_RIVER_TEXTURE];
            this.tiles = tiles;

        }

        _World.prototype.setTile = function(x,y, value){
            this.tiles = this.tiles||{};

            this.tiles[x] = this.tiles[x]||{};
            this.tiles[x][y] = this.tiles[x][y]||"";
            this.tiles[x][y] = value;

        };

        _World.prototype.getTile = function(x,y){

            return Terrain.apply(this, this.tiles[x][y]);
        };


        _World.prototype.generateTerrain = function(){
            for(var x=0; x < CONST_WIDTH; x++) {
                for(var y=0; y < CONST_HEIGHT; y++){
                    if(x % 2 == 0){
                        this.setTile.apply(this, [x,y,this.glassTerrain_]);
                    }else{
                        this.setTile.apply(this, [x,y,this.hillTerrain_]);
                    }
                    if(y % 3 == 1){
                        this.setTile.apply(this, [x,y,this.riverTerrain_]);
                    }
                }
            }
        };

        if(!(this instanceof _World )){
            return new _World();
        }

    }



    function Main(){
        var world = World();
        world.generateTerrain();
        console.dir(world.getTile(1,1).getMovementCost());

    }

    window.onload = function(){
        Main();
    }
</script>
</body>
</html>

2016년 6월 19일 일요일

게임프로그래밍 패턴 - 커맨드패턴을 이용한 이동키로 화면 움직이기 구현

아래는 원본 함수
handleInput에 if문으로 각각의 함수를 호출 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>command pattern</title>
    <style>
        #unit {
            position: absolute;
            background-color: olivedrab;
            top:100px;
            left:100px;
            display:block        }
    </style>
</head>
<body>
원본소스
<div id="unit">
    unit
</div>
<script>
    document.addEventListener("keydown", handleInput, false);
    function handleInput(e){
        var keyCode = e.keyCode;
        if(keyCode == 38){
            up();
        }else if(keyCode == 40){
            down();
        }else if(keyCode == 37){
            left();
        }else if(keyCode == 39){
            right();
        }
    };


    function unit(){
        var target = document.getElementById('unit');

        return {
            "x" : parseInt(getStyle(target,"left"),10),
            "y" : parseInt(getStyle(target,"top"),10),
            "body" : target.style        };
    }
window.onload= function(){
    console.log(unit().x);
};


</script>

<script>


    function getStyle(el, styleProp) {
        var value, defaultView = (el.ownerDocument || document).defaultView;
        // W3C standard way:        if (defaultView && defaultView.getComputedStyle) {
            // sanitize property name to css notation            // (hypen separated words eg. font-Size)            styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase();
            return defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);
        } else if (el.currentStyle) { // IE            // sanitize property name to camelCase            styleProp = styleProp.replace(/\-(\w)/g, function(str, letter) {
                return letter.toUpperCase();
            });
            value = el.currentStyle[styleProp];
            // convert other units to pixels on IE            if (/^\d+(em|pt|%|ex)?$/i.test(value)) {
                return (function(value) {
                    var oldLeft = el.style.left, oldRsLeft = el.runtimeStyle.left;
                    el.runtimeStyle.left = el.currentStyle.left;
                    el.style.left = value || 0;
                    value = el.style.pixelLeft + "px";
                    el.style.left = oldLeft;
                    el.runtimeStyle.left = oldRsLeft;
                    return value;
                })(value);
            }
            return value;
        }
    }

function up(){
    var target = unit().y;
    return unit().body.top = suffixValue(target, -3);
}

    function down(){
        var target = unit().y;
        return unit().body.top = suffixValue(target, +3);
    }

    function left(){
        var target = unit().x;
        return unit().body.left = suffixValue(target, -3);
    }

    function right(){
        var target = unit().x;
        return unit().body.left = suffixValue(target, +3);
    }

function suffixValue(target, value){
    var suffix = "px";
    return (target + value)+suffix;
}

</script>
</body>
</html>


커맨드 패턴을 이용하여 각각의 메서드 호출을 실체화 함, and undo, redo 구현

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>command pattern</title>
    <style>
        #unit {
            position: absolute;
            background-color: olivedrab;
            top:100px;
            left:100px;
            display:block        }
    </style>
</head>
<body>
메서드의 호출을 실체화 함
<div id="unit">
    unit
</div>
<script>

    function getStyle(el, styleProp) {
        var value, defaultView = (el.ownerDocument || document).defaultView;
        // W3C standard way:        if (defaultView && defaultView.getComputedStyle) {
            // sanitize property name to css notation            // (hypen separated words eg. font-Size)            styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase();
            return defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);
        } else if (el.currentStyle) { // IE            // sanitize property name to camelCase            styleProp = styleProp.replace(/\-(\w)/g, function(str, letter) {
                return letter.toUpperCase();
            });
            value = el.currentStyle[styleProp];
            // convert other units to pixels on IE            if (/^\d+(em|pt|%|ex)?$/i.test(value)) {
                return (function(value) {
                    var oldLeft = el.style.left, oldRsLeft = el.runtimeStyle.left;
                    el.runtimeStyle.left = el.currentStyle.left;
                    el.style.left = value || 0;
                    value = el.style.pixelLeft + "px";
                    el.style.left = oldLeft;
                    el.runtimeStyle.left = oldRsLeft;
                    return value;
                })(value);
            }
            return value;
        }
    }
    document.addEventListener("keydown", handleInput, false);
    function handleInput(e){          //메인함수
        var keyCode = e.keyCode;

        var pressDispatcher = {       //command dispatcher            37 : new LeftCommand,
            38 : new UpCommand,
            39 : new RightCommand,
            40 : new DownCommand,
            89 : new RedoCommand,
            90 : new UndoCommand        };

        var button = pressDispatcher[keyCode];
        var isPress = (keyCode in pressDispatcher);
        if(isPress) button.execute();


    };

    var Command = function(){
        var x = unit().x, y = unit().y;
        return {
                "execute" : function(){

                    console.log("origin command execute");
                },
                "undo" : function(){
                    if(this.beforeLocation.length > 0){
                        var locationData = this.beforeLocation.pop();
                            unit().move(locationData);
                            this.redoHistoryWrite(locationData);
                    }
                },

                "redo" : function(){
                    if(this.afterLocation.length > 0){
                        var locationData = this.afterLocation.pop();
                        unit().move(locationData);
                        this.undoHistoryWrite(locationData);
                    }
                },
                'beforeLocation' : [],
                "undoHistoryWrite" : function(){

                    this.beforeLocation.push([unit().x,unit().y]);
                },

                'afterLocation' : [],
                "redoHistoryWrite" : function(){

                    this.afterLocation.push([unit().x,unit().y]);
                }
            };
    }();


    function RedoCommand(){

        function _RedoCommand(){
            this.execute = function(){
                this.redo();
            };

        }
        //@extends        _RedoCommand.prototype = Command;

        if(!(this instanceof _RedoCommand )){
            return new _RedoCommand();
        }
    }


    function UndoCommand(){

        function _UndoCommand(){
            this.execute = function(){
                this.undo();
            };

        }
        //@extends        _UndoCommand.prototype = Command;

        if(!(this instanceof _UndoCommand )){
            return new _UndoCommand();
        }
    }

    function UpCommand(){

        function _UpCommand(){

            this.execute = function(){
                this.undoHistoryWrite();
                up();
            };

        }
        //@extends        _UpCommand.prototype = Command;

        if(!(this instanceof _UpCommand )){
            return new _UpCommand();
        }
    }


    function DownCommand(){

        function _DownCommand(){
            this.execute = function(){
                this.undoHistoryWrite();
                down();
            }
        }
        //@extends        _DownCommand.prototype = Command;

        if(!(this instanceof _DownCommand )){
            return new _DownCommand();
        }
    }


    function LeftCommand(){

        function _LeftCommand(){
            this.execute = function(){
                this.undoHistoryWrite();
                left();
            }
        }
        //@extends        _LeftCommand.prototype = Command;

        if(!(this instanceof _LeftCommand )){
            return new _LeftCommand();
        }
    }


    function RightCommand(){

        function _RightCommand(){
            this.execute = function(){
                this.undoHistoryWrite();
                right();
            }
        }
        //@extends        _RightCommand.prototype = Command;

        if(!(this instanceof _RightCommand )){
            return new _RightCommand();
        }
    }


    function unit(){
        var target = document.getElementById('unit');

        return {
            "x" : parseInt(getStyle(target,"left"),10),
            "y" : parseInt(getStyle(target,"top"),10),
            "body" : target.style,
            "move" : function(arr){
                target.style.left = arr[0]+"px";
                target.style.top = arr[1]+"px";

            }
        };
    }
window.onload= function(){
    /*console.log(unit().x);*/};


</script>

<script>



function up(){
    var target = unit().y;
    return unit().body.top = suffixValue(target, -3);
}

function down(){
    var target = unit().y;
    return unit().body.top = suffixValue(target, +3);
}

function left(){
    var target = unit().x;
    return unit().body.left = suffixValue(target, -3);
}

function right(){
    var target = unit().x;
    return unit().body.left = suffixValue(target, +3);
}

function suffixValue(target, value){
    var suffix = "px";
    return (target + value)+suffix;
}

</script>
</body>
</html>