본문 바로가기
컴퓨터 과학/디자인패턴

[디자인패턴] 행위 패턴(8) : 발행-구독(Publisher-Subscriber)

by webcodur 2024. 3. 25.
728x90
반응형

 

목차

     

    발행-구독(Publisher-Subscriber)

    발행-구독(영어: Publish-Subscribe) 디자인 패턴은 소프트웨어 아키텍처 패턴의 하나로, 메시지 발행 컴포넌트(발행자)와 메시지 수신 컴포넌트(구독자) 사이 결합도를 낮추기 위해 사용된다. 이 패턴의 핵심은 발행자가 메시지를 누구에게 보내야 하는지 몰라도 되며, 구독자는 어떤 발행자로부터 메시지를 받는지 알 필요가 없다는 데 있다. 즉, 발행자와 구독자 사이에 직접적인 연결 고리가 없어, 시스템의 유연성과 확장성을 향상시킨다.

     

    패턴 미적용 예시

    게임 개발 상황을 예로 들어보자. 게임 내에서 특정 이벤트(예: 캐릭터가 레벨업하는 이벤트)가 발생했을 때, 여러 시스템(예: UI, 경험치 로그, 업적 시스템)이 이를 감지하고 반응해야 한다고 가정해보자.

    // 패턴 미적용 코드 예시
    class Character {
        public int Level { get; private set; }
    
        // 레벨 업 함수
        public void LevelUp() {
            Level++;
            UpdateUI();
            CheckAchievements();
        }
    
        void UpdateUI() {
            // UI 업데이트 로직
        }
    
        void CheckAchievements() {
            // 업적 체크 로직
        }
    }
    

    위 코드에서 Character 클래스는 레벨업 할 때마다 직접 UI를 업데이트하고, 경험치를 로깅하며, 업적을 체크한다. 이 경우, 새로운 시스템을 추가하거나 기존 시스템을 변경할 때마다 Character 클래스를 수정해야 한다. 이는 유지보수성과 확장성에 문제를 일으킨다.

     

    패턴 적용 예시

    이제 같은 상황을 발행-구독 패턴을 적용하여 해결해보자. 발행-구독 패턴을 적용하면 Character 클래스는 레벨업 이벤트를 발행하기만 하면 되고, 관심 있는 시스템들이 이 이벤트를 구독하여 각자 필요한 UI 업데이트, 업적 체크 등의 반응을 할 수 있다.

    using System;
    using System.Collections.Generic;
    
    // 발행-구독 패턴의 이벤트 매니저
    public class EventManager {
        // 레벨업 이벤트 핸들러 정의
        public delegate void LevelUpEventHandler(int newLevel);
        // 레벨업 이벤트
        public static event LevelUpEventHandler OnLevelUp;
        // 레벨업 이벤트 발행 메서드
        public static void PublishLevelUp(int level) {
            OnLevelUp?.Invoke(level);
        }
    }
    
    class Character {
        public int Level { get; private set; }
        public void LevelUp() {
            Level++;
            // 이벤트 매니저를 통해 레벨업 이벤트 발행
            EventManager.PublishLevelUp(Level);
        }
    }
    
    // [구독자] UI 클래스
    class UI {
        public UI() {
            EventManager.OnLevelUp += UpdateUI;
        }
        void UpdateUI(int level) {
            Console.WriteLine($"UI Update: Character is now level {level}.");
        }
    }
    
    // [구독자] 업적 시스템 클래스
    class Achievement {
        public Achievement() {
            EventManager.OnLevelUp += CheckAchievements;
        }
        void CheckAchievements(int level) {
            Console.WriteLine($"Achievement Unlocked: Reached level {level}.");
        }
    }
    

     

    위 코드에서 EventManager 클래스는 레벨업 이벤트를 관리하며, Character 클래스는 레벨업 시 해당 이벤트를 발행한다. UI, Achievement 클래스는 각각 레벨업 이벤트에 대해 구독하고 있으며, 이벤트가 발생하면 자신의 로직을 실행한다. 이렇게 발행-구독 패턴을 적용함으로써, 캐릭터 클래스와 각 시스템 간의 결합도를 낮출 수 있으며, 시스템의 확장성과 유지보수성을 향상시킬 수 있다.

     

    기본 구성요소

    발행-구독 패턴의 기본 구성요소는 다음과 같다:

    • 발행자(Publisher): 이벤트를 발생시키는 주체. Character 클래스가 이 역할을 수행한다.
    • 구독자(Subscriber): 이벤트에 반응하여 특정 작업을 수행하는 객체들. UI, Achievement 클래스들이 구독자의 역할을 한다.
    • 이벤트 채널(Event Channel): 발행자와 구독자 사이의 커뮤니케이션을 담당하는 중계체. 이 예시에서는 EventManager 가 이 역할을 맡는다. 이벤트 채널을 통해 발행자는 구독자에게 직접적인 참조 없이도 메시지를 전달할 수 있으며, 구독자는 관심 있는 이벤트를 선택해 구독할 수 있다.

    이 구성 요소들을 통해 발행-구독 패턴은 다음과 같은 이점을 제공한다:

    • 낮은 결합도(Low Coupling): 발행자와 구독자가 서로 독립적으로 작동하며, 서로의 구현에 대해 알 필요가 없다. 이로 인해 시스템의 각 부분을 독립적으로 변경하거나 확장할 수 있다.
    • 유연성(Flexibility): 새로운 구독자를 쉽게 추가하거나 제거할 수 있으멀로, 시스템을 유연하게 확장할 수 있다.
    • 동적 구성(Dynamic Configuration): 실행 시간에 구독자를 추가하거나 제거할 수 있어, 시스템의 동작을 동적으로 조정할 수 있다.

     

    적용 사례의 이점과 문제점

    발행-구독 패턴을 적용함으로써, 위의 게임 개발 예시에서 볼 수 있듯이 다음과 같은 이점을 얻을 수 있다:

    • 확장성: 새로운 시스템(예: 새로운 업적 시스템이나 경험치 로그 시스템)을 게임에 추가하는 것이 쉬워진다. 새로운 시스템을 추가하기 위해 기존 코드를 변경할 필요가 없으며, 단지 새로운 구독자를 이벤트 매니저에 등록하기만 하면 된다.
    • 재사용성: 이벤트 매니저와 같은 컴포넌트는 다양한 시나리오와 애플리케이션에서 재사용될 수 있다.
    • 테스트 용이성: 발행자와 구독자가 분리되어 있기 때문에, 각 컴포넌트를 독립적으로 테스트하기 용이하다.

    그러나 몇 가지 주의해야 할 문제점도 존재한다:

    • 이벤트 플로우 추적 어려움: 발행자와 구독자 사이의 직접적인 연결이 없기 때문에, 시스템 내에서 이벤트 흐름을 추적하기 어려울 수 있다. 이로 인해 디버깅이 복잡해질 수 있다.
    • 메모리 누수 위험: 구독자가 적절히 등록을 해제되지 않는 경우, 메모리 누수가 발생할 수 있다. 따라서 구독자가 더 이상 필요 없을 때는 이벤트로부터 등록을 해제하는 것이 중요하다.

    발행-구독 디자인 패턴은 이러한 이점과 주의해야 할 사항들을 고려하여 적절히 적용될 때, 특히 다양한 이벤트가 발생하고, 시스템의 다양한 부분이 이러한 이벤트에 반응해야 하는 대규모 애플리케이션에서 강력한 아키텍처적 이점을 제공할 수 있다.

     

    이벤트 핸들링, 이벤트 기반 처리

    참고로 발행-구독 패턴은 플랫폼 간 차이는 있지만, 웹(리액트)의 STATE나 응용프로그램(윈폼)의 버튼 클릭 등 이벤트 핸들링 도구들과도 밀접한 관련이 있다. 

     

    하지만 모든 이벤트 기반 처리가 발행-구독 패턴에만 한정된다고 말하기는 어렵다. 발행-구독은 이벤트를 처리하는 대표적인 디자인 패턴이며, 기타 이벤트 처리 패턴도 여러 가지가 있기 때문이다.

     

    이 표는 각 패턴의 핵심 개념과 주요 사용 예시를 간략하게 요약하여 제공한다. 이벤트 처리 패턴을 선택할 때는 특정 상황의 요구 사항과 문제를 해결하는 데 가장 적합한 패턴을 고르는 것이 중요하다.

     

    발행-구독  이벤트 발행자가 이벤트를 발행하면 구독자들이 이를 수신하여 처리한다. 발행자와 구독자 사이에는 직접적인 연결이 없다. 메시징 시스템, 이벤트 기반 마이크로서비스 아키텍처 등
    옵저버 객체의 상태 변화를 관찰하는 옵저버들이 있으며, 상태가 변할 때마다 옵저버에게 알림을 보낸다. GUI 컴포넌트 상태 변화 감지, 데이터 모델 변화 감지 등
    이벤트 버스 이벤트를 중앙 집중식으로 관리하는 이벤트 버스를 통해 시스템의 다른 부분들이 이벤트를 송수신한다. 안드로이드 이벤트 처리, 크로스-서비스 이벤트 통신 등
    요청-응답 클라이언트가 서버에 요청을 보내고, 서버가 그에 대한 응답을 반환한다. 동기적 통신에 주로 사용된다. 웹 서버와 브라우저 간의 HTTP 통신, API 호출 등
    명령-쿼리
    책임 분리
    (CQRS)
    시스템의 읽기(read) 연산과 쓰기(write) 연산을 분리하여 설계한다. 이를 통해 읽기와 쓰기 작업의 최적화 및 복잡성 관리가 용이해진다. 대규모 분산 시스템, 복잡한 도메인 로직을 가진 애플리케이션 등
    액터 모델
    (Actor) 
    시스템을 독립적인 액터의 집합으로 모델링하며, 각 액터는 메시지를 통해 통신한다. 액터 모델은 동시성과 병렬 처리를 용이하게 한다. 에카(Erlang), 액카(Akka) 라이브러리, 동시성이 필요한 애플리케이션 등

     

     

    반응형