디자인패턴

디자인 패턴

싱글톤(Singleton)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Universe() {
// 캐싱된 인스터스
var instance;
// 생성자를 재작성한다.
Universe = function Universe() {
return instance;
};
// prototype 프로퍼티를 변경한다.
Universe.prototype = this;
// instance
instance = new Universe();
// 생성자 포인터를 재지정 한다.
instance.constructor = Universe;
instance.start_time = 0;
instance.bang = 'Big';
}

팩터리(Factory)

팩터리 패턴의 목적은 객체들을 생성하는 것이다.
팩터리 패턴은 흔히 클래스 내부에서 또는 클래스의 스태틱 메서드로 구현되며, 다음과 같은 목적으로 사용된다.

  • 비슷한 객체를 생성하는 반복 작업을 수행한다.
  • 팩터리 패턴의 사용자가 컴파일 타임에 구체적인 타입(클래스)을 모르고도 객체를 생성할 수 있게 해준다.

팩터리 메서드(또는 클래스)로 만들어진 객체들은 의도적으로 동일한 부모 객체를 상속한다.
즉, 이들은 특화된 기능을 구현하는 구체적인 서브 클래스들이다.
어떤 경우에는 공통의 부모 클래스가 팩터리 메서드를 갖고 있기도 한다.

  • CarMaker 생성자 : 공통의 부모
  • CarMaker.factory() : car 객체들을 생성하는 스태틱 메서드
  • CarMaker.Compact, CarMaker.SUV, CarMaker.Convertible : CarMaker 를 상속하는 특화된 생성자.
    이 모두는 부모의 스태틱 프로터티로 정의되어 전역 네임스페이스를 깨끗이 유지하며, 필요할 때 쉽게 찾을 수 있다.

사용예

1
2
3
4
5
6
var corolla = CarMaker.factory('Compact');
var solstice = CarMaker.factory('Convertible');
var cherokee = CarMaker.factory('SUV');
corolla.drive(); // Vroom, I have 4 doors;
solstice.drive(); // Vroom, I have 2 doors;
cherokee.drive(); // Vroom, I have 17 doors;

이 부분을 눈여겨 보자
var corolla = CarMaker.factory(‘Compact’);
이 부분이 아마도 팩터리 패턴에서 가장 특징적인 부분일 것이다.
이 메서드는 런타임시 문자열로 타입을 받아 해당 타입의 객체를 생성하고 반환한다.
new와 함께 생성자를 사용하지 않고, 객체 리터럴도 보이지 않는다.

팩터리 패턴 구현 예

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function CarMaker(){}
CarMaker.prototype.dirve = function() {
return 'Vroom, I have ' + this.doors + ' doors';
}
CarMaker.factory = function (type) {
var constr = type,
newcar;
// 생성자가 존재하지 않으면 에러 발생
if ( typeof CarMaker[constr] !== 'function' ) {
throw {
name : 'Error',
message : constr + ' doesn`t exist';
}
}
// 생성자의 존재를 확인했으므로 부모를 상속한다.
// 상속은 단 한번만 실행된다.
if ( typeof CarMaker[constr].prototype.drive !== 'function' ) {
CarMaker[constr].prototype = new CarMaker();
}
// 새로운 인스톤스 생성
newcar = new CarMaker[constr]();
// 다른 메서드 호출이 필요하면 여기서 실핼한 후, 인스턴스를 반환한다.
return newcar;
}
// 구체화
CarMaker.Compact = function() {
this.door = 4;
}
CarMaker.Convertible = function() {
this.door = 2;
}
CarMaker.SUV = function() {
this.door = 24;
}

반복자(Iterator)

데이터가 저장된 내부구조는 복잡하더라도 개별 요소에 쉽게 접근할 방법이 필요하다.
사용자는 데이터 구조를 알 필요가 없이, 개별 요소로 원하는 작업을 할수만 있으면 된다.

장식자(Decorator)

런타임시 부가적인 기능을 객체에 동적으로 추가할 수 있다.
특징은 기대되는 행위를 사용자화하거나 설정할 수 있다는 것이다.
처음에는 기본적인 몇 가지 기능을 가지는 평범한 객체로 시작한다. 그런다음 사용 가능한 장식자들의 풀(poll)에 원하는 것을 골라 객체에 기능을 덧 붙여간다.

1
2
3
4
5
var sale = new Sale(100);
sale = sale.decorate('fedtax');
sale = sale.decorate('quebec');
sale = sale.decorate('money');
sale.getPrice();

