programing

게시/구독 패턴(JS/jQuery)을 사용하는 이유는 무엇입니까?

css3 2023. 9. 17. 13:26

게시/구독 패턴(JS/jQuery)을 사용하는 이유는 무엇입니까?

그래서 동료가 (JS/jQuery에서) 게시/구독 패턴을 소개해 주었는데, 왜 '정상적인' 자바스크립트/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다양한 이벤트에 대한 기능성 등.

그러나 게시/구독 패턴을 구현하고 동일한 작업을 수행할 경우 다음과 같은 길이로 이동하는 이유는 무엇입니까? (FYI, 저는 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 패턴을 사용할 가치가 있습니까?

이것은 느슨한 결합과 단일 책임에 관한 것으로, 지난 몇 년간 매우 현대적인 자바스크립트의 MV*(MVC/MVP/MVVM) 패턴과 함께 사용됩니다.

느슨한 결합은 시스템의 각 구성 요소가 해당 시스템의 책임을 알고 다른 구성 요소에 대해 신경 쓰지 않는 객체 지향 원리입니다(또는 최소한 가능한 한 해당 구성 요소에 대해 신경 쓰지 않으려 함).여러 모듈을 쉽게 재사용할 수 있기 때문에 느슨한 결합이 좋은 것입니다.다른 모듈의 인터페이스와 연결되어 있지 않습니다.게시/구독을 사용하면 게시/구독 인터페이스와 연결할 수 있는데, 이는 큰 문제가 아닙니다. 단 두 가지 방법입니다.따라서 다른 프로젝트에서 모듈을 재사용하기로 결정한 경우에는 모듈을 복사하여 붙여넣기만 하면 됩니다. 그러면 모듈이 작동하거나 최소한 작동하는 데는 많은 노력이 필요하지 않습니다.

느슨한 결합에 대해 이야기할 때, 우리는 우려의 분리를 언급해야 합니다.MV* 아키텍처 패턴을 사용하여 애플리케이션을 구축하는 경우에는 항상 모델과 보기가 있습니다.모델은 애플리케이션의 비즈니스 부분입니다.다른 응용프로그램에서 재사용할 수 있으므로 표시할 단일 응용프로그램의 보기와 결합하는 것은 좋지 않습니다. 일반적으로 다른 응용프로그램에서는 보기가 다르기 때문입니다.따라서 모델-뷰 커뮤니케이션을 위해 게시/구독을 사용하는 것이 좋습니다.모델이 변경되면 이벤트를 게시합니다. 보기는 이벤트를 캡처하고 자체를 업데이트합니다.게시/구독으로 인한 오버헤드가 없으므로 디커플링에 도움이 됩니다.마찬가지로 애플리케이션 로직을 컨트롤러(MVVM, MVP는 정확히 컨트롤러가 아님)에 유지하고 보기를 최대한 단순하게 유지할 수 있습니다.보기가 변경되면(또는 사용자가 어떤 것을 클릭하는 등) 새 이벤트를 게시하기만 하면 컨트롤러가 해당 이벤트를 잡고 수행할 작업을 결정합니다.MVC 패턴이나 Microsoft 기술(WPF/Silverlight)의 MVVM에 익숙하다면 Observer 패턴과 같은 게시/구독을 생각해 볼 수 있습니다.이 접근 방식은 Backbone.js, Knout.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 Modules

인터페이스를 보면 상자가 다를 뿐입니다.각각의 박스를 다른 모듈로 생각하시면 됩니다.예를 들어 트윗을 올릴 수 있습니다.이 작업을 수행하려면 몇 가지 모듈을 업데이트해야 합니다.먼저 프로필 데이터(왼쪽 위 상자)를 업데이트해야 하지만 타임라인도 업데이트해야 합니다.물론 두 모듈에 대한 참조를 유지하고 공용 인터페이스를 사용하여 개별적으로 업데이트할 수 있지만 이벤트를 게시하는 것이 더 쉽고 좋습니다.이렇게 하면 느슨한 결합으로 인해 응용프로그램을 쉽게 수정할 수 있습니다.새로운 트윗에 의존하는 새로운 모듈을 개발한다면 그냥 "퍼블리시-트윗" 이벤트에 가입해서 처리하면 됩니다.이 접근 방식은 매우 유용하며 응용프로그램을 매우 분리할 수 있습니다.모듈을 매우 쉽게 재사용할 수 있습니다.

다음은 마지막 접근 방법의 기본적인 예입니다. (이것은 원래 트위터 코드가 아닙니다. 단지 제가 샘플로 만든 것입니다.)

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);
           });
       }
   };
}());

