블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2013. 9. 28. 17:45 Languages/JavaScript

자바스크립트의 상속과 성능향상을 위하여서는 Prototype Pattern을 갖는 Prototypal Inheritance를 이용한다.




개념

  - ECMAScript 5에서 Object.create 로 가능하다 

var myCar = { name: "Ford Escort", drive: function () { console.log( "Weeee. I'm driving!" ); }, panic: function () { console.log( "Wait. How do you stop this thing?" ); } }; // Use Object.create to instantiate a new car var yourCar = Object.create( myCar ); // Now we can see that one is a prototype of the other 

console.log( yourCar.name );

 

  - 오브젝트 확장할 때 

var vehicle = { getModel: function () { console.log( "The model of this vehicle is.." + this.model ); } };

// 두번째 인자에서 확장한다 Mixin... var car = Object.create(vehicle, { "id": { value: MY_GLOBAL.nextId(), // writable:false, configurable:false by default enumerable: true }, "model": { value: "Ford", enumerable: true } }); 


  - Object.create를 사용하지 않고 Construct Function의 prototype 프로퍼티를 이용하여 prototypal inheritance 구현 

var vehiclePrototype = { init: function ( carModel ) { this.model = carModel; }, getModel: function () { console.log( "The model of this vehicle is.." + this.model); } }; function vehicle( model ) { function F() {};  // 일급 클래스 펑션의 prototype을 vehiclePrototype객체로 정의 

// 상단 이미지에서 Foo의 프로퍼티인 propertype 변수에 Foo.prototype 객체를 참조한다

F.prototype = vehiclePrototype;

// new를 하면 vechiclePrototype객체가 f 변수에 할당 된다 var f = new F(); f.init( model ); return f; } var car = vehicle( "Ford Escort" ); 

console.log(car.getModel());

다른 방법으로 proto 전달 

var beget = (function () { function F() {} return function ( proto ) { F.prototype = proto; return new F(); }; 

})();



다양한 Object creation으로 메소드 확장하기

  - 메소드안에 정의 

    + getInfo는 Car 생성때 마다 생성되므로 메모리를 차지한다 (안좋음) 

function Car(make, model, level, color, warranty) {
    this.make     = make;
    this.model    = model;
    this.level    = level;
    this.color    = color;
    this.warranty = warranty;
    
    this.getInfo = function() {
        return this.make + ', ' + this.model + ', ' + this.level + ', '+ this.color + ', ' + this.warranty;
    };
}
 
var aCar = new Car('Acura', 'TL', 'Sport', 'blue', 5);

console.log(aCar.getInfo());  //displays Acura, TL, Sport, blue, 5 


  - 메소드를 외부에서 Prototype에 지정하기 

    + 필요한 시점에 메소드를 추가할 수 있다 예) Car.prototype.accelerate 

function Car(make, model, level, color, warranty) {
    this.make     = make;
    this.model    = model;
    this.level    = level;
    this.color    = color;
    this.warranty = warranty;
}
 
Car.prototype.getInfo = function() {
  return this.make +','+ this.model +','+ this.level +','+ this.color +','+ this.warranty;
};
 
var aCar = new Car('Acura', 'TL', 'Sport', 'blue', 5);
alert(aCar.getInfo());  //displays Acura, TL, Sport, blue, 5
 
Car.prototype.accelerate = function() {
    return 'How fast?';
};

console.log(aCar.accelerate()); //displays "How fast?"


  - Prototype Pattern을 이용

    + prototype에 필요한 메소드를 Literal 객체로 지정한다 

function Car(make, model, level, color, warranty) {
    this.make     = make;
    this.model    = model;
    this.level    = level;
    this.color    = color;
    this.warranty = warranty;
}
 
Car.prototype = {
    getInfo: function () {
      return this.make +','+ this.model +','+ this.level +','+ this.color +','+ this.warranty;
    }

};


  - Revealing Prototype Pattern을 이용

    + prototype에 객체를 생성하여 할당한다 

var UsedCar = function(mileage) {
    //Define a variable unique to each instance of UsedCar
    this.mileage = mileage;
};
 

UsedCar.prototype = new Car('Honda', 'Civic', 'LX', 'gray', 2);


// Revealing Module Pattern 형식

UsedCar.prototype = function () { }();



<참조>

  - 원문 : The Prototype Pattern

  - Some Prototype Pattern 사용하기 

  - Revealing Prototype Pattern 사용하기

  - 드미트리 소스시코브의 JavaScript Core

posted by 윤영식
2013. 9. 14. 12:18 Languages/JavaScript

