ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [헤드퍼스트 디자인 패턴] 2장. 객체들에게 연락 돌리기, 옵저버 패턴
    Book/헤드 퍼스트 디자인 패턴 2023. 4. 14. 22:08

    2장 객체들에게 연락 돌리기, 옵저버 패턴

    본 글은 '헤드퍼스트 디자인 패턴' 책를 읽고 정리한 글입니다.

     

    어떤 서비스를 만들어야 하나요?

    2장에서 만나는 어플리케이션은 기상 모니터링 서비스입니다.

     

    우리는 온도, 습도 기압을 추적하는 WeatherData 객체를 바탕으로 3개의 항목을 각각 현재 조건, 기상 통계, 그리고 간단한 기상 예보를 화면에 표시하는 애플리케이션을 만들어야합니다. 또한 이 항목들은 모두 최신 측정치를 수집할 때마다 실시간으로 갱신됩니다. 우선 외주가 들어온 것이기 때문에 납품처(Weather-O-Rama)에서 제공하는 부분을 살펴봐야합니다.

     

    그러면 다시 한 번 만들어야하는 기상 모니터링 어플리케이션을 살펴봅시다. 

    이 시스템은 기상 스테이션(실제 기상 정보를 수집하는 물리 장비), WeatherData 객체(기상 스테이션으로부터 오는 정보를 추적하는 객체), 사용자에게 현재 기상 조건을 보여 주는 디스플레이 장비로 구성됩니다.

     

    이 중 우리가 구현해야하는 구현체는 WeatherData 객체 디스플레이를 통합하는 작업과 디스플레이 장비에서 보여지는 부분입니다. 

     

    그러면 대표가 보내준 소스코드를 한번 살펴봐볼까요?

     

    받은 클래스와 우리가 구현해야할 부분을 모식도로 정리한 내용입니다.

     

    우리는 더해서 변경사항이 디스플레이에 반영되는 것도 구현해야하므로 현재 조건, 기상 통계, 그리고 간단한 기상 예보를 보여주는 3가지 디스플레이가 업데이트되도록 measurementsChanged()를 바꿔야합니다.

     

    구현 목표

    우리는 measurementsChanged() 메서드가 호출될 때마다 WeatherData에서 디스플레이를 업데이트해야 합니다. 

     

    우리가 해야하는 일을 자세히 살펴보면 다음과 같습니다.

    • WeatherData 클래스는 3가지 측정값(온도, 습도, 기압)의 게터 메소드가 있다.
    • 새로운 기상 측정 데이터가 들어올 때마다 measurementChanged()메소드가 호출된다.
    • 기상 데이터를 사용하는 디스플레이 요소 3가지를 구현해야 한다. 하나는 현재 조건 디스플레이, 다른 하나는 기상 통계 디스플레이, 마지막은 기상 예보 디스플레이이다. WeatherData에서 새로운 측정값이 들어올 때마다 디스플레이를 갱신해야 한다.
    • 디스플레이를 업데이트하도록 measurementChanged() 메소드에 코드를 추가해야 한다.
    💡 알고 갑시다! 

    객체 지향의 5가지 원칙 중에 SOLID 원칙을 아시나요? 그 중 OCP 원칙이 무엇인가요? 
    OCP 원칙이란 개방 폐쇄 원칙으로, 확장엔 열려있고 변경에는 닫혀 있어야 한다는 원칙입니다.

     

    우리는 이 원칙을 바탕으로 추가 목표가 생겼습니다. 나중에 새로운 마켓플레이스가 만들어질 것을 고려해 확장 기능을 추가하는 겁니다. 

     

    - 1차 코드 적어보기는 생략하였습니다.

    옵저버 패턴 이해하기

    그러면 대체 옵저버 패턴은 무엇일까요? 

     

    신문이나 잡지를 구독할 때를 생각해 봅시다.

     

    1.  신문사가 사업을 시작하고 신문을 찍어내기 시작합니다.
    2. 독자가 특정 신문사에 구독 신청을 하면 매번 새로운 신문이 나올 때마다 배달을 받을 수 있습니다.  구독을 해지하기 전까지 신문을 계속 받을 수 있습니다.
    3. 신문을 더 이상 보고 싶지 않으면 구독 해지 신청을 합니다. 그러면 더이상 신문이 오지 않습니다.
    4. 신문사가 망하지 않는 이상 개인, 호텔, 항공사 및 기타 회사 등은 꾸준하게 신문을 구독하거나 해지합니다.

    신문 구독 메커니즘만 제대로 이해할 수 있다면, 옵저버 패턴을 쉽게 이해할 수 있습니다.

     

    신문사 + 구독자 = 옵저버 패턴 

     

    이 때 신문사를 주제(subject), 구독자를 옵저버(observer)라고 생각하면 됩니다.

     

    자세히 살펴보면 다음과 같습니다.

     

     

    그림을 통해 대략적으로 이해를 했다면 옵저버 패턴의 작동 순서, 원리를 알아봅시다.

     

    옵저버 패턴의 작동 원리

     

    1. 옵저버가 되고 싶은 A 객체가 등장해서 주제한테 자기도 옵저버가 되고 싶다고 얘기합니다.
    2. A 객체가 정식 옵저버가 되었습니다.
    3. 주제 값이 바뀌었습니다.
      • 이제 A 객체를 비롯한 모든 옵저버가 주제 값이 바뀌었다는 연락을 받습니다.
    4. B 객체가 옵저버 목록에서 탈퇴하고 싶다는 요청을 합니다.
    5. 주제가 B객체의 요청을 받아들여 옵저버 집합에서 제거 합니다.
    6. 주제가 변경되면 B 값은 그 값이 무엇인지 확인할 수 없습니다.
      • 만일 새로운 값을 알고 싶다면 다시 옵저버 등록 요청을 해야합니다.

     

    옵저버 패턴의 정의

    앞서 신문사, 구독자에 빗대어 옵저버 패턴을 이해했다면, 정확한 정의에 대해 알아봅시다.

     

    옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다.

    옵저버 패턴은 일련의 객체 사이에서 일대다 관계를 정의합니다. 한 객체의 상태가 변경되면 그 객체에 의존하는 모든 객체에 연락이 갑니다.

     

    옵저버 패턴은 보통 여러가지 방법으로 구현할 수 있지만, 보통은 주제 인터페이스와 옵저버 인터페이스가 들어있는 클래스 디자인으로 구현합니다.

     

    옵저버 패턴의 구조

    다음은 옵저버 패턴의 구조입니다.

     

    Subject 인터페이스

    주제를 나타내는 Subject 인터페이스는 객체에서 옵저버로 등록하거나 옵저버 목록에서 탈퇴하고 싶을 때 Subject 인터페이스 내의 메소드를 사용합니다.

    각 주제마다 여러 개의 옵저버가 있을 수 있습니다.

     

    Observer 인터페이스

    옵저버가 될 가능성이 있는 객체는 반드시 Observer 인터페이스를 구현해야 합니다. 이 인터페이스 에는 주제와 상태가 바뀌었을 때 호출되는 update() 메소드 밖에 없습니다.

     

    ConcreteSubject 클래스

    주제 역할을 하는 구상 클래스 에는 항상 Subject 인터페이스를 구현해야합니다.

    주제 클래스에는 등록 및 해지용 메소드와 상태가 바뀔 때 마다 모든 옵저버에게 연락하는 notifyObserver() 메소드도 구현해야합니다.

     

    ConcreateObserver 클래스

    Observer 인터페이스만 구현한다면 무엇이든 옵저버 클래스가 될 수 있습니다.

    각 옵저버는 특정 주제에 등록해서 연락받을 수 있습니다.

     

    느슨한 결합의 위력

    앞서 본 구조에서 우리는 의존성에 대한 생각을 했어야합니다.

    여러개의 옵저버가 하나의 주제에 연관되어 있는 부분을 우리는 옵저버가 주제에 의존하고 있다. 라고 생각할 수 있습니다.

     

    그런데 이 의존성에서 중요한 것은 느슨한 결합입니다.

     

    느슨한 결합은 객체들이 상호작용할 수는 있지만, 서로를 잘 모르는 관계를 의미합니다. 느슨한 결합을 하면 유연성이 아주 좋아집니다. 옵저버 패턴은 느슨한 결합을 보여주는 훌륭한 예입니다. 옵저버 패턴에서 어떤 식으로 느슨한 결합을 만드는지 알아봅시다.

     

    주제는 옵저버가 특정 인터페이스(Observer 인터페이스)를 구현한다는 사실만 압니다.

    옵저버의 구상 클래스가 무엇인지, 옵저버가 무엇을 하는지도 알 필요가 없습니다.

     

    옵저버는 언제든지 새로 추가할 수 있습니다.

    주제는 Observer 인터페이스를 구현하는 객체의 목록에만 의존하므로 새로운 옵저버를 추가할 수 있습니다. 마찬가지로 아무 때나 옵저버를 제거해도 됩니다.

     

    새로운 형식의 옵저버를 추가할 때도 주제를 변경할 필요가 전혀 없습니다.

    옵저버가 되어야 하는 새로운 구상 클래스가 생겼다고 해도 그 클래스에서 Observer 인터페이스를 구현하고 옵저버로 등록하기만 하면 됩니다.

     

    주제와 옵저버는 서로 독립적으로 재사용할 수 있습니다.

    주제나 옵저버를 다른 용도로 활용할 일이 있다고 해도 손쉽게 재사용할 수 있습니다. 

     

    주제나 옵저버가 달라져도 서로에게 영향을 미치지는 않습니다.

    서로 느슨하게 결합되어 있으므로 주제나 옵저버 인터페이스를 구현한다는 조건만 만족한다면 어떻게 고쳐도 문제가 생기지 않습니다.

     

    이렇게 읽어보니 느슨한 결합의 정의를 알 것 같습니다.

     

    우리는 여기서 새로운 디자인 원칙을 볼 수 있습니다.

     

    상호작용을 하는 객체 사이에는 가능하면 느슨한 결합을 사용해야 한다.

     

    느슨하게 결합하는 디자인을 사용하면 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있습니다.

    객체 사이의 상호의존성을 최소화할 수 있기 때문이죠.

     

    기상 스테이션 설계하기

    - 출처: 헤드퍼스트 디자인 패턴

     

     

    그리고 더해서 내용은 기상 스테이션의 코드 구현 내용이 나옵니다. 이는 생략하겠습니다.

     


    번외

    라이브러리 속 옵저버 패턴 알아보기

    잠시 JDK의 스윙 라이브러리에서 어떻게 옵저버 패턴을 활용하는지 알아봅시다.

    스윙 툴킷의 기본 요소 중 하나로 JButton 클래스를 들 수 있습니다. JButton의 수퍼클래스인 AbstractButton을 찾아보면 리스너를 추가하고 제거하는 메소드가 잔뜩 있다는 사실을 알 수 있습니다. 이런 메소드 들은 스윙 구성 요소에서 일어나는 다양한 이벤트를 감지하는 옵저버를 추가하거나 제거하는 역할을 합니다. 

     

    코딩 심화학습
    옵저버 패턴을 조금 더 다양하게 활용하는 방법이 있습니다. 내부 클래스 대신 람다 표현식을 사용하면 ActionListener 객체를 만드는 단계를 건넌 뛸 수 있습니다. 또한 함수 객체를 만들고 옵저버로 활용할 수도 있습니다.

    푸시를 풀 방식으로 바꾸기

    사실 주제가 옵저버로 데이터를 봬는 푸시(push)를 사용하거나 옵저버가 주제로부터 당겨오는 풀(pull)을 사용하는 방법 중 어느 하나를 선택하는 일은 구현 방법의 문제라고 볼 수 있습니다. 하지만 대체로 옵저버가 필요한 데이터를 골라서 가져가도록 만드는 방법이 더 좋습니다. 

     

    어떻게 코드를 바꾸면 좋을까요?

     

    원래는 subject의 notifyObserver()에 observer.update()가 있었다면, 이제는 Observer 클래스의 update 메소드를 바꿔 get 메서드를 넣어 바꿔줍니다.


    오늘은 옵저버 패턴에 대하여 살펴보았습니다. 어떤 상황에 옵저버 패턴이 필요한지, 옵저버 패턴의 동작 원리를 보았습니다. 

     

    오늘 제가 새롭게 배운 내용을 정리하자면 첫번째로 옵저버 패턴은 subject 와 observer의 인터페이스와 구현체로 구성되어있고 이들은 느슨한 결합으로 이루어져 있고 보통은 푸시의 방식보다 풀의 방식을 선호한다는 점도 배워볼 수 있었습니다.

     

    다음 글은 데코레이터 패턴을 만나보도록해요!

     

Designed by Tistory.