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정신이다
'Languages > JavaScript' 카테고리의 다른 글
[JavaScript] Design Pattern - Prototype Pattern (0) | 2013.09.28 |
---|---|
[JavaScript] Design Pattern - Observer Pattern (0) | 2013.09.07 |
[JavaScript] Design Pattern - Singleton Pattern (0) | 2013.09.07 |
[JavaScript] Design Pattern - Module Pattern (0) | 2013.09.07 |
[JavaScript] Design Pattern - Constructor Pattern (0) | 2013.09.07 |