이 접근법을 위해 니콜라스 자카스의 훌륭한 강연이 있습니다.MV*의 경우, 제가 아는 최고의 기사와 책은 아디 오스마니가 출판합니다.

단점:과도한 게시/구독 사용에 주의하셔야 합니다.수백 개의 이벤트가 있는 경우 해당 이벤트를 모두 관리하기가 매우 혼란스러울 수 있습니다.네임스페이스를 사용하지 않거나 올바른 방식으로 사용하지 않으면 충돌이 발생할 수도 있습니다.게시/게시판과 유사한 Mediator의 고급 구현은 여기 https://github.com/ajacksified/Mediator.js 에서 확인할 수 있습니다.이름 공간이 있고 이벤트 "버블"과 같은 특징이 있습니다. 물론 중단될 수도 있습니다.게시/구독의 또 다른 단점은 하드 유닛 테스트가 어렵다는 것입니다. 모듈에서 서로 다른 기능을 분리하여 독립적으로 테스트하는 것이 어려워질 수 있습니다.

주요 목표는 코드 간의 결합을 줄이는 것입니다.다소 사건에 기반한 사고 방식이지만, '사건'은 특정 대상에 얽매이지 않습니다.

자바스크립트와 조금 비슷한 유사 코드에 아래의 큰 예시를 적겠습니다.

라디오 클래스와 릴레이 클래스가 있다고 가정해 보겠습니다.

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);
        }
    }

}

이 정도면 잘 됩니다.하지만 이제 우리는 라디오 클래스가 수신하는 신호의 일부를 다른 구성 요소가 차지하기를 원한다고 상상해 보십시오. 즉, 스피커:

(유사한 것들이 최고 수준이 아니라면 죄송합니다...)

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"와 같은 인터페이스를 만들어서 이것을 훨씬 더 좋게 만들 수 있습니다. 그래서 우리는 라디오 클래스에서 오직 하나의 목록만 필요하고, 우리가 신호를 듣고 싶어하는 어떤 물체에 대해서도 항상 같은 기능을 호출할 수 있습니다.그러나 이것은 여전히 우리가 결정하는 어떤 인터페이스/베이스 클래스/기타와 무선 클래스 사이에 연결을 만들어 냅니다.기본적으로 라디오, 신호 또는 릴레이 클래스 중 하나를 변경할 때마다 다른 두 클래스에 어떤 영향을 미칠 수 있는지 생각해야 합니다.

이제 다른 것을 시도해 보겠습니다.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(모든 메시지 전달을 처리하는 클래스)를 알고 있습니다.
  • 메시지 보내기/받기 방법 서명에 대해 알고 있음

따라서 라디오 클래스를 최종적이고 간단한 형태로 변경합니다.

class Radio {
    function ReceiveSignal(signal) {
        RadioMast.Broadcast("specialradiosignal", signal);
    }
}

스피커와 릴레이를 RadioMast의 수신기 목록에 추가합니다.

RadioMast.RegisterReceivers("specialradiosignal", speakers.PlaySignal);
RadioMast.RegisterReceivers("specialradiosignal", relay.RelaySignal);

스피커와 릴레이 클래스는 신호를 수신할 수 있는 방법을 가지고 있다는 것 외에는 아무것도 알지 못합니다. 그리고 Radio 클래스는 게시자로서 RadioMast를 알고 있습니다.이것이 게시/구독과 같은 메시지 전달 시스템을 사용하는 포인트입니다.

다른 답변들은 패턴이 어떻게 작동하는지 보여주는 데 큰 역할을 했습니다.저는 최근에 이 패턴을 연구하면서 '옛 방식이 왜 그럴까'라는 함축된 질문을 하고 싶었습니다. 그리고 그것이 제 생각의 변화를 수반한다는 것을 알게 되었습니다.

우리가 경제 회보를 구독했다고 상상해 보세요.이 게시판에는 "다우존스 지수 200포인트 하락"이라는 제목이 게재되어 있습니다.그것은 이상하고 다소 무책임한 메시지일 것입니다.그러나 "오늘 아침 엔론이 파산보호 신청을 했다"는 기사를 냈다면, 이것이 더 유용한 메시지입니다.이 메시지로 인해 다우존스 지수가 200포인트 하락할 도 있지만, 이는 다른 문제입니다.

명령을 보내는 것과 방금 일어난 일에 대해 조언하는 것에는 차이가 있습니다.이 점을 염두에 두고 펍/서브 패턴의 원래 버전을 선택하고 핸들러는 일단 무시합니다.

$.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.