Mediator 패턴은 충돌을 해결하고 협력할 수 있도록 해주는 것이다. 즉 시스템에 많은 컴포넌트들이 있다면 서로의 커뮤니케이션을 중재하고 제어한다. 예로 공항 관제실을 생각해 보자. 비행기가 컴포넌트이고 이들의 이착륙을 관리하는 관제실이 Mediator가 된다. 




개념 이해하기 

  - Mediator

    Colleague 오븐젝트와 통신하기 위한 인터페이스를 정의한다 

  - ConcreteMediator

    Colleague 클래스를 알고 Colleague 오브젝트의 레퍼런스를 가지고 있는다. 

    Colleague 끼리 메세지를 전달하고 통신토록 해준다 

  - Colleague Classes

    Mediator 오브젝트에 레퍼런스를 유지한다. Mediator를 통하여 다른 Colleague와 통신한다 



PubSub 만들기 

  - Mediator Core

  - publish() 와 subscribe() 

var mediator = (function(){

    // Storage for topics that can be broadcast or listened to

    var topics = {};


    // Subscribe to a topic, supply a callback to be executed

    // when that topic is broadcast to

    var subscribe = function( topic, fn ){

        if ( !topics[topic] ){ 

          topics[topic] = [];

        }

        topics[topic].push( { context: this, callback: fn } );

        return this;

    };


    // Publish/broadcast an event to the rest of the application

    var publish = function( topic ){

        var args;

        if ( !topics[topic] ){

          return false;

        } 


        args = Array.prototype.slice.call( arguments, 1 );

        for ( var i = 0, l = topics[topic].length; i < l; i++ ) {

            var subscription = topics[topic][i];

            subscription.callback.apply( subscription.context, args );

        }

        return this;

    };


    return {

        publish: publish,

        subscribe: subscribe,

        installTo: function( obj ){

            obj.subscribe = subscribe;

            obj.publish = publish;

        }

    };


}());

  - Jack Lawson의 Mediator.js 를 가져다 쓰면 된다 

