옵저버패턴은 어떤 객체에 이벤트가 발생했을 때, 이 객체와 관련된 객체들(옵저버)에게 통지하도록 하는 디자인 패턴을 말한다. 즉, 객체의 상태가 변경되었을 때 특정 객체에 의존하지 않으면서 상태의 변경을 관련된 객체들에게 통지하는 것이 가능해진다.
유튜브를 예시로, 유튜브 채널은 발행자가 되고 구독자는 구독자(옵저버)가 되는 구조이다. 즉, 유튜버가 영상을 업로드하면 구독자(옵저버)는 영상이 업로드 되었다는 알림을 받을 수 있다.
채팅방을 예로들면, 관리자는 채팅방에 말을 입력할 수 있고 채팅방은 이를 화면에 보여줄 수 있다.
이를 코드로 표현하면 다음과 같다.
// Admin.java
public class Admin {
private Room room
public void setRoom(Room room) {
this.room = room;
}
public void talk(String msg) {
room.receive(msg)
}
}
// Room.java
public class Room {
public void receive(String msg) {
System.out.println(msg);
}
}
// Client.java
public class Client {
public static void main(String args[]) {
Admin admin = new Admin();
Room room = new Room();
admin.setRoom(room);
admin.talk("안녕하세요");
}
}
해당 코드를 실행하면 admin
객체가 setRoom
메소드를 통해 입장할 방을 선택하고 talk
메소드를 통해 Room
객체 내부의 receive
메소드가 실행되어 "안녕하세요"를 출력할 것이다.
만약 여기서 관리자가 여러 채팅방을(게임의 경우 서버1, 서버2...) 생성할 경우 모든 채팅방에 공지사항과 같은 메세지를 전송하기 위해서는 어떻게 해야할까?
아래의 코드는 여러 개의 채팅방에 메세지를 전송하기 위한 코드이다.
// Room.java
public class Room {
public String roomName;
public void receive(String msg) {
System.out.println(this.roomName + "에서 메세지를 받음 " + msg);
}
}
// Room1.java
public class Room1 extends Room {
public Room1(String roomName) {
this.roomName = roomName;
}
}
// Room2.java
public class Room2 extends Room {
public Room2(String roomName) {
this.roomName = roomName;
}
}
Room1, Room2 클래스를 정의하고 이를 Room클래스로 캡슐화한다.
// Admin.java
public class Admin {
private List < Room > room;
public void setRoom(List < Room > room) {
this.room = room;
}
public void talk(String msg) {
for (Room numRoom: room) {
numRoom.receive(msg);
}
}
}
// Client.java
public class Client {
public static void main(String args[]) {
Admin admin = new Admin();
List<Room> rooms = new ArrayList<>();
rooms.add(new Room1("1번방"));
rooms.add(new Room2("2번방"));
admin.setRoom(rooms);
admin.talk("공지사항입니다.");
}
}
Client클래스의 코드를 보면 Admin
과 Room
은 매우 강하게 연결되어 있다. 즉, 결합도가 강하다는 것인데 이런 상황에서 다른 방이 추가되거나 삭제되어 메세지를 보내야할 경우 어떻게 해야 할까?
옵저버 패턴을 이용하면 위와 같은 상황을 객체지향적으로 구현이 가능해지고 객체간의 의존성도 제거된다.
위 UML다이어그램을 보고 예제에 옵저버 패턴을 적용하겠다. 가장 먼저 Room1
과 Room2
를 옵저버로 두기 위해 옵저버 클래스를 정의하여 캡슐화 한다.
// Observer.java
public class Observer {
public String roomName;
public void receive(String msg) {
System.out.println(this.roomName + "에서 메세지를 받음 " + msg);
}
}
// Room1.java
public class Room1 extends Observer {
public Room1(String roomName) {
this.roomName = roomName;
}
}
// Room2.java
public class Room2 extends Observer {
public Room2(String roomName) {
this.roomName = roomName;
}
}
다음으로 옵저버를 추가, 제거하고 메세지를 알리는 기능들을 정의하는 Subject
클래스를 정의한다. Admin클래스는 Subject클래스를 상속받는다. 따라서 Admin클래스에서 옵저버를 관리할 수 있게 된다.
// Subject.java
public class Subject {
private List < Observer > observers = new ArrayList < > ();
// Add Observer
public void attach(Observer observer) {
observers.add(observer);
}
// remove Observer
public void detach(Observer observer) {
observer.remove(observer);
}
// notice Observer
public void notifyObservers(String msg) {
for (Observer observer: observers) {
observer.receive(msg);
}
}
}
// Admin.java
public class Admin extends Subject {
}
마지막으로 Client
에서 코드가 어떻게 바뀌었을까? 이전 코드와 비교했을 때 의존성이 많이 제거된 것을 확인할 수 있다.
// Client.java
public class Client {
public static void main(String args[]) {
Admin admin = new Admin();
Room1 room1 = new Room1("1번방");
Room2 room2 = new Room2("2번방");
admin.attach(room1);
admin.attach(room2);
String msg = "공지사항입니다.";
admin.notifyObeservers(msg);
admin.detach(room1);
msg = "두 번째 공지사항입니다.";
admin.notifyObservers(msg);
}
}
위 코드를 실행시켜보면, admin
객체를 통해 추가된 room1
과 room2
에게 메세지가 전송되고 admin
객체가 room1
을 detach메소드를 사용하여 Observer 리스트에서 제거했기 때문에 "두 번째 공지사항입니다."의 문구는 room2
만 전송되는 것을 확인할 수 있다.