Table of contents
지난 포스팅에서 알고리즘을 캡슐화하고 동적으로 알고리즘을 변경할 수 있는 스트래티지 패턴에 대해 복습했다. 이번에은 상태의 변화를 알리면서 의존성을 줄이는데 초점을 두는 옵저버 패턴에 대해 복습한다.
옵저버 패턴은 어떤 객체의 상태가 바뀌거나 중요한 메소드가 호출 되었을 때 이와 관련된 객체들에게 소식을 전달할 수 있는 패턴이다. 일대일로 연락을 전할 뿐 아니라, 연락을 전달하는 입장에서는 일대다 관계, 연락을 받는 입장에서는 다대일 관계, 즉 다대다 관계로 연락을 전달할 수 있다.
정의
한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 연락받은 객체에서는 자동으로 내용이 갱신되는 일대다(One to Many) 의존성을 정의한다.
일대다 관계는 주제와 옵저버에 의해 정의된다. 주제는 연락을 전달하는 오브젝트를 의미하여 옵저버는 연락을 받는 오브젝트를 의미한다. 연락을 받는 옵저버들은 주제에 의존하게 된다.
다이어그램
옵저버 패턴에서 상태를 저장하고 상황을 지배하는 것은 주제 객체이다. 결국 상태가 들어있는 주제는 하나의 역할에서 하나만 존재할 수 있다. 옵저버는 상태를 주제로부터 가져오거나 받아서 사용한다. 주제 객채에서 상태가 바뀌었다는 것을 알려주기를 기다리는, 혹은 주제로부터만 상태를 가져올 수 있는 상황이기 때문에 옵저버들이 주제에 의존적인 성질을 가지게 된다.
이러한 방법을 사용하면 여러 객체에서 동일한 데이터를 제어하도록 하는 것에 비해 더 깔끔한 디자인을 만들 수 있다. 주제에 해당하는 객체없이 옵저버들에서 같은 상태를 관리하는 것보다, 하나의 주제 클래스에서 상태를 관리하고 옵저버에게 상태를 던져주거나 가져가도록 하는 것이 의존성을 줄이고 유지보수성을 높이는 한 방법이다.
옵저버 패턴과 느슨한 결합
서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.(Loose Coupling)
두 객체가 느슨하게 결합되어있다는 것의 의미는 둘이 메세지를 주고 받지만 서로에 대해서는 잘 모른다는 것을 의미한다. 느슨한 결합을 사용하면 어떤 변경 사항이 생겨도 의존성이 최소화되어있기 떄문에 무난히 처리할 수 있는 유연한 시스템을 구축할 수 있다. 옵저버 패턴으로 디자인을 구성함으로써 느슨한 결합의 효과를 얻을 수 있는데, 그 이유는 다음과 같다.
- 주제의 입장에서 옵저버라는 객체들에 대해 알고있는 것은 특정 인터페이스를 구현한다는 것 뿐이다.
- 연락을 받거나 상태를 가져갈 수 있는 Observer라는 인퍼테이스를 구현한 구상 클래
- 옵저버는 언제든지 새로 추가할 수 있다.
- 주제는 Observer라는 인터페이스를 구현한 객체라면 그 객체가 어느 종류의 클래스인지는 상관없이 옵저버로 등록할 수 있다.
- 구체적인 구상클래스에 의존하지 않기 때문에 실행중에 옵저버 객체를 제거하거나 다른 클래스의 인스턴스로 변경할 수 있다.
- 새로운 형식(규격)을 갖는 옵저버를 추가하려고해도 주제를 전혀 변경할 필요가 없다.
- 옵저버 클래스가 Oberser의 인터페이스를 어떤 형식으로든 구현만 하면 다른 규격을 갖는 클래스나 인터페이스를 구현해도 주제 입장에서는 전혀 상관이 없다
- 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.
- 주제와 옵저버는 서로에 대해 아는것은 특정 인터페이스를 구현한 객체라는 것 뿐이다. 이렇게 느슨한 결합을 갖고있기때문에 주제나 옵저버를 전혀 다른 용도로 재사용할 수 있다.
- 주제나 옵저버가 바뀌더라도 서로한테 영향을 미치지는 않는다.
- 1 ~ 4와 같은 효과를 통해 느슨한 결합이 형성되기 때문이다.
예시
스프링을 이용해서 웹을 만들때의 간략한 상황이다. Model은 오늘의 날짜에 해당하는 상태를 갖고있는 주제이다. View는 날짜라는 상태를 화면에 표시하는 역할을 하는 옵저버이다. Model의 setDate() 메소드로 날짜를 변경하면 notify() 메소드를 통해 옵저버인 View들에게 소식을 전달한다. 옵저버인 View는 주제인 Model의 상태가 변경되었음을 update() 메소드를 통해 통보받았으니 화면의 날짜정보를 갱신한다.
푸시방식과 풀 방식
지금까지는 옵저버에게 통보만 가능한 푸시 방식에 대한 내용을 정리했다. 그러나 옵저버들은 다양하게 재사용될 수 있으므로 소식을 전달받는 특징은 한 부분에 불과하다. 보통 옵저버들은 연락을 받는 것 외에도 중요한 역할이 있다. 어떤 옵저버에서는 주제의 모든 상태를 알 필요가 없을 수도 있다. 예를 들어 날짜를 관리하는 주제의 온도정보만 필요한 옵저버가 있을 수도 있다.
푸시 방식과 다르게 옵저버가 직접 주제로부터 데이터를 가져오는 방식인 풀 방식이 존재한다. 자바에는 푸시 방식과 풀 방식의 옵저버 패턴을 마음대로 쓸 수 있게 해주는 Observable(주제)과 Observer(옵저버)클래스가 존재한다. 하지만 자바에 내장된 주제인 Observable은 인터페이스가 아닌 클래스이기 때문에 아래와 같은 단점이 존재한다.
- 어떤 클래스를 주제로 사용하고 싶을 때 이미 수퍼클래스가 있는 경우 해당 클래스는 주제로 사용할 수 없다.
- 다중 상속의 문제, 재사용 불가
- Observable이 인터페이스가 아니기 때문에 자바에 내장된 Observer API하고 잘 맞는 클래스를 직접 구현할 수 없다.
- Observable 클래스는 java.util이라는 패키지에 속해있다.
- java.util 구현을 멀티스레드로 구현한다거나 하는 일이 불가능하다.
- setChanged() 메소드가 protected로 선언되어 있다.
- Observable의 서브클래스에서만 setChanged() 메소드를 호출 할 수가 있다.
- java.util.Observable은 changed가 true로 설정되어 있지 않으면 notifyObservers() 메소드를 호출해도 아무 일이 일어나지 않는다. 클래스를 만들고 Observable의 서브클래스를 인스턴스 변수로 구성할 수가 없다. 결국 상속으로만 문제를 해결할 수 있다.
마무리
클래스가 java.util.Observable을 상속 가능하다면 자바에서 내장된 옵저버 패턴을 사용하는 것도 좋다. 하지만 사용이 불가능하다면 직접 옵저버 패턴을 디자인해야한다. 위에서 예시에도 등장했듯이 옵저버 패턴은 우리의 프레임워크에 녹아있는 MVC 패턴과 관계가 깊다. 상태의 변화를 알리면서 의존성을 줄이는데 초점을 두는 옵저버 패턴에 대한 복습을 마치겠다.