getPrice()를 호출하면 money 장식자의 동일한 메서드를 호출한 셈이된다. 그러나 각각의 꾸며진 메서드는 우선 부모의 메서드를 호출하기 때문에,
money의 getPrice()는 quebec의 getPrice()를 호출하고, 다시 fedtax의 getPrice를 호출하는 식으로 차례대로 거슬러 올라 간다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
function Sale(price) {
this.price = price || 100;
}
Sale.prototype.getPrice = function() {
return this.price;
}
// 장식자 객체들은 생성자 프로퍼티 Sale.decorators의 프로퍼티로 구현된다.
Sale.decorators = {}
Sale.decorators.fedtax = {
getPrice : function() {
var price = this.uber.getPrice();
price += price * 5 / 100;
return price;
}
}
Sale.decorators.fedtax = {
getPrice : function() {
var price = this.uber.getPrice();
price += price * 5 / 100;
return price;
}
}
Sale.decorators.quebec = {
getPrice : function() {
var price = this.uber.getPrice();
price += price * 7.5 / 100;
return price;
}
}
Sale.decorators.money = {
getPrice : function() {
return '$' + this.uber.getPrice().toFixed(2);
}
}
Sale.decorators.cdn = {
getPrice : function() {
return 'CDN$' + this.uber.getPrice().toFixed(2);
}
}
// 모든 조각들을 짜맞추는 마법의 decorate() 메서드.
sale = sale.decorate('fedtax');
// 임시생성자 패턴을 이용해 상속을 구현하고, 자식 객체가 부모 객체에 접근 할 수 있도록 newobj에 uber 프로퍼티 지정.
Sale.prototype.decorate = function(decorator) {
var F = function() {},
overrides = this.constructor.decorators[decorator],
i, newobj;
F.prototype = this;
newobj = new F();
newobj.uber = F.prototpye;
for ( i in overrides ) {
if ( overrides.hasOwnProperty(i) ) {
newobj[i] = overrides[i];
}
}
return newobj;
}
// 다름 방식 (큐에 에 답아서 ..)
Sale.prototype.decorate = function(decorator) {
this.decorators_list.push(decorator);
}
Sale.prototype.getPrice = function() {
var price = this.price,
i,
max = this.decorators_list.length,
name;
for ( i = 0; i < max; i++ ) {
name = this.decorators_list[i];
price = Sale.decorators[name].getPrice(price);
}
}

전략

데이터 유효성 검사 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
var data = {
first_name : 'super',
last_name : 'man',
age : 'unknow',
username : 'o_o'
}
validator.config = {
first_name : 'isNonEmpty',
age : 'isNumber',
username : 'isAlphaNum'
}
validator.validate(data);
if ( validator.hasErrors() ) {
console.log(validator.message.join("\n"));
}
// 값을 가지는지 확인
validator.types.isNonEmpty = {
validate : function (value) {
return value !=='';
},
instructions : '이 값은 필수 입니다.'
};
// 숫자 값인지 확인한다.
validator.type.isNumber = {
validate : function(value) {
return !isNan(value);
},
instructions : '숫자만 사용할 수 있습니다.'
}
// 값이 문자와 숫자로만 이루어졌는지 확인한다.
validator.types.isAlphaNum = {
validate : function(value) {
return !/[^a-z0-9]/i.test(value);
},
instructions : '특수 문자를 제외한 글자와 숫자만 사용할 수 있습니다.'
}
var validator = {
// 사용할 수 있는 모든 검사 방법들
types : {},
// 검사 메세지들
messages : [],
// 유효성 검사 설정
config : {},
// 인터페이스 메서드
validate : function(data) {
var i, msg, type, checker, result_ok;
this.messages = [];
for ( i in data ) {
if ( data.hasOwnProperty(i) ) {
type = this.config[i];
checker = this.types[type];
if ( !type ) {
continue; //설정값이 없을때 건너뜀
}
// 설정이 존재하지 않거나 오류 발생시
if ( !checker ) {
throw {
name : 'ValidationError',
message : type + '값을 처리할 유효성 검사기가 존재하지 않습니다.'
};
}
result_ok = checker.validate(data[i]);
if ( !result_ok ) {
msg = ㅑ + '값이 유효하지 않습니다.' + checker.instructions;
}
}
}
return this.Errors();
},
hasErrors : function() {
return this.messages.length !== 0;
}
}

퍼사드(Facade)

프록시(Proxy)

중재자(Mediator)

