03. 옵저버 패턴

AlmondGood·2022년 7월 15일
0

디자인패턴

목록 보기
4/16
post-thumbnail

옵저버 패턴(Observer Pattern)

옵저버는 '감시자'라는 뜻을 가지고 있습니다. '감시'라는 건 어떤 상황이 일어나는지 계속 지켜보는 것이죠.

옵저버 패턴이란, 한 객체의 상태 변화에 따라 다른 객체의 상태도 연동되도록 일대다 객체 의존 관계를 구성 하는 패턴을 말합니다. 다시 말해, A 클래스와 B 클래스가 있다고 한다면, B가 수정되었을 때, 옵저버를 통해 A도 B가 수정된 사실을 알 수 있게 해주는 것입니다.

예를 들면, 감자 가격이 1% 올랐다고 가정해 봅시다.
그러면 감자를 재료로 하는 물건들도 가격이 따라 올라가겠죠.
각각 감자튀김 가격이 5%, 감자칩 가격이 3% 올랐습니다.

감자튀김이랑 감자칩이 감자의 가격이 오른 것을 어떻게 알았을까요?
가격을 찾아볼 수 있는 것도 아닐텐데 말이죠.
여기서 옵저버가 등장합니다.
감자와, 감자튀김/감자칩을 이어주는 역할을 옵저버가 맡음으로써 서로의 수정 사실을 알 수 있게 됩니다.



옵저버 패턴의 구현

예시가 이해가 되셨나요? 그렇다면 이 예시를 코드로 바꾸어 다시 한번 설명해 보겠습니다.

// 옵저버
interface Products {
	void priceUpdate();
}

먼저, 옵저버 인터페이스를 만들었습니다.
이 옵저버를 각 물건들에 상속시켜서 물건들에게 가격 변동을 알리는 역할을 합니다.
즉, 이 옵저버는 물건들의 명단이 추상화된 것 입니다.


// 감자를 재료로 하는 물건들 관리
abstract class PotatoProducts {
	//감자를 재료로 하는 물건 리스트
	private ArrayList<Products> products = new ArrayList<>();

	// 물건 리스트에 추가
  	public void attach(Products product) {
  		products.add(product);
  	}
    // 물건 리스트에서 제거
  	public void detach(Products product) {
  		products.remove(product);
  	}

	// 물건들에게 가격 변동 알림
  	public void notifyPriceUpdate() {
  		for (Products product : products) {
  			product.priceUpdate();
  		}
  	}
}

감자를 재료로 하는 물건들을 관리하는 클래스를 만들었습니다.
이 클래스 안에 물건들을 직접 변수로 넣어 관리하면 객체 간의 독립성이 떨어지고 SOLID에 위배되기 때문에, 리스트로 만들어 간접적으로 관리해줍니다.


class Potato extends PotatoProducts {
 	private int increase = 0;

  	public void setIncrease(int increase) {
  		this.increase = increase;
  		notifyPriceUpdate();
  	}

  	public void getIncrease() {
  		return this.increase;
  	}
}

감자 클래스입니다. PotatoProducts를 통해 가격 변동을 물건들에게 전달해야 하기 때문에 감자에 상속시켜 줍니다.
숫자를 설정하면, notifyPriceUpdate 메소드가 실행되어 물건들에게 가격 변동 알림을 전달합니다.


class FrenchFries implements Products{
  	// 감자튀김 가격 변동 폭
  	private int FFIncrease = 5;
  	// potato의 가격 변동 폭에 접근하기 위해 선언(getIncrease)
    private Potato potato;

    FrenchFries(Potato potato){
        this.potato = potato;
    }

  	@Override
  	public void priceUpdate() {
  		int nowIncrease = FFIncrease * potato.getIncrease();
  		System.out.println("감자튀김 가격이 " + nowIncrease + "% 올랐습니다!");
  	}
}

potato.getIncrease()를 호출하기 위해 감자 객체를 만들어주고,
setIncrease()의 실행 후 호출된 notifyPriceUpdate()를 통해 감자튀김 객체에 접근해서, priceUpdate()를 실행했습니다.