/*! * Mediator.js Library v0.9.5 * https://github.com/ajacksified/Mediator.js * * Copyright 2013, Jack Lawson * MIT Licensed (http://www.opensource.org/licenses/mit-license.php) * * For more information: http://thejacklawson.com/2011/06/mediators-for-modularized-asynchronous-programming-in-javascript/index.html * Project on GitHub: https://github.com/ajacksified/Mediator.js * * Last update: June 13 2013 */ (function(global, factory) { 'use strict';

// 다양한 환경에서 적용할 수 있도록 모듈에 대한 export if(typeof exports !== 'undefined') { // Node/CommonJS exports.Mediator = factory(); } else if(typeof define === 'function' && define.amd) { // AMD define('mediator-js', [], function() { global.Mediator = factory(); return global.Mediator(); }); } else { // Browser global global.Mediator = factory(); } }(this, function() { 'use strict'; // We'll generate guids for class instances for easy referencing later on. // Subscriber instances will have an id that can be refernced for quick // lookups.

// 글로벌 유니크 아이디를 생성하는 로직이다. 등록된 Colleague 오브젝트에 (즉, Subscriber Instances)

// 아이디를 부여해 준다 function guidGenerator() { var S4 = function() { return (((1+Math.random())*0x10000)|0).toString(16).substring(1); }; return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); } // Subscribers are instances of Mediator Channel registrations. We generate // an object instance so that it can be updated later on without having to // unregister and re-register. Subscribers are constructed with a function // to be called, options object, and context. // Colleague인 Subscriber 펑션 정의 function Subscriber(fn, options, context){ if(!(this instanceof Subscriber)) { return new Subscriber(fn, options, context); } this.id = guidGenerator(); this.fn = fn; this.options = options; this.context = context; this.channel = null; }

// Subscriber Prototype 객체 정의

// Subscriber에 등록한 fn, context, options를 변경할 수 있다 Subscriber.prototype = { // Mediator.update on a subscriber instance can update its function,context, // or options object. It takes in an object and looks for fn, context, or // options keys. update: function(options){ if(options){ this.fn = options.fn || this.fn; this.context = options.context || this.context; this.options = options.options || this.options; if(this.channel && this.options && this.options.priority !== undefined) { this.channel.setPriority(this.id, this.options.priority); } } } }; // 채널을 통하여 Subscriber 의 등록/제거 관리 

// Subscriber에 정보 publish function Channel(namespace, parent){ if(!(this instanceof Channel)) { return new Channel(namespace); } this.namespace = namespace || ""; this._subscribers = []; this._channels = []; this._parent = parent; this.stopped = false; } // A Mediator channel holds a list of sub-channels and subscribers to be fired // when Mediator.publish is called on the Mediator instance. It also contains // some methods to manipulate its lists of data; only setPriority and // StopPropagation are meant to be used. The other methods should be accessed // through the Mediator instance. // Mediator 채널이 sub-channel 목록을 가지고 있다 

// Channel Prototype 객체 정의 Channel.prototype = { addSubscriber: function(fn, options, context){ var subscriber = new Subscriber(fn, options, context); if(options && options.priority !== undefined){ // Cheap hack to either parse as an int or turn it into 0. Runs faster // in many browsers than parseInt with the benefit that it won't // return a NaN. options.priority = options.priority >> 0; if(options.priority < 0){ options.priority = 0; } if(options.priority >= this._subscribers.length){ options.priority = this._subscribers.length-1; } this._subscribers.splice(options.priority, 0, subscriber); }else{ this._subscribers.push(subscriber); } subscriber.channel = this; return subscriber; }, // The channel instance is passed as an argument to the mediator subscriber, // and further subscriber propagation can be called with // channel.StopPropagation().

// Mediator에 등록한 Subscriber에 아규먼트 전달 멈춤 stopPropagation: function(){ this.stopped = true; },


getSubscriber: function(identifier){ var x = 0, y = this._subscribers.length; for(x, y; x < y; x++){ if(this._subscribers[x].id === identifier || this._subscribers[x].fn === identifier){ return this._subscribers[x]; } } }, // Channel.setPriority is useful in updating the order in which Subscribers // are called, and takes an identifier (subscriber id or named function) and // an array index. It will not search recursively through subchannels. // subchannel을 돌면서 우선순위를 업데이트 한다 setPriority: function(identifier, priority){ var oldIndex = 0, x = 0, sub, firstHalf, lastHalf, y; for(x = 0, y = this._subscribers.length; x < y; x++){ if(this._subscribers[x].id === identifier || this._subscribers[x].fn === identifier){ break; } oldIndex ++; } sub = this._subscribers[oldIndex]; firstHalf = this._subscribers.slice(0, oldIndex); lastHalf = this._subscribers.slice(oldIndex+1); this._subscribers = firstHalf.concat(lastHalf); this._subscribers.splice(priority, 0, sub); },

// sub channel 등록/삭제 addChannel: function(channel){ this._channels[channel] = new Channel((this.namespace ? this.namespace + ':' : '') + channel, this); }, hasChannel: function(channel){ return this._channels.hasOwnProperty(channel); }, returnChannel: function(channel){ return this._channels[channel]; }, removeSubscriber: function(identifier){ var x = this._subscribers.length - 1; // If we don't pass in an id, we're clearing all if(!identifier){ this._subscribers = []; return; } // Going backwards makes splicing a whole lot easier. for(x; x >= 0; x--) { if(this._subscribers[x].fn === identifier || this._subscribers[x].id === identifier){ this._subscribers[x].channel = null; this._subscribers.splice(x,1); } } }, // This will publish arbitrary arguments to a subscriber and then to parent // channels. // 퍼블리싱 : subscriber에 아규먼트 전달 publish: function(data){ var x = 0, y = this._subscribers.length, called = false, subscriber, l, subsBefore,subsAfter; // Priority is preserved in the _subscribers index. for(x, y; x < y; x++) { if(!this.stopped){ subscriber = this._subscribers[x]; if(subscriber.options !== undefined && typeof subscriber.options.predicate === "function"){ if(subscriber.options.predicate.apply(subscriber.context, data)){ subscriber.fn.apply(subscriber.context, data); called = true; } }else{ subsBefore = this._subscribers.length; subscriber.fn.apply(subscriber.context, data); subsAfter = this._subscribers.length; y = subsAfter; if (subsAfter === subsBefore - 1){ x--; } called = true; } } if(called && subscriber.options && subscriber.options !== undefined){ subscriber.options.calls--; if(subscriber.options.calls < 1){ this.removeSubscriber(subscriber.id); y--; x--; } } } if(this._parent){ this._parent.publish(data); } this.stopped = false; } };

// Mediator 펑션 function Mediator() { if(!(this instanceof Mediator)) { return new Mediator(); } this._channels = new Channel(''); } // A Mediator instance is the interface through which events are registered // and removed from publish channels. Mediator.prototype = { // Returns a channel instance based on namespace, for example // application:chat:message:received // sub channel은 namespace 기반으로 만들어 진다 getChannel: function(namespace){ var channel = this._channels, namespaceHierarchy = namespace.split(':'), x = 0, y = namespaceHierarchy.length; if(namespace === ''){ return channel; } if(namespaceHierarchy.length > 0){ for(x, y; x < y; x++){ if(!channel.hasChannel(namespaceHierarchy[x])){ channel.addChannel(namespaceHierarchy[x]); } channel = channel.returnChannel(namespaceHierarchy[x]); } } return channel; }, // Pass in a channel namespace, function to be called, options, and context // to call the function in to Subscribe. It will create a channel if one // does not exist. Options can include a predicate to determine if it // should be called (based on the data published to it) and a priority // index. // sub channel에 namespace 기반으로 Subscriber 등록하기 subscribe: function(channelName, fn, options, context){ var channel = this.getChannel(channelName); options = options || {}; context = context || {}; return channel.addSubscriber(fn, options, context); }, // Pass in a channel namespace, function to be called, options, and context // to call the function in to Subscribe. It will create a channel if one // does not exist. Options can include a predicate to determine if it // should be called (based on the data published to it) and a priority // index. // 한번 호출하고 끝내기 once: function(channelName, fn, options, context){ options = options || {}; options.calls = 1; return this.subscribe(channelName, fn, options, context); }, // Returns a subscriber for a given subscriber id / named function and // channel namespace // 아이디와 명칭으로 Subscriber 얻어오기 (identifier === GUID) getSubscriber: function(identifier, channel){ return this.getChannel(channel || "").getSubscriber(identifier); }, // Remove a subscriber from a given channel namespace recursively based on // a passed-in subscriber id or named function. remove: function(channelName, identifier){ this.getChannel(channelName).removeSubscriber(identifier); }, // Publishes arbitrary data to a given channel namespace. Channels are // called recursively downwards; a post to application:chat will post to // application:chat:receive and application:chat:derp:test:beta:bananas. // Called using Mediator.publish("application:chat", [ args ]); // 퍼블리싱 publish: function(channelName){ var args = Array.prototype.slice.call(arguments, 1), channel = this.getChannel(channelName); args.push(channel); this.getChannel(channelName).publish(args); } };

// 일반적으로 사용하는 이름으로도 Aliasing // Alias some common names for easy interop Mediator.prototype.on = Mediator.prototype.subscribe; Mediator.prototype.bind = Mediator.prototype.subscribe; Mediator.prototype.emit = Mediator.prototype.publish; Mediator.prototype.trigger = Mediator.prototype.publish; Mediator.prototype.off = Mediator.prototype.remove; // Finally, expose it all. Mediator.Channel = Channel; Mediator.Subscriber = Subscriber; Mediator.version = "0.9.5"; return Mediator; })); 



사용해 보기 

  - Plunker에서 수행한 내역 보기 : http://plnkr.co/edit/k9dT9Ms7xzbx77GhVraA?p=preview

  - HTML

<!DOCTYPE html>

<html>


  <head>

    <script data-require="jquery@*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>

    <link data-require="bootstrap-css@*" data-semver="3.0.0" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />

    <link data-require="bootstrap@*" data-semver="3.0.0" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />

    <script data-require="bootstrap@*" data-semver="3.0.0" src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>

    <link rel="stylesheet" href="style.css" />

    <script src="mediator.js"></script>

    <script src="script.js"></script>

  </head>


  <body>

    <h1>Chat</h1>

    <form id="chatForm">

      <label for="fromBox">Your Name:</label>

      <input id="fromBox" type="text" />

      <br />

      <label for="toBox">Send to:</label>

      <input id="toBox" type="text" />

      <br />

      <label for="chatBox">Message:</label>

      <input id="chatBox" type="text" />

      <button type="submit">Chat</button>

    </form>

    <div id="chatResult"></div>

  </body>


</html>


  - JavaScript

// create Mediator.js

var mediator = new Mediator();


$("#chatForm").submit(function(){

    // Collect the details of the chat from our UI

    var text = $( "#chatBox" ).val(),

        from = $( "#fromBox" ).val(),

        to = $( "#toBox" ).val();

        

    // newMessage 채널로 퍼블리싱 하기 

    // Publish data from the chat to the newMessage topic

    mediator.publish( "newMessage" , { message: text, from: from, to: to } );

    return false;

});


// Append new messages as they come through

function displayChat( data ) {

    var date = new Date(),

        msg = data.from + " said \"" + data.message + "\" to " + data.to;

  

    $( "#chatResult" ).prepend("<p>" + msg + " (" + date.toLocaleTimeString() + ")</p>");

}


// Log messages

function logChat( data ) {

    if ( window.console ) {

        console.log( data );

    }

}


// newMessage 명칭으로 Subscriber 등록하기 

// Subscribe to new chat messages being submitted

// via the mediator

mediator.subscribe( "newMessage", displayChat );

mediator.subscribe( "newMessage", logChat );


  - 수행내역



<참조>

  - 원문 : Mediator Pattern

  - OODesign : Mediator Pattern

  - http://thejacklawson.com/Mediator.js/

  - 오늘의 명언 : 내가 감동하면 남도 감동할 수 있다. 그것이 SNS정신이다 

posted by 윤영식
2013. 9. 7. 17:32 Languages/JavaScript

Publish/Subscribe 를 위한 Observer 패턴을 알아보자. JavaScript은 Web의 Virtual Machine이다. JavaScript는 웹앱이다. 




개념

  - Observable

     Client에 observers를 붙였다 띄었다 하는 동작을 가진 추상 클래스 또는 인터페이스  (Publisher)

    ConcreteObservable

     오브젝트의 상태정보를 가지고 있다가 상태가 변경되면 등록된 Observers 에게 변경 정보를 알려준다 

    Observer

     정보를 알려줄 Operation을 정의한 추상 클래스 또는 인테페이스 (Subscriber)


  - 어떤 오브젝트가 오브젝트 목록을 관리하다가 어떤 상태의 변화가 감지되면 해당 오브젝트 목록들에 변경값을 Notify한다 

  - 상태 정보 변경에 관심을 갖는 오브젝트를 Observer Object라 하고 Observer Object를 register/remove 한다 

  - GoF의 정의 

"One or more observers are interested in the state of a subject and register their interest with the subject by attaching themselves. When something changes in our subject that the observer may be interested in, a notify message is sent which calls the update method in each observer. When the observer is no longer interested in the subject's state, they can simply detach themselves."



구현 

  - Observer Pattern 구현을 위해 필요한 컴포넌트 

    > Subject : observers 목록을 제어(register/remove)한다 

    > Observer : Subject로부터 상태변화를 받을 오브젝트 인터페이스를 제공한다 

    > ConcreteSubject : 상태 변경을 observers 에게 notify를 수행한다 

    > ConcreteObserver : ConcreteSubject에 Observer 인터페이스를 생성하여 등록한다 

// Object 목록 관리 Constructor Function

function ObserverList(){

this.observerList = []; } ObserverList.prototype.Add = function( obj ){ return this.observerList.push( obj ); }; ObserverList.prototype.Empty = function(){ this.observerList = []; }; ObserverList.prototype.Count = function(){ return this.observerList.length; }; ObserverList.prototype.Get = function( index ){ if( index > -1 && index < this.observerList.length ){ return this.observerList[ index ]; } }; ObserverList.prototype.Insert = function( obj, index ){ var pointer = -1; if( index === 0 ){ this.observerList.unshift( obj ); pointer = index; }else if( index === this.observerList.length ){ this.observerList.push( obj ); pointer = index; } return pointer; }; ObserverList.prototype.IndexOf = function( obj, startIndex ){ var i = startIndex, pointer = -1; while( i < this.observerList.length ){ if( this.observerList[i] === obj ){ pointer = i; } i++; } return pointer; }; ObserverList.prototype.RemoveAt = function( index ){ if( index === 0 ){ this.observerList.shift(); }else if( index === this.observerList.length -1 ){ this.observerList.pop(); } }; // Extend an object with an extension

function extend( extension, obj ){ for ( var key in extension ){ obj[key] = extension[key]; } }


  - Subject 구현, Observer에는 Update메소드를 구현하면 된다

// 1. Subject Constructor Function을 정의한다

// (Constructor Pattern의 prototype 방식 사용)

function Subject(){ this.observers = new ObserverList(); } Subject.prototype.AddObserver = function( observer ){ this.observers.Add( observer ); }; Subject.prototype.RemoveObserver = function( observer ){ this.observers.RemoveAt( this.observers.IndexOf( observer, 0 ) ); };

// Observer가 상태 정보를 받는다 
Subject.prototype.Notify = function( context ){
  var observerCount = this.observers.Count();
  for(var i=0; i < observerCount; i++){
    this.observers.Get(i).Update( context );

};



// 2. Observer 구현 

// The Observer
function Observer(){
  this.Update = function(){
    // ...
  };
}


  - ConcreteSubject, ConcreteObserver

// HTML

1) button을 클릭하면 observable checkbox를 페이지에 추가

2) checkbox가 subject로 동작한다. check가 되면 모든 checkbox에 상태변경을 알려준다(notify) 

3) 새로운 checkbox를 container에 추가한다 

