그래서 동료가 저에게 게시 / 구독 패턴 (JS / jQuery에서)을 소개했지만 ‘일반적인’JavaScript / jQuery보다이 패턴을 사용 하는 이유 를 파악하는 데 어려움을 겪고 있습니다.
예를 들어 이전에는 다음 코드가있었습니다.
$container.on('click', '.remove_order', function(event) {
event.preventDefault();
var orders = $(this).parents('form:first').find('div.order');
if (orders.length > 2) {
orders.last().remove();
}
});
예를 들어, 대신 이렇게하는 것의 장점을 볼 수 있습니다.
removeOrder = function(orders) {
if (orders.length > 2) {
orders.last().remove();
}
}
$container.on('click', '.remove_order', function(event) {
event.preventDefault();
removeOrder($(this).parents('form:first').find('div.order'));
});
removeOrder
다양한 이벤트 등에 대해 기능을 재사용 할 수있는 기능을 도입하기 때문입니다 .
그러나 게시 / 구독 패턴을 구현하고 동일한 작업을 수행하는 경우 다음 길이로 이동하는 이유는 무엇입니까? (참고로, jQuery tiny pub / sub 사용 )
removeOrder = function(e, orders) {
if (orders.length > 2) {
orders.last().remove();
}
}
$.subscribe('iquery/action/remove-order', removeOrder);
$container.on('click', '.remove_order', function(event) {
event.preventDefault();
$.publish('iquery/action/remove-order', $(this).parents('form:first').find('div.order'));
});
나는 패턴에 대해 확실히 읽었지만 이것이 왜 필요한지 상상할 수 없습니다. 이 패턴을 구현 하는 방법 을 설명 하는 튜토리얼 은 저의 기본 예제 만 다룹니다.
pub / sub의 유용성은 더 복잡한 응용 프로그램에서 분명해질 것이라고 생각하지만 하나는 상상할 수 없습니다. 요점을 완전히 놓치고있는 것이 두렵습니다. 하지만 요점이 있으면 알고 싶습니다!
이 패턴이 유리한 이유와 상황에 대해 간결하게 설명해 주 시겠습니까? 위의 예제와 같은 코드 스 니펫에 pub / sub 패턴을 사용할 가치가 있습니까?
답변
지난 몇 년 동안 매우 현대적인 JavaScript의 MV * (MVC / MVP / MVVM) 패턴과 함께 사용되는 느슨한 결합과 단일 책임에 관한 것입니다.
느슨한 결합 은 시스템의 각 구성 요소가 자신의 책임을 알고 다른 구성 요소에 대해서는 신경 쓰지 않는 객체 지향 원칙입니다 (또는 최소한 가능한 한 신경 쓰지 않으려 고합니다). 느슨한 결합은 다른 모듈을 쉽게 재사용 할 수 있기 때문에 좋은 것입니다. 다른 모듈의 인터페이스와 연결되지 않습니다. 발행 / 구독을 사용하면 큰 문제가 아닌 발행 / 구독 인터페이스와 결합됩니다. 두 가지 방법 만 있습니다. 따라서 다른 프로젝트에서 모듈을 재사용하기로 결정한 경우 복사하여 붙여 넣기 만하면 작동 할 수 있거나 적어도 작동하도록 만드는 데 많은 노력이 필요하지 않습니다.
느슨한 결합에 대해 이야기 할 때 우려 의 분리를 언급해야합니다.. MV * 아키텍처 패턴을 사용하여 애플리케이션을 빌드하는 경우 항상 모델과 뷰가 있습니다. 모델은 애플리케이션의 비즈니스 부분입니다. 다른 응용 프로그램에서 재사용 할 수 있으므로 일반적으로 다른 응용 프로그램에서 다른보기를 갖기 때문에 표시하려는 단일 응용 프로그램의보기와 결합하는 것은 좋지 않습니다. 따라서 Model-View 통신을 위해 게시 / 구독을 사용하는 것이 좋습니다. 모델이 변경되면 이벤트를 게시하고 뷰는 이벤트를 포착하고 자체적으로 업데이트합니다. 게시 / 구독으로 인한 오버 헤드가 없으므로 디커플링에 도움이됩니다. 같은 방식으로 컨트롤러에 애플리케이션 로직을 유지하고 (MVVM, MVP는 정확히 컨트롤러가 아님) 가능한 한 간단하게보기를 유지할 수 있습니다. 뷰가 변경되면 (예를 들어 사용자가 무언가를 클릭) 새 이벤트를 게시하고 컨트롤러가이를 포착하여 수행 할 작업을 결정합니다. 당신이 익숙한 경우MVC 패턴 또는 Microsoft 기술 (WPF / Silverlight)의 MVVM 을 사용하면 게시 / 구독을 Observer 패턴 과 같이 생각할 수 있습니다 . 이 접근 방식은 Backbone.js, Knockout.js (MVVM)와 같은 프레임 워크에서 사용됩니다.
다음은 예입니다.
//Model
function Book(name, isbn) {
this.name = name;
this.isbn = isbn;
}
function BookCollection(books) {
this.books = books;
}
BookCollection.prototype.addBook = function (book) {
this.books.push(book);
$.publish('book-added', book);
return book;
}
BookCollection.prototype.removeBook = function (book) {
var removed;
if (typeof book === 'number') {
removed = this.books.splice(book, 1);
}
for (var i = 0; i < this.books.length; i += 1) {
if (this.books[i] === book) {
removed = this.books.splice(i, 1);
}
}
$.publish('book-removed', removed);
return removed;
}
//View
var BookListView = (function () {
function removeBook(book) {
$('#' + book.isbn).remove();
}
function addBook(book) {
$('#bookList').append('<div id="' + book.isbn + '">' + book.name + '</div>');
}
return {
init: function () {
$.subscribe('book-removed', removeBook);
$.subscribe('book-aded', addBook);
}
}
}());
다른 예시. MV * 접근 방식이 마음에 들지 않으면 약간 다른 것을 사용할 수 있습니다 (다음에 설명 할 것과 마지막에 언급 한 것 사이에 교차점이 있습니다). 애플리케이션을 다른 모듈로 구성하기 만하면됩니다. 예를 들어 Twitter를보십시오.
인터페이스를 보면 단순히 다른 상자가 있습니다. 각 상자를 다른 모듈로 생각할 수 있습니다. 예를 들어 트윗을 게시 할 수 있습니다. 이 작업을 수행하려면 몇 가지 모듈을 업데이트해야합니다. 먼저 프로필 데이터 (왼쪽 상단 상자)를 업데이트해야하지만 타임 라인도 업데이트해야합니다. 물론 두 모듈에 대한 참조를 유지하고 공개 인터페이스를 사용하여 개별적으로 업데이트 할 수 있지만 이벤트를 게시하는 것이 더 쉽고 (더 좋습니다). 이렇게하면 결합이 느슨해져 애플리케이션을 더 쉽게 수정할 수 있습니다. 새 트윗에 의존하는 새 모듈을 개발하는 경우 “publish-tweet”이벤트를 구독하고 처리 할 수 있습니다. 이 접근 방식은 매우 유용하며 애플리케이션을 매우 분리 할 수 있습니다. 모듈을 매우 쉽게 재사용 할 수 있습니다.
다음은 마지막 접근 방식의 기본 예입니다 (이는 원본 트위터 코드가 아니라 제가 작성한 샘플 일뿐입니다).
var Twitter.Timeline = (function () {
var tweets = [];
function publishTweet(tweet) {
tweets.push(tweet);
//publishing the tweet
};
return {
init: function () {
$.subscribe('tweet-posted', function (data) {
publishTweet(data);
});
}
};
}());
var Twitter.TweetPoster = (function () {
return {
init: function () {
$('#postTweet').bind('click', function () {
var tweet = $('#tweetInput').val();
$.publish('tweet-posted', tweet);
});
}
};
}());
이 접근 방식에 대해 Nicholas Zakas 의 훌륭한 강연이 있습니다. MV * 접근 방식의 경우 내가 아는 최고의 기사와 책은 Addy Osmani 가 출판했습니다 .
단점 : 발행 / 구독의 과도한 사용에주의해야합니다. 수백 개의 이벤트가있는 경우 모든 이벤트를 관리하는 것이 매우 혼란 스러울 수 있습니다. 네임 스페이스를 사용하지 않거나 올바른 방식으로 사용하지 않는 경우에도 충돌이 발생할 수 있습니다. 게시 / 구독과 매우 유사한 Mediator의 고급 구현은 여기 https://github.com/ajacksified/Mediator.js 에서 찾을 수 있습니다 . 물론 중단 될 수있는 이벤트 “버블 링”과 같은 네임 스페이스 및 기능이 있습니다. 게시 / 구독의 또 다른 단점은 하드 단위 테스트입니다. 모듈에서 다른 기능을 분리하고 독립적으로 테스트하기가 어려울 수 있습니다.
답변
주요 목표는 코드 간의 결합을 줄이는 것입니다. 다소 이벤트 기반 사고 방식이지만 “이벤트”는 특정 대상에 묶여 있지 않습니다.
JavaScript와 비슷한 의사 코드로 아래에 큰 예제를 작성하겠습니다.
Radio 클래스와 Relay 클래스가 있다고 가정 해 보겠습니다.
class Relay {
function RelaySignal(signal) {
//do something we don't care about right now
}
}
class Radio {
function ReceiveSignal(signal) {
//how do I send this signal to other relays?
}
}
라디오가 신호를 수신 할 때마다 우리는 여러 릴레이가 어떤 방식 으로든 메시지를 릴레이하기를 원합니다. 릴레이의 수와 유형은 다를 수 있습니다. 다음과 같이 할 수 있습니다.
class Radio {
var relayList = [];
function AddRelay(relay) {
relayList.add(relay);
}
function ReceiveSignal(signal) {
for(relay in relayList) {
relay.Relay(signal);
}
}
}
이것은 잘 작동합니다. 그러나 이제 다른 구성 요소가 Radio 클래스가 수신하는 신호, 즉 Speakers의 일부를 취하기를 원한다고 상상해보십시오.
(비유가 최고 수준이 아니라면 죄송합니다 …)
class Speakers {
function PlaySignal(signal) {
//do something with the signal to create sounds
}
}
패턴을 다시 반복 할 수 있습니다.
class Radio {
var relayList = [];
var speakerList = [];
function AddRelay(relay) {
relayList.add(relay);
}
function AddSpeaker(speaker) {
speakerList.add(speaker)
}
function ReceiveSignal(signal) {
for(relay in relayList) {
relay.Relay(signal);
}
for(speaker in speakerList) {
speaker.PlaySignal(signal);
}
}
}
“SignalListener”와 같은 인터페이스를 만들어서 더 좋게 만들 수 있습니다. 따라서 Radio 클래스에 하나의 목록 만 필요하고 신호를 듣고 자하는 객체에 대해 항상 동일한 함수를 호출 할 수 있습니다. 그러나 그것은 우리가 결정한 인터페이스 / 기본 클래스 / 등과 Radio 클래스 사이에 여전히 결합을 생성합니다. 기본적으로 Radio, Signal 또는 Relay 클래스 중 하나를 변경할 때마다 다른 두 클래스에 어떻게 영향을 미칠 수 있는지 생각해야합니다.
이제 다른 것을 시도해 봅시다. RadioMast라는 네 번째 클래스를 만들어 보겠습니다.
class RadioMast {
var receivers = [];
//this is the "subscribe"
function RegisterReceivers(signaltype, receiverMethod) {
//if no list for this type of signal exits, create it
if(receivers[signaltype] == null) {
receivers[signaltype] = [];
}
//add a subscriber to this signal type
receivers[signaltype].add(receiverMethod);
}
//this is the "publish"
function Broadcast(signaltype, signal) {
//loop through all receivers for this type of signal
//and call them with the signal
for(receiverMethod in receivers[signaltype]) {
receiverMethod(signal);
}
}
}
이제 우리는 우리가 알고 있는 패턴 을 가지고 있으며, 다음과 같은 한 어떤 수와 유형의 클래스에도 사용할 수 있습니다.
- RadioMast (모든 메시지 전달을 처리하는 클래스)를 알고 있습니다.
- 메시지 보내기 / 받기를위한 메서드 서명을 알고 있습니다.
그래서 우리는 Radio 클래스를 최종적이고 간단한 형태로 변경합니다.
class Radio {
function ReceiveSignal(signal) {
RadioMast.Broadcast("specialradiosignal", signal);
}
}
그리고 우리는 이러한 유형의 신호에 대해 RadioMast의 수신기 목록에 스피커와 릴레이를 추가합니다.
RadioMast.RegisterReceivers("specialradiosignal", speakers.PlaySignal);
RadioMast.RegisterReceivers("specialradiosignal", relay.RelaySignal);
이제 Speakers 및 Relay 클래스는 신호를 수신 할 수있는 메서드가 있고 게시자 인 Radio 클래스가 신호를 게시하는 RadioMast를 알고 있다는 점을 제외하고는 아무것도 알지 못합니다. 이것이 발행 / 구독과 같은 메시지 전달 시스템을 사용하는 지점입니다.
답변
다른 답변은 패턴이 어떻게 작동하는지 보여주는 데 큰 역할을했습니다. 나는 최근에이 패턴으로 작업을 해왔 기 때문에 ” 이전 방식에 무엇이 잘못 되었는가? ” 라는 묵시적인 질문에 답하고 싶었습니다 .
우리가 경제 게시판에 가입했다고 상상해보십시오. 이 게시판에는 ” 다우 존스를 200 포인트 낮추기 “라는 제목이 게시 되어 있습니다. 그것은 이상하고 다소 무책임한 메시지가 될 것입니다. 그러나 ” Enron이 오늘 아침 11 장 파산 보호를 위해 신청했습니다 “라고 게시 했다면 이것은 더 유용한 메시지입니다. 이 메시지로 인해 Dow Jones가 200 포인트 하락할 수 있지만 이는 또 다른 문제입니다.
명령을 보내는 것과 방금 일어난 일에 대해 조언하는 것에는 차이가 있습니다. 이를 염두에두고 지금은 핸들러를 무시하고 pub / sub 패턴의 원래 버전을 사용합니다.
$.subscribe('iquery/action/remove-order', removeOrder);
$container.on('click', '.remove_order', function(event) {
event.preventDefault();
$.publish('iquery/action/remove-order', $(this).parents('form:first').find('div.order'));
});
사용자 행동 (클릭)과 시스템 반응 (주문 제거) 사이에는 이미 암시 된 강력한 결합이 있습니다. 귀하의 예에서 효과적으로 행동은 명령을 내리고 있습니다. 이 버전을 고려하십시오.
$.subscribe('iquery/action/remove-order-requested', handleRemoveOrderRequest);
$container.on('click', '.remove_order', function(event) {
event.preventDefault();
$.publish('iquery/action/remove-order-requested', $(this).parents('form:first').find('div.order'));
});
이제 핸들러는 발생한 관심있는 일에 응답하지만 주문을 제거 할 의무는 없습니다. 실제로 핸들러는 주문 제거와 직접적으로 관련되지는 않지만 호출 작업과 관련이있을 수있는 모든 종류의 작업을 수행 할 수 있습니다. 예를 들면 :
handleRemoveOrderRequest = function(e, orders) {
logAction(e, "remove order requested");
if( !isUserLoggedIn()) {
adviseUser("You need to be logged in to remove orders");
} else if (isOkToRemoveOrders(orders)) {
orders.last().remove();
adviseUser("Your last order has been removed");
logAction(e, "order removed OK");
} else {
adviseUser("Your order was not removed");
logAction(e, "order not removed");
}
remindUserToFloss();
increaseProgrammerBrowniePoints();
//etc...
}
명령과 알림을 구분하는 것은이 패턴 인 IMO를 사용하는 데 유용한 구분입니다.
답변
메서드 / 함수 호출을 하드 코딩 할 필요가 없도록 누가 수신하는지 신경 쓰지 않고 이벤트를 게시하면됩니다. 이렇게하면 게시자가 구독자로부터 독립되어 애플리케이션의 서로 다른 두 부분 간의 종속성 (또는 원하는 용어에 관계없이 결합)이 줄어 듭니다.
wikipedia에서 언급 한 커플 링의 단점은 다음과 같습니다.
밀접하게 결합 된 시스템은 종종 단점으로 간주되는 다음과 같은 발달 특성을 나타내는 경향이 있습니다.
- 한 모듈의 변경은 일반적으로 다른 모듈의 변경으로 인한 파급 효과를 강제합니다.
- 모듈 조립은 모듈 간 종속성 증가로 인해 더 많은 노력 및 / 또는 시간이 필요할 수 있습니다.
- 특정 모듈은 종속 모듈을 포함해야하므로 재사용 및 / 또는 테스트가 더 어려울 수 있습니다.
비즈니스 데이터를 캡슐화하는 객체와 같은 것을 고려하십시오. 연령이 설정 될 때마다 페이지를 업데이트하는 하드 코딩 된 메서드 호출이 있습니다.
var person = {
name: "John",
age: 23,
setAge: function( age ) {
this.age = age;
showAge( age );
}
};
//Different module
function showAge( age ) {
$("#age").text( age );
}
이제 showAge
함수를 포함하지 않고 사람 객체를 테스트 할 수 없습니다 . 또한 다른 GUI 모듈에서도 나이를 표시해야하는 경우 해당 메서드 호출을에서 하드 코딩해야
.setAge
하며 이제 person 개체에 관련되지 않은 2 개의 모듈에 대한 종속성이 있습니다. 또한 이러한 호출이 수행되고 동일한 파일에 있지 않을 때 유지 관리하기가 어렵습니다.
물론 동일한 모듈 내에서 직접 메서드를 호출 할 수 있습니다. 그러나 비즈니스 데이터와 피상적 인 GUI 동작은 합리적인 표준에 따라 동일한 모듈에 상주해서는 안됩니다.
답변
PubSub 구현은 일반적으로 다음 위치에서 볼 수 있습니다.
- 이벤트 버스의 도움으로 통신하는 여러 포틀릿이있는 구현과 같은 포틀릿이 있습니다. 이것은 aync 아키텍처에서 생성하는 데 도움이됩니다.
- 긴밀한 결합으로 손상된 시스템에서 pubsub는 다양한 모듈 간의 통신을 돕는 메커니즘입니다.
예제 코드-
var pubSub = {};
(function(q) {
var messages = [];
q.subscribe = function(message, fn) {
if (!messages[message]) {
messages[message] = [];
}
messages[message].push(fn);
}
q.publish = function(message) {
/* fetch all the subscribers and execute*/
if (!messages[message]) {
return false;
} else {
for (var message in messages) {
for (var idx = 0; idx < messages[message].length; idx++) {
if (messages[message][idx])
messages[message][idx]();
}
}
}
}
})(pubSub);
pubSub.subscribe("event-A", function() {
console.log('this is A');
});
pubSub.subscribe("event-A", function() {
console.log('booyeah A');
});
pubSub.publish("event-A"); //executes the methods.
답변
“발행 / 구독의 다양한면” 이라는 논문 은 좋은 읽기이며 그들이 강조하는 한 가지는 세 가지 “차원”에서 분리하는 것입니다. 여기에 내 조잡한 요약이 있지만 논문도 참조하십시오.
- 공간 분리.상호 작용하는 당사자는 서로를 알 필요가 없습니다. 게시자는 누가 듣고 있는지, 얼마나 많은 사람이 듣고 있는지 또는 이벤트에서 무엇을하고 있는지 알지 못합니다. 구독자는 이러한 이벤트를 누가 제작하는지, 얼마나 많은 제작자가 있는지 등을 모릅니다.
- 시간 분리. 상호 작용하는 당사자는 상호 작용 중에 동시에 활성화 될 필요가 없습니다. 예를 들어 게시자가 일부 이벤트를 게시하는 동안 구독자의 연결이 끊어 질 수 있지만 온라인 상태가되면 이에 반응 할 수 있습니다.
- 동기화 디커플링. 게시자는 이벤트를 생성하는 동안 차단되지 않으며 구독자는 구독 한 이벤트가 도착할 때마다 콜백을 통해 비동기 적으로 알림을받을 수 있습니다.
답변
간단한 대답
원래 질문은 간단한 대답을 찾고있었습니다. 여기 내 시도가 있습니다.
자바 스크립트는 코드 객체가 자체 이벤트를 생성하는 메커니즘을 제공하지 않습니다. 따라서 일종의 이벤트 메커니즘이 필요합니다. 게시 / 구독 패턴은 이러한 요구에 응답 할 것이며 자신의 요구에 가장 적합한 메커니즘을 선택하는 것은 사용자에게 달려 있습니다.
이제 pub / sub 패턴이 필요하다는 것을 알 수 있습니다. 그러면 pub / sub 이벤트를 처리하는 방법과 다르게 DOM 이벤트를 처리해야합니까? 복잡성을 줄이고 SoC (관심 분리)와 같은 기타 개념을 줄이기 위해 모든 것이 균일 한 이점을 확인할 수 있습니다.
따라서 역설적이게도 코드가 많을수록 우려 사항이 더 잘 분리되어 매우 복잡한 웹 페이지까지 확장됩니다.
나는 누군가가 자세히 설명하지 않고 충분히 좋은 토론이라고 생각하기를 바랍니다.