옵저버 패턴 ( Observer Pattern )

옵저버 패턴은 어떤 객체에 이벤트가 발생했을 때, 이 객체와 관련된 객체들( 옵저버들 )에게 통지하도록 하는 디자인 패턴을 말합니다.

즉, 객체의 상태가 변경되었을 때, 특정 객체에 의존하지 않으면서 상태의 변경을 관련된 객체들에게 통지하는 것이 가능해집니다.

이 패턴은 Pub/Sub( 발행/구독 ) 모델으로 불리기도 합니다.


예를 들어, 유튜브를 생각해보겠습니다.

Pub/Sub 모델에 따르면, 유튜브 채널은 발행자가 되고 구독자들은 구독자( 옵저버 )가 되는 구조입니다.

즉, 유튜버가 영상을 올리면 구독자들은 영상이 올라왔다는 알림을 받을 수 있습니다.


이렇게 각각의 유저들을 유튜브 채널을 구독하고 있는 옵저버가 됩니다.





1. 옵저버 패턴 사용 이유

예를 들어, 어떤 유저와 채팅 방이 있다고 가정하겠습니다.

유저는 채팅방에 말할 수 있고, 채팅방은 이를 들을 수 있습니다.


이를 코드로 표현하면 다음과 같습니다.

public class User {
private Room room;

public void setRoom(Room room){
this.room = room;
}

public void talk(String msg){
room.receive(msg);
}
}
public class Room {
public void receive(String msg){
System.out.println(msg);
}
}
public class Client {
public static void main(String args[]){
User user = new User();
Room room = new Room();

user.setRoom(room);

String msg = "안녕하세요~~";
user.talk(msg);
}
}



그런데 유저가 여러 채팅 방(채팅방, 게임방, 개발방)에 입장을 하게되었습니다.

그리고 유저가 채팅을 입력하면 모든 채팅방에 메시지가 전달되어야 하는 상황입니다.

( 예를 들면, 공지사항을 전달하는 개념 )


이렇게 여러 채팅 방에 참여하게 될 경우, 코드는 다음과 같습니다.


public class Room {
publicString roomName;

public void receive(String msg){
System.out.println(this.roomName + "에서 메시지를 받음 : " + msg);
}
}

public class ChatRoom extends Room{
public ChatRoom(String roomName){
this.roomName = roomName;
}
}
public class GameRoom extends Room{
public GameRoom(String roomName){
this.roomName = roomName;
}
}
public class DevRoom extends Room{
public DevRoom(String roomName){
this.roomName = roomName;
}
}

채팅방, 게임방, 개발방 클래스를 정의하고 이를 Room 클래스로 캡슐화합니다.


import java.util.List;

public class User {
private List<Room> room;

public void setRoom(List<Room> room){
this.room = room;
}

public void talk(String msg){
for(Room r:room){
r.receive(msg);
}
}
}
import java.util.ArrayList;
import java.util.List;

public class Client {
public static void main(String args[]){
User user = new User();
List<Room> rooms = new ArrayList<Room>();
rooms.add(new ChatRoom("채팅방"));
rooms.add(new GameRoom("게임방"));
rooms.add(new DevRoom("개발방"));

user.setRoom(rooms);

String msg = "안녕하세요~";
user.talk(msg);

}
}

Client 클래스의 코드를 보시면, UserRoom은 매우 강하게 연결되어 있습니다.

이런 상황에서 다른 방이 생성되거나, 특정 방이 제거되어 메시지를 보내야 한다면 어떻게 해야 할까요?

물론 List이기 때문에 추가 제거가 가능하지만, 옵저버 패턴을 이용하면 객체지향적으로 구현이 가능해집니다.

즉, 옵저버 패턴을 사용함으로써 시스템이 유연해지고, 객체간의 의존성도 제거됩니다.





2. 옵저버 패턴 적용

이제 옵저버 패턴을 적용해서 개선을 하도록 하겠습니다.


1)

먼저 채팅방, 게임방, 개발방을 옵저버로 둡니다.

그러기 위해서 옵저버 클래스를 정의하여 캡슐화 하도록 하겠습니다.

( 상황에 따라서 인터페이스 또는 추상클래스로 정의해도 상관은 없는데, 지금은 클래스가 더 어울려 보입니다. )

public class Observer {
public String roomName;

public void receive(String msg){
System.out.println(this.roomName + "에서 메시지를 받음 : " + msg);
}
}

public class ChatRoom extends Observer{
public ChatRoom(String roomName){
this.roomName = roomName;
}
}

public class GameRoom extends Observer{
public GameRoom(String roomName) {
this.roomName = roomName;
}
}
public class DevRoom extends Observer{
public DevRoom(String roomName){
this.roomName = roomName;
}
}


2)

다음으로 옵저버를 추가하고 제거하고 메시지를 알리는 기능들을 정의하는 Subject 클래스를 정의합니다.

그리고 User 클래스에서 Subject 클래스를 상속받도록 합니다.

즉, User 클래스에서 옵저버들을 관리할 수 있는 것이죠.

import java.util.ArrayList;
import java.util.List;

public class Subject {
private List<Observer> observers = new ArrayList<Observer>();

// 옵저버에 추가
public void attach(Observer observer){
observers.add(observer);
}

// 옵저버에서 제거
public void detach(Observer observer){
observers.remove(observer);
}

// 옵저버들에게 알림
public void notifyObservers(String msg){
for (Observer o:observers) {
o.receive(msg);
}
}
}
public class User extends Subject{

}


3)

마지막으로 Client에서 코드가 어떻게 바뀌었는지 보겠습니다.

의존성이 많이 제거된 것을 확인할 수 있습니다.

public class Client {
public static void main(String args[]){
User user = new User();
ChatRoom chatRoom = new ChatRoom("채팅방");
GameRoom gameRoom = new GameRoom("게임방");
DevRoom devRoom = new DevRoom("개발방");
user.attach(chatRoom);
user.attach(gameRoom);
user.attach(devRoom);

String msg = "안녕하세요~";
user.notifyObservers(msg);

user.detach(chatRoom);
msg = "Hi~";
user.notifyObservers(msg);
}
}





이상으로 옵저버 패턴이 무엇인지에 대해 알아보았습니다.


Java에서도 여러 디자인 패턴을 사용하고 있는데 옵저버 패턴을 사용하고 있는 API는 다음과 같으며, 참고하시면 좋을 것 같습니다. ( 참고링크 )

Observer (or Publish/Subscribe)

(recognizeable by behavioral methods which invokes a method on an instance of another abstract/interface type, depending on own state)