<button id="addNewObserver">Add New Observer checkbox</button>
<input id="mainCheckbox" type="checkbox"/>
<div id="observersContainer"></div>

// Script 

// References to our DOM elements var controlCheckbox = document.getElementById( "mainCheckbox" ), addBtn = document.getElementById( "addNewObserver" ), container = document.getElementById( "observersContainer" ); // 3. Concrete Subject

// Subject의 notify 메소드를 호출하는 구문이 존재 // Extend the controlling checkbox with the Subject class extend( new Subject(), controlCheckbox ); // Clicking the checkbox will trigger notifications to its observers controlCheckbox["onclick"] = new Function( "controlCheckbox.Notify(controlCheckbox.checked)" ); addBtn["onclick"] = AddNewObserver;

// 4. Concrete Observer function AddNewObserver(){ // Create a new checkbox to be added var check = document.createElement( "input" ); check.type = "checkbox"; // Extend the checkbox with the Observer class extend( new Observer(), check ); // Override with custom update behaviour check.Update = function( value ){ this.checked = value; }; // Add the new observer to our list of observers // for our main subject controlCheckbox.AddObserver( check ); // Append the item to the container container.appendChild( check ); }



Publish/Subscribe

  - Publish/Subscriber와 유사하다. Observer나 Pub/Sub 사용이유는 틀린 계층간의 결합시 Loose Coupling으로 관계설정에 유용한다 