이 패턴은 결합도를 낮추고 유지보수를 쉽게 개선하여 완화시킬수 있다.
객체들은 직접 통신하지 않고 중재자를거쳐 중재자에게 알리고, 중재자는 이 변경사항을 알아야하는 객체들에게 알린다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function Player(name) {
this.points = 0;
this.name = name;
}
Player.prototype.play = function() {
this.points += 1;
mediator.played();
}
var scoreboard = {
// 점수를 표시할 HTML 엘리먼트
element : document.getElementById('results'),
// 점수 표시를 갱신한다.
update : function (score) {
var i, msg = '';
for ( i in score ) {
if ( score.hasOwnProperty(i) ) {
msg += '<p><srtong>'+i+'</srtong>';
msg += scord[i];
msg += '</p>'
}
}
this.element.innerHTML = msg;
}
}
var mediator = {
...
}

감시자(Observer)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
var publisher = {
subscribers : {
any : [] // '이벤트 타입 : 구독자의 배열'
},
subscribe : function( fn, type ) {
type = type || 'any';
if ( typeof this.subscribers[type] === 'undefiend' ) {
this.subscribers[type] = [];
}
this.subscribers[type].push(fn);
},
unsubscribe : function( fn, type ) {
this.visitSubscribers('unsubscribe', fn, type);
},
publish : function(publication, type) {
this.visitSubscribers('publish', publication, type);
},
visitSubscribers : function(action, arg, type) {
var pubtype = type || 'any',
subscribers = this.subscribers[pubtype],
i,
max = subscribers.length;
for ( i = 0; i < max; i++ ) {
if ( action === 'publish' ) {
subscribers[i](arg);
} else {
if ( subscribers[i] === arg ) {
subscribers.splice(i, 1)
}
}
}
}
}
// 객체를 받아 발행자 객체로 바꿔준다.
// 단순히 해당 객체에 범용 발행자 메서드들을 복사해 넣는다.(deep copy)
function makePublisher(o) {
var i;
for ( i in publisher ) {
if ( publisher.hasOwnProperty(i) &&
typeof publisher[i] === 'function' ) {
o[i] = publisher[i];
}
}
o.subscribers = {any : []};
}
var paper = {
daily : function() {
this.publish('big news today');
},
monthly : function() {
this.publish('interesting analysis', 'monthly');
}
}
// paper을 발행자로 만든다.
makePublisher(paper)
// 구독자 joe
var joe = {
drinkCoffee : function (paper) {
console.log(paper + '를 읽었습니다.');
},
sundayPreNap : function (monthly) {
console.log('잠들기 전에' + monthly + '를 읽고 있습니다.');
}
}
paper의 구독자 목록에 joe를 추가
paper.subscrib(joe.drinkCoffee);
paper.subscrib(joe.sundayPreNap, 'monthly');
// 이벤트 발생 실행
paper.daily();
paper.daily();
paper.daily();
paper.monthly();

paper 객체 내에서 joe를 하드코딩하지 않았고 , joe 객체 안에서도 역시 paper 객체를 하드코딩 하지 않았다는 점에서 훌륭하다.
모든 내용을 알고 있는 중재자 객체가 존재하지도 않는다.
객체들은 느슨하게 결합되었고, 이 객체들을 전혀 수정하지 않고 paper에 수많은 구독자를 추가할 수 있다.
구독 해지도 언제든지 가능하다.

예제 2 키 누르기

