CS_Step4 옵저버 패턴(Observer Pattern)

장선웅·2022년 7월 15일
0

옵저버 패턴?

  1. 객체들의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록한다.
  2. 객체의 상태에 변화가 있을 때마다 메스드를 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 한다.

간단히 정리하자면, 어떤 객체의 상태가 변화할 때, 그와 연관된 객체들(옵저버를 의미한다.)에게 알림을 보내는 디자인 패턴.

1. 옵저버 패턴은 왜 사용할까?

예를 들어, 어떤 채팅방(Room)이 있다고 가정하자. 이 채팅방을 사용자(User)가 사용한다면, 이 사용자는 채팅방에 말을 할 수 있고, 채팅방은 유저의 말을 들을 수 있다.

	//Room 클래스 구현
	public class Room{
        //Room에서 User가 말하는 것을 받는 메서드
        public void receive(String msg) {
        	System.out.println(msg);
        }
    }
    //User 클래스 구현
    public class User{
    private Room room;
    	//User가 방을 선택하는 메서드
    	public void setRoom(Room room) {
        	this.room = room;
        }
        //User가 방에서 말하는 메서드
        public void talk(String msg) {
        	System.out.println(msg);
        }
    }
	public class Client{
    	public static void main(String args[]) {
        	//User와 Room인스턴스 객체화
            User user = new User();
            Room room = new Room();
            //User가 선택한 방에 입장
            user.setRoom(room);
         	//User가 Room에 입장해서 말하는 메서드 실행
            String msg = "Hello~! Everybody~~";
            user.talk(msg);
        }
    }

현재 User는 하나의 채팅방에 입장을 한 상태이고, 거기서 인사를 건내고 있다. 그런데 User가 만약 여러 채팅 방에 입장을 하게 되었다고 가정해보자. 그리고 유저가 채팅을 입력히면 모든 채팅방에 메세지가 전달되어야 하는 상황이다.

	//Room 클래스 
	public class Room{
    	//Room의 종류를 받을 String 변수
    	public String roomType;
        //Room에서 User가 보낸 메세지를 받는 메서드
        public void receive(String msg) {
        	System.out.println(roomType + "에서 온 메세지 입니다 : " + msg);
        }
    }
    //채팅방 클래스 
    public class Chatting extends Room{
    	//채팅방 생성자
    	public Chatting(String roomType) {
        	this.roomType = roomType;
        }
    }
    //게임방 클래스
    public class Gaming extends Room{
    	//게임방 생성자
    	public Gaming(String roomType) {
        	this.roomType = roomType;
        }
    }
    //코딩방 클래스
    public class Coding extends Room{
    	//코딩방 생성자
        public Coding(String roomType) {
        	this.roomType = roomType;
        }
    }

이처럼 각각 방을 클래스로 정의하고 Room클래스 하나로 캡슐화해서 표현했다.

	//User클래스
    public class User{
    	//Room클래스를 List화 시킨다.
        private List<Room> room;
  		//User가 방을 선택하는 메서드
  		public void setRoom(List<Room> room) {
  			this.room = room;
  		}
  		//User가 방에 말하는 메서드
  		public void talk(String msg) {
  			//List<Room>에 있는 room을 다 돌면서 메세지를 보낸다.(방 입장으로는 받는 것)
  			for(Room : room) {
  				r.receive(msg);
  			}
  		}
    }