var pubsub = {};

(function(q) {

    var topics = {},
        subUid = -1;

    // Publish or broadcast events of interest
    // with a specific topic name and arguments
    // such as the data to pass along
    q.publish = function( topic, args ) {

        if ( !topics[topic] ) {
            return false;
        }

        var subscribers = topics[topic],
            len = subscribers ? subscribers.length : 0;

        while (len--) {
            subscribers[len].func( topic, args );
        }

        return this;
    };

    // Subscribe to events of interest
    // with a specific topic name and a
    // callback function, to be executed
    // when the topic/event is observed
    q.subscribe = function( topic, func ) {

        if (!topics[topic]) {
            topics[topic] = [];
        }

        var token = ( ++subUid ).toString();
        topics[topic].push({
            token: token,
            func: func
        });
        return token;
    };

    // Unsubscribe from a specific
    // topic, based on a tokenized reference
    // to the subscription
    q.unsubscribe = function( token ) {
        for ( var m in topics ) {
            if ( topics[m] ) {
                for ( var i = 0, j = topics[m].length; i < j; i++ ) {
                    if ( topics[m][i].token === token) {
                        topics[m].splice( i, 1 );
                        return token;
                    }
                }
            }
        }
        return this;
    }; 

}( pubsub ));



<참조>

  - 원문 : Observer Pattern

  - OODesign : Observer Pattern

  - PubSubJS for JavaScript

  - RadioJS for Pub/Sub