```js
var publisher = {
subscribers : {
any : [] // ‘이벤트 타입 : 구독자의 배열’
},
on : function( type, fn, context ) {
type = type || ‘any’;
fn = typeof fn === ‘function’ ? fn : context[fn];

    if ( typeof this.subscribers[type] == 'undefined' ) {
        this.subscribers[type] = [];
    }

    this.subscribers[type].push({
        fn : fn,
        context : context || this
    });
},
remove : function( type, fn, context ) {
    this.visitSubscribers('unsubscribe', type, fn, context);
},
fire : function( type, publication ) {
    this.visitSubscribers('publish', type, publication);
},
visitSubscribers : function(action, type, arg, context) {
    var pubtype =  type || 'any',
        subscribers = this.subscribers[pubtype],
        i,
        max = subscribers.length;

    for ( i = 0; i < max; i++ ) {
        if ( action === 'publish' ) {
            subscribers[i].fn.call(subscribers[i].context, arg);
        } else {
            if ( subscribers[i].fn === arg &&
                 subscribers[i].context === context) {
                subscribers.splice(i, 1)
            }
        }
    }
}

}

function Player(name, key) {
this.points = 0;
this.name = name;
this.key = key;
this.fire(‘newplayer’, this);
}

Player.prototype.play = function() {
this.points += 1;
this.fire(‘play’, this);
}

var game = {
keys : {}
addPlayer : function(player) {
var key = player.key.toString().charCodeAt(0);
this.keys[key] = player;
},
handleKeypress : function(e) {
e = e || window.event;
if ( game.keys[e.which] ) {
game.keys[e.which].play();
}
},
handlePlay : function(player) {
var i,
players = this.keys,
score = {};
for ( i in players ) {
if ( players.hasOwnProperty(i) ) {
score[players[i].name] = players[i].points;
}
}
this.fire(‘scorechange’, score);
}
}

makePublisher(Player.prototype);
makePublisher(game);

/**

  • game 객체는 play와 newplayer 이벤트 브라우저의 keypress 이벤트를 구독한다.
  • scoreboard 객체는 scorechange 이벤트를 구독한다.
    */

Player.prototype.on(‘newplayer’, ‘addPlayer’, game);
Player.prototype.on(‘newplayer’, ‘handlePlayer’, game);
game.on(‘scorechange’, scoreboard.update, scoreboard);
window.onkeypress = game.handleKeypress;

// TEST CODE —————–

function makePublisher(o) {
var i;
for ( i in publisher ) {
if ( publisher.hasOwnProperty(i) &&
typeof publisher[i] === ‘function’ ) {
o[i] = publisher[i];
}
}
o.subscribers = {any : []};
}
var scoreboard = {
// 점수를 표시할 HTML 엘리먼트
element : document.getElementById(‘results’),

// 점수 표시를 갱신한다.
update : function (score) {
  console.log('scoreboard.update');

// var i, msg = ‘’;
// for ( i in score ) {
// if ( score.hasOwnProperty(i) ) {
// msg += ‘

‘+i+’‘;
// msg += scord[i];
// msg += ‘


// }
// }
// this.element.innerHTML = msg;
}
}

var publisher = {
subscribers : {
any : [] // ‘이벤트 타입 : 구독자의 배열’
},
on : function( type, fn, context ) {
type = type || ‘any’;
fn = typeof fn === ‘function’ ? fn : context[fn];

    if ( typeof this.subscribers[type] == 'undefined' ) {
        this.subscribers[type] = [];
    }

    this.subscribers[type].push({
        fn : fn,
        context : context || this
    });
},
remove : function( type, fn, context ) {
    this.visitSubscribers('unsubscribe', type, fn, context);
},
fire : function( type, publication ) {
    this.visitSubscribers('publish', type, publication);
},
visitSubscribers : function(action, type, arg, context) {
    //console.log(action, type, arg);
    var pubtype =  type || 'any',
        subscribers = this.subscribers[pubtype],
        i,
        max = subscribers.length;

    for ( i = 0; i < max; i++ ) {
        if ( action === 'publish' ) {
            subscribers[i].fn.call(subscribers[i].context, arg);
        } else {
            if ( subscribers[i].fn === arg &&
                 subscribers[i].context === context) {
                subscribers.splice(i, 1)
            }
        }
    }
}

}

function Player(name, key) {
this.points = 0;
this.name = name;
this.key = key;
this.fire(‘newplayer’, this);
}

Player.prototype.play = function() {
this.points += 1;
this.fire(‘newplayer’, this);
}

var game = {
keys : {},
addPlayer : function(player) {
console.log(‘add player’);
// var key = player.key.toString().charCodeAt(0);
// this.keys[key] = player;
},
handleKeypress : function(e) {
e = e || window.event;
if ( game.keys[e.which] ) {
game.keys[e.which].play();
}
},
handlePlay : function(player) {
var i,
players = this.keys,
score = {};
for ( i in players ) {
if ( players.hasOwnProperty(i) ) {
score[players[i].name] = players[i].points;
}
}
this.fire(‘scorechange’, score);
}
}

makePublisher(Player.prototype);
makePublisher(game);

/**

  • game 객체는 play와 newplayer 이벤트 브라우저의 keypress 이벤트를 구독한다.
  • scoreboard 객체는 scorechange 이벤트를 구독한다.
    */

Player.prototype.on(‘newplayer’, ‘addPlayer’, game);
Player.prototype.on(‘newplayer’, ‘handlePlay’, game);
game.on(‘scorechange’, scoreboard.update, scoreboard);

Share