메소드/기능 호출을 하드코드화하지 않아도 되도록 이벤트를 게시하는 것은 누가 듣는지 신경 쓰지 않아도 됩니다.이를 통해 게시자가 가입자로부터 독립적이 되어 애플리케이션의 서로 다른 두 부분 간의 종속성(또는 원하는 용어에 상관없이 결합)을 줄일 수 있습니다.

위키피디아에서 언급한 커플링의 몇 가지 단점은 다음과 같습니다.

긴밀하게 결합된 시스템은 다음과 같은 발달 특성을 나타내는 경향이 있으며, 종종 단점으로 간주됩니다.

  1. 한 모듈의 변경은 일반적으로 다른 모듈의 변경에 따른 파급 효과를 강제합니다.
  2. 모듈 간 의존성이 증가하여 모듈 조립에 더 많은 노력 및/또는 시간이 필요할 수 있습니다.
  3. 특정 모듈은 종속 모듈을 포함해야 하므로 재사용 및/또는 테스트가 어려울 수 있습니다.

비즈니스 데이터를 캡슐화하는 개체와 같은 것을 생각해 보십시오.연령이 설정될 때마다 페이지를 업데이트할 수 있도록 하드 코딩된 메서드 호출이 있습니다.

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 없는 . , 에 에 2 이 이 또한 통화가 진행되는 것을 볼 때 동일한 파일에 포함되지 않는 것을 볼 때 유지보수가 어렵습니다.

동일한 모듈 안에서는 당연히 직접 메서드 호출을 할 수 있습니다.그러나 비즈니스 데이터와 피상적인 gui 동작은 합리적인 기준에 따라 동일한 모듈에 상주해서는 안 됩니다.

PubSub 구현은 일반적으로 다음이 있는 곳에서 볼 수 있습니다.

  1. 이벤트 버스의 도움으로 통신하는 여러 포틀렛이 있는 구현과 같은 포틀렛이 있습니다.이는 동기식 아키텍처에서 작성하는 데 도움이 됩니다.
  2. 견고한 결합으로 손상된 시스템에서 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.

"출판/구독의 다양한 면"이라는 논문은 좋은 평가를 받고 있으며, 그들이 강조하는 한 가지는 세 가지 "차원"에서의 탈동조화입니다.여기 제 조잡한 요약본이 있습니다만, 논문도 참고해주시기 바랍니다.

  1. 공간 분리.상호작용하는 당사자들은 서로를 알 필요가 없습니다.게시자는 누가 듣고 있는지, 몇 명이 듣고 있는지, 또는 이벤트로 무엇을 하고 있는지 알지 못합니다.구독자들은 이런 이벤트를 누가 제작하고 있는지, 제작자가 몇 명인지 등을 모릅니다.
  2. 시간 분리.상호 작용 중에 상호 작용 당사자가 동시에 활동할 필요는 없습니다.예를 들어, 게시자가 일부 이벤트를 게시하는 동안 가입자의 연결이 끊어질 수 있지만, 가입자가 온라인 상태가 되면 가입자에 대응할 수 있습니다.
  3. 동기화 디커플링.게시자는 이벤트를 생성하는 동안 차단되지 않으며 가입자는 가입한 이벤트가 도착할 때마다 콜백을 통해 비동기적으로 통지받을 수 있습니다.

간단한 답 원래 질문은 간단한 답을 찾는 것이었습니다.제 시도는 이렇습니다.

자바스크립트는 코드 개체가 자신의 이벤트를 생성할 수 있는 메커니즘을 제공하지 않습니다.따라서 당신은 일종의 이벤트 메커니즘이 필요합니다.게시/구독 패턴을 통해 이러한 요구를 충족할 수 있으며, 자신의 요구에 가장 적합한 메커니즘을 선택하는 것은 사용자에게 달려 있습니다.

이제 pub/sub 패턴에 대한 필요성을 알 수 있습니다. 그러면 pub/sub 이벤트를 처리하는 방식과 DOM 이벤트를 다르게 처리하시겠습니까?복잡성을 줄이기 위해, 그리고 관심사 분리(SoC)와 같은 다른 개념들은 모든 것이 통일되어 있다는 이점을 볼 수 있을 것입니다.

따라서 역설적으로 코드가 많을수록 관심사가 더 잘 분리되어 매우 복잡한 웹 페이지까지 확장됩니다.

누군가가 이것에 대해 자세히 말하지 않아도 충분히 좋은 토론을 할 수 있기를 바랍니다.

언급URL : https://stackoverflow.com/questions/13512949/why-would-one-use-the-publish-subscribe-pattern-in-js-jquery