posted by 윤영식
2013. 9. 7. 17:11 Languages/JavaScript

클래스의 인스턴스를 오직 하나만 생성하는 Singleton Pattern을 알아보자. 기술은 향기와 같다. 막아도 냄새를 맡을 수 있는 것이다. 옳바른 향기를 피워보자. 




개념

  - Java에서의 싱글톤 정의 패턴 

    1) 자신을 private 멤버변수로 선언

    2) private 생성자 정의

    3) public static synchronized 형태 getInstance() 메소드 정의

    4) 자신의 변수가 null인지 아닌지 체크 

class Singleton

{

    private static Singleton instance; // 1) 

    private Singleton() // 2) 

    {

        ...

    }


    public static synchronized Singleton getInstance() // 3)

    {

        if (instance == null) // 4) 

            instance = new Singleton();

        return instance;

    }

    ...

    public void doSomething()

    {

        ... 

    }

}


  - Global Scope에 격리되고 내부 변수를 공유한다

    > Constructor Function을 정의한다 

    > Closure 객체안에서 Constructor Function을 한번만 new하는 로직을 넣고 Closure객체를 return한다 

var mySingleton = (function () {

// Instance stores a reference to the Singleton var instance; function Init() { // define Constructor Function // Singleton // Private methods and variables function privateMethod(){ console.log( "I am private" ); } var privateVariable = "Im also private"; var privateRandomNumber = Math.random(); return { // Public methods and variables publicMethod: function () { console.log( "The public can see me!" ); }, publicProperty: "I am also public", getRandomNumber: function() { return privateRandomNumber; } }; }; return { // return the Closure Object // Get the Singleton instance if one exists // or create one if it doesn't getInstance: function () {

// 생성이 안되었으면 최초에 한번만 init 한다 if ( !instance ) { instance = Init(); } return instance; } }; })();


// 잘 못된 예제 var myBadSingleton = (function () { // Instance stores a reference to the Singleton var instance; function init() { // Singleton var privateRandomNumber = Math.random(); return { getRandomNumber: function() { return privateRandomNumber; } }; }; return { // Always create a new Singleton instance getInstance: function () {

instance = init(); return instance; } }; })(); var singleA = mySingleton.getInstance(); var singleB = mySingleton.getInstance(); console.log( singleA.getRandomNumber() === singleB.getRandomNumber() ); // true var badSingleA = myBadSingleton.getInstance(); var badSingleB = myBadSingleton.getInstance(); console.log( badSingleA.getRandomNumber() !== badSingleB.getRandomNumber() ); // true