class PotatoChip implements Products{
  	// 감자칩 가격 변동 폭
  	private int PCIncrease = 3;
  	private Potato potato;

 	 PotatoChip(Potato potato) {
        this.potato = potato;
    }

  	@Override
  	public void priceUpdate() {
  		int nowIncrease = PCIncrease * potato.getIncrease();
  		System.out.println("감자칩 가격이 " + nowIncrease + "% 올랐습니다!");
  	}
}

감자튀김 클래스와 동일합니다.


public static void main(String[] args) {

  Potato potato = new Potato();
	
  FrenchFries frenchFries = new FrenchFries(potato);
  PotatoChip potatoChip = new PotatoChip(potato);

  // 물건 관리 리스트에 추가
  potato.attach(frenchFries);
  potato.attach(potatoChip);

  // i가 변화할 때 마다 감자의 가격 변동 폭 설정
  for (int i = 1; i <= 5; i++) {
      potato.setIncrease(i);  // 감자튀김 가격이 5 * i% 올랐습니다!
      System.out.println();   // 감자칩 가격이 3 * i% 올랐습니다!
  }
}

실행 순서를 요약하면,

Potato 인스턴스화 → 물건들 인스턴스화 후 리스트에 추가(attach()) →
감자 가격 설정(potato.setIncrease()) → 각 물건에게 가격 변동 알림(notifyPriceUpdate()) → 오버라이딩한 priceUpdate() 실행

순으로 진행되겠습니다.

결과적으로, 관리 대상 물건들을 유연하게 관리할 수 있고, 객체들 간의 의존성을 떨어뜨려 객체들끼리 직접 상호작용하지 않고, 옵저버를 통해 간접적으로 교류할 수 있었습니다.
이러한 것을 "느슨한 결합" 이라고 합니다.


느슨한 결합(Loose Coupling)

느슨한 결합이란, 두 객체가 상호작용 하긴 하지만 그 둘이 서로에 대해 잘 모른다는 것을 의미합니다.

위의 예시에서는 감자 - 감자튀김/감자칩이 있습니다.
감자는 감자튀김/감자칩의 내부 변수(Increase)를 알 수 없고, 감튀/감자칩 또한 그렇습니다.
하지만 감튀/감자칩은 감자의 내부 변수가 필요하기에 getIncrease()라는 메소드를 통해 값을 전달받았죠.

만약 내부 변수를 직접 접근(Potato.Increase)했다면, 감자의 가격에 변동이 생겼을 때, 감자 내부의 코드를 수정해야 하기 때문에 개방-폐쇄 원칙에 어긋나게 됩니다.



옵저버 패턴의 장단점

장점

  • 실시간으로 한 객체의 변경사항을 다른 객체에 전파할 수 있다.
  • 느슨한 결합으로 시스템이 유연하고 객체간의 의존성을 제거할 수 있다.

단점

  • 너무 많이 사용하게 되면, 상태 관리가 힘들 수 있다.
  • 데이터 배분에 문제가 생기면 자칫 큰 문제로 이어질 수 있다.
  • Thread safe 하지 않아 구독을 신청/취소하는 동안 원하는 결괏값을 얻기 힘들수도 있다.
  • observer를 제때 제거해주지 않으면 메모리 누수가 일어날 수 있다.

    필요없는 옵저버를 제때 detach하지 않을 경우 상태가 변경될 때 마다 필요 없는 함수가 계속 호출된다.

  • 비동기 방식이기 때문에 이벤트 구독을 원하는 순서대로 받지 못할 수 있다.

    옵저버의 추가/삭제가 빈번해질 경우 순서가 꼬일 수 있다.

참고 자료

https://gmlwjd9405.github.io/2018/07/08/observer-pattern.html

https://velog.io/@haero_kim/%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4-%EA%B0%9C%EB%85%90-%EB%96%A0%EB%A8%B9%EC%97%AC%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4#2-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4%EC%9D%98-%EA%B5%AC%ED%98%84-%EC%9B%90%EB%A6%AC

profile
Zero-Base to Solid-Base

0개의 댓글