Skip to content

Latest commit

 

History

History
232 lines (195 loc) · 7.89 KB

File metadata and controls

232 lines (195 loc) · 7.89 KB

옵저버 패턴

채팅 서비스를 만들 때 다음과 같은 폴링(polling) 방식을 적용한다해보자

  • 유저가 특정 토픽메세지를 전달후 저장
  • 유저가 주기적으로 서버에 원하는 토픽전체 메세지를 요청

위 방식의 예제 코드를 보자.

//채팅 서버 클래스
public class ChatServer {
    //특정 토픽의 메세지 리스트
    private Map<String, List<String>> messages = new HashMap<>();

    //토픽에 메세지 추가
    public void addMessage(String topic, String message) {
        //토픽 존재시
        if (this.messages.containsKey(topic)) {
            //토픽에 메세지 추가
            this.messages.get(topic).add(message);
        } else {
            List<String> messageList = new ArrayList<>();
            //메세지 추가
            messageList.add(message);
            //토픽 추가
            this.messages.put(topic, messageList);
        }
    }

    //토픽 메세지 얻기
    public List<String> getMessage(String topic) {
        return this.messages.get(topic);
    }
}
//채팅을 이용하는 클라이언트 클래스
public class Client {
    private final ChatServer chatServer;
    private final String name;

    //채팅 서버와 Client 이름 받기
    public Client(ChatServer chatServer, String name) {
        this.chatServer = chatServer;
        this.name = name;
    }

    //토픽에 메세지 전송
    public void sendMessage(String topic, String message) {
        chatServer.addMessage(topic, message);
    }

    //해당 토픽 메세지 출력
    public void printMessage(String topic) {
        List<String > messages = this.getMessage(topic);
        if(Objects.isNull(messages)) return;
				//메세지 출력
        System.out.printf("======== 이름: %s   토픽: %s =======\n", this.name, topic);
        messages.forEach(System.out::println);
    }

    //해당 토픽 메세지 얻기
    private List<String> getMessage(String topic) {
        return this.chatServer.getMessage(topic);
    }
}
public class Main {
    public static void main(String[] args) {
        //채팅 서버 생성
        ChatServer chatServer = new ChatServer();
        //클라이언트 생성
        Client client1 = new Client(chatServer, "홍길동");
        Client client2 = new Client(chatServer, "아무개");

        //사람인이라는 토픽에 메세지 전송
        client1.sendMessage("사람인", "안녕하세요");
        client2.sendMessage("사람인", "반갑습니다");
        //클라이언트가 서버로부터 메세지 출력 호출
        client1.printMessage("사람인");
        client2.printMessage("사람인");

        //점핏이라는 토픽에 메세지 전송
        client1.sendMessage("점핏", "잘부탁드립니다");
        //클라이언트가 서버로부터 메세지 출력 호출
        client2.printMessage("점핏");
    }
}

결과
======== 이름: 홍길동   토픽: 사람인 =======
안녕하세요
반갑습니다
======== 이름: 아무개   토픽: 사람인 =======
안녕하세요
반갑습니다
======== 이름: 아무개   토픽: 점핏 =======
잘부탁드립니다

예제에서 클라이언트는 메세지를 출력하고 싶을 때마다 채팅 서버에 요청을 보낸다.

이 방식의 문제는 전달된 메세지는 없지만 클라이언트가 주기적으로 서버에 메세지를 요청하면 불필요한 작업이 늘어나 리소스가 낭비된다는 점이다.

옵저버 패턴을 사용하면 메세지 전달을 감지하여 클라이언트가 불필요한 작업을 없앨 수 있다.

패턴을 적용한 예제를 보자

요구사항은 다음과 같다

  • 유저는 특정 토픽을 구독 가능
  • 유저가 구독한 토픽메세지를 전송시 토픽을 구독한 다른 유저는 메세지 확인 가능

Untitled

/**
 * 구독자 인터페이스
 * ** Observer **
 */
public interface Subscriber {
		//전달받은 메세지 처리 메소드
    void handleMessage(String message);
}
/**
 * Subscriber 인터페이스 구현체
 * ** ConcreteObserver **
 */
public class User implements Subscriber{
    //유저 이름
    private final String name;
    
    //생성자
    public User(String name) {
        this.name = name;
    }
    
    //메세지 처리
    @Override
    public void handleMessage(String message) {
        System.out.printf("%s --- %s 수신\n",message,this.name);
    }
    //Getter
}
/**
 * 채팅 서버 클래스
 * ** Subject **
 */
public class ChatServer {
    //각 토픽에 구독자들을 저장
    public Map<String, List<Subscriber>> subscribers = new HashMap<>();

    //구독 등록
    public void register(String topic, Subscriber subscriber) {
        //토픽 존재시 구독자 추가
        if(this.subscribers.containsKey(topic)) {
            this.subscribers.get(topic).add(subscriber);
            return;
        }
        //존재하지 않을시 토픽 생성후 추가
        List<Subscriber> newSubscribers = new ArrayList<>();
        newSubscribers.add(subscriber);
        this.subscribers.put(topic, newSubscribers);
    }

    //구독 취소
    public void unregister(String topic, Subscriber subscriber) {
        //토픽 존재시 토픽의 구독자 제거
        if(this.subscribers.containsKey(topic)) {
            this.subscribers.get(topic).remove(subscriber);
        }
    }

    //토픽의 구독자들에게 메세지 전송
    public void sendMessage(User user, String topic, String message) {
        // 해당 토픽이 존재시
        if (this.subscribers.containsKey(topic)) {
            String userMessage = user.getName() + ": " + message;
            // 토픽 구독자들에게 메세지 전달
            this.subscribers.get(topic).forEach(s -> s.handleMessage(userMessage));
        }
    }
}
public class Main {
    public static void main(String[] args) {
        ChatServer chatServer = new ChatServer();
        User user1 = new User("홍길동");
        User user2 = new User("아무개");

        System.out.println("==========각 토픽에 구독자가 1명일 때==========");
        chatServer.register("개발", user1);   //구독
        chatServer.register("기획", user2);   //구독
        chatServer.sendMessage(user1, "개발", "개발 토픽에 메세지 전달");   //메시지전송
        chatServer.sendMessage(user2, "기획", "기획 토픽에 메세지 전달");   //메시지전송

        System.out.println("==========구독자 추가후 메세지 전달==========");
        chatServer.register("기획", user1);   //구독
        chatServer.sendMessage(user1, "기획", "기획 구독후 메세지 전달");   //메시지전송

        System.out.println("==========구독 취소후 메세지 전달==========");
        chatServer.unregister("기획", user2);  //구독 취소
        chatServer.sendMessage(user2, "기획", "기획 구독 취소후 메세지 전달");  //메시지 전송
    }
}

결과
========== 토픽에 구독자가 1명일 ==========
홍길동: 개발 토픽에 메세지 전달 --- 홍길동 수신
아무개: 기획 토픽에 메세지 전달 --- 아무개 수신
==========구독자 추가후 메세지 전달==========
홍길동: 기획 구독후 메세지 전달 --- 아무개 수신
홍길동: 기획 구독후 메세지 전달 --- 홍길동 수신
==========구독 취소후 메세지 전달==========
아무개: 기획 구독 취소후 메세지 전달 --- 홍길동 수신

옵저버 패턴을 적용하면 ChatServer(Subject) 에서 User(Observer) 리스트를 가지고 있는 구조가 된다.

ChatServer.sendMessage()메소드 호출시 User(Observer).handleMessage() 메소드를 호출함으로써 유저는 메세지가 전달되는 시점에 처리할 수 있는 구조가 된다.