<참조>

  - 원문 : Singleton Pattern

  - OODesign : Singleton Pattern

 - 널리 Singleton을 사용해 보자

posted by 윤영식
2013. 9. 7. 16:53 Languages/JavaScript

SNS의 정신은 감동, 배려, 책임이다. 기술이 아니다, 자바스크립트를 행복하게 사용하려면 모듈단위의 개발이 되어야 한다. 모듈 패턴을 알아보자.



종류

  - 견고한 애플리케이션을 개발하기 위하여 모듈단위 개발이 필요하다 

  - 모듈을 구현하기 위한 방법

    > The Module pattern

    > Object literal notation

    > AMD modules

    > CommonJS modules

    > ECMAScript Harmony modules



Object Literals

  - curly braces {} 를 사용한다 

  - new 키워드가 필요없다 

  - 오브젝트에 프로퍼티의 동적 추가가 가능하다. 예) myObjectLiteral.sencondKey = value; 

var myObjectLiteral = {

  variableKey: variableValue,

  functionKey: function() {...},

  someObject: {...}

}



Module Pattern

  - class처럼 private, public를 캡슐화 하여 정의하는 방법이다 

  - 별도 name space 사용으로 global scope의 오염을 방지한다 

  - ClosureIIFE (Immediately Invoked Function Expression) 을 사용한다 

var testModule = (function() {

  var counter = 0;

  return {  // Closure

    incrementCounter: function() {

      return counter++;

    },

    resetCounter: function() {

      console.log('counter is', counter);

      counter = 0;

    }

  };

})(); // IIFE


testModule.incrementCounter();

testModule.resetCounter();

// output : counter is 1

  - 예제에서 counter는 global scope에서 숨겨져이다. 즉, private variable같다, counter 접근은 두개의 method로만 가능하다. 물론 function도 private 으로 정의가능하다 

var myNamespace = (function () {

  var myPrivateVar, myPrivateMethod;

  // A private counter variable
  myPrivateVar = 0;

  // A private function which logs any arguments
  myPrivateMethod = function( foo ) {
      console.log( foo );
  };

  return {

    // A public variable
    myPublicVar: "foo",

    // A public function utilizing privates
    myPublicFunction: function( bar ) {

      // Increment our private counter
      myPrivateVar++;

      // Call our private method using bar
      myPrivateMethod( bar );

    }
  };
 

})();



Module Pattern 변형 

  - Import mixins : global scope의 변수를 module에 import 하는 방법

    jQuery 와 Underscore 객체를 익명함수의 파라미터로 받아서 import 한다

// Global module
var myModule = (function ( jQ, _ ) {
  
    function privateMethod1(){
        jQ(".container").html("test");
    }

    function privateMethod2(){
      console.log( _.min([10, 5, 100, 2, 1000]) );
    }
    
    return{
        publicMethod: function(){
            privateMethod1();                
        }            
    };
   
// Pull in jQuery and Underscore
}( jQuery, _ ));
 

myModule.publicMethod();  

  

  - Export : global module 만들기 

    var module = {} 객체를 만들어서 return 하여 export 한다 

// Global module
var myModule = (function () {

    // Module object 
  var module = {},
    privateVariable = "Hello World";
  
  function privateMethod() {
    // ...
  }

  module.publicProperty = "Foobar";
  module.publicMethod = function () {
    console.log( privateVariable );
  };
  
  return module;
 

}());



<참조>

  - 원문 : 모듈패턴

posted by 윤영식

오브젝트간의 이벤트 전달이 필요할 경우 loose coupling 을 유지하면서 커뮤니케이션이 가능토록 해주는 중재자 패턴에 대해 알아보자 