public class Client {
  	public static void main(String args[]) {
  		//User 객체 생성
  		User user = new User();
  		//캡슐화 한 Room을 List에 넣는다.
  		List<Room> rooms = new ArrayList<Room>();
  		//rooms 리스트에 각 방의 객체를 불러와 roomType지정
  		rooms.add(new Catting("채팅방");
  		rooms.add(new Catting("게임방");
  		rooms.add(new Catting("코딩방");
  		//유저가 방을 선택(여기서는 모든 방을 선택)
  		user.setRoom(rooms);
  		//보낼 msg입력
  		String msg = "Hello~! Everybody~~";
  		//User가 각각의 Room에 msg전달
  		user.talk(msg);
  	}
}

이처럼 구현 할 수 있다. 하지만 User와 Room 사이에 강한 결합력이 생기게 된다. 이런 상황에서 다른 방이 생기거나 특정 방이 제거되어 메세지를 보내야 한다고 생각해보자. 물론 Room을 List에 담았기 때문에 추가 제거가 가능하지만, 옵저버 패턴을 이용한다면 더욱 객체지향적 구현이 가능해진다. 즉, 이를 사용함으로써, 시스템이 더 유연해지고 객체간 의존성도 떨어지게 된다.


2. 옵저버 패턴 구현 방법

1) 옵저버 생성

채팅방, 게임방, 코딩방을 옵저버로 설정한 뒤, 옵저버 클래스를 정의하여 캡슐화 한다.
상황에 따라서는 인터페이스 또는 추상 클래스로 정의해도 상관 없다.

  	//Observer 클래스
	public class Observer {
  		public String roomType;
  		//Room에서 메세지를 받는 메서드 구현
  		public void receive(String msg) {
  			System.out.println(roomType + "에서 온 메세지 입니다 : " + msg);
  		}
  }
  //채팅방 클래스
  public class Chatting{
  		//채팅룸 생성자
  		public Chatting(String roomType){
  			this.roomType = roomType;
  		}
  }
   //게임방 클래스
  public class Gaming{
  		//게임방 생성자
  		public Gaming(String roomType){
  			this.roomType = roomType;
  		}
  }
   //코딩방 클래스
  public class Coding{
  		//코딩방 생성자
  		public Coding(String roomType){
  			this.roomType = roomType;
  		}
  }

2) 옵저버를 추가, 제거, 메세지를 알리는 기능들을 정의하는 Subject클래스를 정의한다. 그리고 User클래스에서 Subject클래스를 상속받게 하여 User클래스에서 옵저버들을 관리할 수 있도록 한다.

  //Subject 클래스
  public class Subject{
  	private List<Room> observers = new ArrayList<Room>();
  //옵저버에 추가 메서드
  public void attach(Observer observer) {
  	observers.add(observer);
  }
  //옵저버에서 제거 메서드
  public void detach(Observer observer) {
  	observers.remove(observer);
  }
  //옵저버에게 알리는 메서드
  public void toObservers(String msg) {
  	//옵저버 List를 돌며 메세지 전달
  	for(Observer o : observers) {
  		o.receive(msg);
  		}
  	}
  }
  //Subject클래스를 상속받은 User클래스
  public class User extends Subject{}
  //Client 클래스 구현
  public class Client{
  	public static void main(String args[]) {
  		//User객체 생성
  		User user = new User();
  		//각각 룸 객체 생성
  		Chatting chatting = new Chatting("채팅방");
  		Gaming gaming = new Gaming("게임방");
  		Coding coding = new Coding("코딩방");
  		//옵저버 붙이기
  		user.attach(chatting);
  		user.attach(gaming);
  		user.attach(coding);
  		//구분선
  		System.out.println("==========");
  		//msg 보내기
  		String msg = "Hello~! Everybody~~";
  		user.toObserver(msg);
  		//일부 옵저버 떼고 msg보내기
  		user.detach(chatting);
  		msg = "Hi!!";
  		user.toObserver(msg);
  }
  }

위와 같이 하면
채팅방에서 온 메세지 입니다 : Hello~! Everybody~~
게임방에서 온 메세지 입니다 : Hello~! Everybody~~
코딩방에서 온 메세지 입니다 : Hello~! Everybody~~
==========
게임방에서 온 메세지 입니다 : Hi!!
코딩방에서 온 메세지 입니다 : Hi!!

처럼 결과값이 나오게 된다.


3. 옵저버 패턴의 장/단점

장점

  1. 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있다.(객체 사이의 상호의존성을 최소화 할 수 있다 - 느슨한 결합)
  2. 객체지향 프로그래밍의 5원칙(SOLID)중 O에 적합핟.(개방-폐쇄의 원칙)

단점

  1. Observer에게 알림이 가는 순서를 보장할 수 없다.
  2. Observer와 Subject의 관계가 잘 정의되지 않으면 원하지 않는 동작이 발생할 수 있다.

4. 느슨한 결합(Loose Coupling)?

  1. 느슨한 결합이란, 두 객체가 상호작용을 하지만, 서로에 대해 잘 모른다는 것을 의미한다.
  2. 인터페이스를 이용하여 객체간의 느슨한 결합이 가능하다.
  3. 상속을 통한 구현이 아닌 구성을 이용해야한다.
profile
개발을 꿈꾸는 초짜

0개의 댓글