ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java Design Pattern - 옵저버 패턴(Observer Pattern)
    Language/Java Design Pattern 2022. 11. 21. 14:27

    옵저버 패턴(Observer pattern)

    옵저버 패턴이란?

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

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

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

    예를 들어, 유튜브를 생각해 보겠다. Pub/Sub 모델에 따르면, 유튜브 채널은 발행자가 되고 구독자들은 구독자(옵저버)가 되는 구조이다.

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

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

     

    옵저버 패턴의 사용 이유

    예를 들어, 어떤 유저와 채팅방이 있다고 가정해보겠다. 유저는 채팅방에 말을 할 수 있고, 채팅방은 이를 들을 수 있다.

     

    public class User {
        private Room room;
        
        public void setRoom(Room room) {
            this.room = room;
        }
        
        public String talk(String message) {
            return room.receive(message);
        }
    
    }
    public class Room {
        public String receive(String message) {
            return "message";
        }
    }
    public class client {
        public static void main(String[] args) {
            User user = new User();
            Room room = new Room();
            
            user.setRoom(room);
            
            String message = "안녕하세요.";
            System.out.println(user.talk(message));
        }
    }

    이를 코드로 표현하면 위와 같다.

     

    그런데, 유저가 여러 채팅방(채팅방, 게임방, 개발방)에 입장을 하게 되었다. 그리고 유저가 채팅을 입력하면 모든 채팅방에 메시지가 전달되어야 하는 상황이다.

     

    public class Room {
        public String roomName;
        
        public String receive(String message) {
            return this.roomName + " : " + message;
        }
    }
    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 클래스로 캡슐화한다.

     

    public class User {
        private List<Room> rooms;
        
        public void setRoom(List<Room> rooms) {
            this.rooms = rooms;
        }
        
        public List<String> talk(String message) {
            List<String> messages = new ArrayList<>();
            for (Room room : rooms) {
                messages.add(room.receive(message));
            }
            return messages;
        }
    }
    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 message = "안녕하세요.";
            List<String> messages = user.talk(message);
            
            for (String message : messages) {
                System.out.println(message);
            }
        }
    }

    Client 클래스의 코드를 보면, User와 Room은 매우 강하게 연결되어 있다.

    이러한 상황에서 다른 방이 생성되거나, 특정 방이 제거되어 메시지를 보내야 한다면 어떻게 해야 할까? 물론, List 자료구조를 사용하기 때문에 추가, 제거가 가능하지만, 옵저버 패턴을 이용하면 객체지향적으로 구현이 가능해진다.

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

     

    옵저버 패턴의 적용

    이제, 옵저버 패턴을 적용해서 위 코드를 개선해 보도록 하겠다.

     

    public class Observer {
        public String roomName;
        
        public String receive(String message) {
            return this.roomName + " : " + message;
        }
    }
    public class ChatRoom extends Observer {
        public CharRoom(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;
        }
    }

    먼저 채팅방, 게임방, 개발방을 옵저버로 둔다. 그러기 위해서는 옵저버 클래스를 정의하여 캡슐화하도록 하겠다.

     

    public class Subject {
        private List<Observer> observers = new ArrayList<Observer>();
        
        /** 옵저버 추가 */
        public void attach(Observer observer) {
            observers.add(observer);
        }
        
        /** 옵저버 삭제 */
        public void detach(Observer observer) {
            observer.remove(observer);
        }
        
        /** 옵저버들에게 알림 */
        public List<String> notifyObservers(String message) {
            List<String> meesages = new ArrayList<>();
            for (Observer observer : observers) {
                messages.add(observer.receive(message));
            }
            return messages;
        }
    }
    public class User extends Subject {
    }

    다음으로, 옵저버를 추가하고, 제거하고, 메시지를 알리는 기능들을 정의하는 Subject 클래스를 정의한다. 그리고 User 클래스에서 Subject 클래스를 상속받도록 한다. 즉, User 클래스에서 옵저버들을 관리할 수 있는 것이다.

     

    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 message = "안녕하세요."
            List<String> messages = user.notifyObservers(message);
            printMessage(messages);
            
            user.detach(chatRoom);
            message = "Hello World";
            messages = user.notifyObservers(message);
            printMessage(messages);
        }
        
        private void printMessage(List<String> messages) {
            for (String message : messages) {
                System.out.println(message);
            }
        }
    }

    마지막으로, Client 클래스에서 코드가 어떤 식으로 수정되었는지 보겠다. 의존성이 많이 제거된 것을 확인할 수 있다.


    출처

    https://victorydntmd.tistory.com/296

     

    728x90

    댓글

Designed by Tistory.