1) 중재자 패턴

  - 하나의 오브젝트 상태 변경시 다른 오브젝트가 해당 변경 정보를 알아야 할 경우

  - 별도의 중재자를 두어서 해당 중재자가 한 오브젝트의 변화에 대해 관심을 가지고 있는 오브젝트들한테 변경 정보를 전달한다

  

    + Mediator : Colleague 오브젝트와 통신할 수 있는 인터페이스를 정의한다 

    + ConcreteMediator : ConcreteColleagues 오브젝트들의 레퍼런스를 가지고 있다. 정보를 전달해 주는 역할을 수행

    + Colleague classes : Mediator 오브젝트의 레퍼런스를 가지고 있다가 다른 Colleague와 통신하길 원하면  Mediator를 통해서 통신을 한다 


  - 사용예

    + GUI Libraries : GUI 컴포넌트에서 하나가 선택되면 다른 것들이 enable/disable 되는 경우 

    + Chatting Application

       Chatroom : Mediator 로써 참석자들끼리 대화를 중재 인터페이스 정의

       ChatroomImpl : ConcreteMediator 한 참가자가 메세지 보내면 다른 참가들에게 메세지 전송하는 역할을 구현

       Participant : Colleague 참가자 인터페이스 정의

       HumanParticipant, Bot : ConcreteColleague 는 사람이 될 수도 bot이 될 수도 있다. Mediator 레퍼런스를 참조한다다



2) 구현상의 고려사항

  - Mediator 인터페이스정의는 Colleague 들이 여러개의 Mediator가 필요할 경우이다. 한개의 Mediator만 필요하다면 굳이 인터페이스 정의는 필요없다

  - Colleague의 상태가 변하면 정보를 Mediator에 전달하고 해당 정보에 관심이 있는 Colleague에 정보를 전달해 주는 Observer 패턴과 유사하다

  - 정보가 많을 경우 Asynch를 고려한다면 Message Queue 가 필요할 수 있다 

  - Colleague가 많아지면 Mediator 구현체가 복잡해 질 수 있다. 



3) Mediator.js 분석

  - 사이트 : https://github.com/ajacksified/Mediator.js

  - 중재자 패턴을 이용하여 WebSocket, AJAX call, DOM 이벤트를 쉽게 다루고 테스트 할 수 있도록 한다 

  - 채널(Channel)이 중재자가 되어서 Colleague들에게 관심 정보를 전파한다 

  - 설치하기 

$ npm install mediator-js

npm http GET https://registry.npmjs.org/mediator-js

npm http 200 https://registry.npmjs.org/mediator-js/-/mediator-js-0.9.2.tgz

mediator-js@0.9.2 node_modules/mediator-js


  - Node.js에서 사용하기 

////////////////////

// md_node.js 파일

var Mediator = require("mediator-js").Mediator,

    mediator = new Mediator();


mediator.subscribe("wat", function(){ console.log(arguments); });

mediator.publish("wat", 7, "hi dowon", { one: 1 }); 


////////////////////

// 결과 (wat이 채널) 

$ node md_node.js

{ '0': 7,

  '1': 'hi dowon',

  '2': { one: 1 },

  '3':

   { namespace: 'wat',

     _subscribers: [ [Object] ],

     _channels: [],

     _parent:

      { namespace: '',

        _subscribers: [],

        _channels: [Object],

        _parent: undefined,

        stopped: false },

     stopped: false } }


  - 브라우져에서 AMD로 사용하기 

     + require.js 와 mediator.js 파일은 md_browser.html 파일 위치에서 js/ 폴더 밑에 존재함

////////////////////

// md_browser.html

<!doctype html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Test</title>

</head>

<body>


<script src="js/require.js"></script>

<script>

        // base url을 반드시 주어야 한다 

requirejs.config({

   baseUrl: './js/'

});


        // 주의) mediator.js 에서 define을 Mediator 로 M을 대문자로 정의하였다 

require(['Mediator'], function(mediator) {

 mediator.subscribe("wat", function(){ console.log(arguments); });

 mediator.publish("wat", 7, "hi", { one: 1 });

});

</script>


</body>

</html>


   + subscribe/publish API

mediator.subscribe(channel, callback, <options>, <context>);

mediator.publish(channel, <data, data, ... >)

mediator.remove(channel, <identifier>)


   + on/bind 는 subscribe 의 alias 이고, trigger/emit 은 publish 의 alias, off 는 remove의 alias

   + Channel의 Namespace 로 : 를 사용한다 예) application:chat



<참조>

  - http://www.oodesign.com/mediator-pattern.html

  - 3실 청년 설명

  - 예제를 가지고 시퀀스 다이어그램을 통해서 설명

  - 다이어그램 보기 : UML 기초

  - 자바스크립트의 Mediator 패턴 설명

  - 자바스크립트 디자인패턴 - 설명

posted by 윤영식
prev 1 next