[CS-Design Pattern]옵저버 패턴 & 스트레티지 패턴

지영·2023년 9월 25일
0

CS

목록 보기
72/77

1. 옵저버 패턴

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에게 notify()되며 자동으로 내용이 update()되는 방식. 다수에게 상태를 전달해주는(=상태를 가지고 있는) Subject와 내용을 갱신받는(=의존성을 가지고 있는) Observer(옵저버)로 나눌 수 있다.

  • 사용 상황 : 서로의 정보를 주고 받는 크기가 커지면서 객체의 관계성이 복잡해질 때, 의존성을 기준으로 나누어 가이드라인을 제공하고자 할 때 사용한다.

  • 예시 : 유튜브 채널과 구독자와의 관계와 비슷하다. 구독, 구독 취소, 영상 업로드 등..

  • 특징 : subjectObserver사이는 느슨한 결합성을 가진다.

📜 느슨한 결합성

상호작용은 하지만 서로 잘 모르는 사이!

  • Observer Interface를 구현하여 추가, 교체, 동적 변경이 가능(OCP원칙 보장)하다. 따라서 Observer가 변경된다고 해서 subject의 코드를 바꿀 필요가 없다.

  • 장점

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

    • 복잡도가 증가하고 다수의 Observer 객체를 등록 이후 해지하지 않는다면 메모리 누수가 발생할 수도 있습니다

✔ 적용 예제

  • Publisher 인터페이스
    : Observer을 관리하는 메소드. 옵저버 등록, 삭제, 정보 알리기가 있음.
public interface Publichser{
	public void add(Observer observer);
    public void delete(Observer observer);
    public void notifyObserver(); 
}
  • Observer 인터페이스
    : 정보 update
public interface Observer{
	public void update(String title, String news);

}
  • NewsMachine 클래스
    : Publisher 구현 클래스. 정보를 제공하는 퍼블리셔가 됨.
public class NewMachine implements Publisher{
	private ArrayList<Observer> observers;
    private String title;
    private String news;
    
    public NewMachine(){
    	observers = new ArrayList<>();
    }
    
    @Override
    public void add(Observer observer){
    	observers.add(observer);
    }
    
    @Override
    pubic void delete(Observer observer){
    	int idx = observers.indexOf(observer);
        observers.remove(idx);
    }
    
    @Override public void notifyObserver(){
    	for(Observer observer:observers){
        	observer.update(title, news);
        }
    }
    
    
    public void setNewsInfo(String title, String news){
    	this.title = title;
        this.news = news;
        notifyObserver();
    }
    
    public String getTitle(){
    	return title;
    }
    
    public String getNews(){
    	return news;
    }


}
  • AnnualSubscriber, EventSubscriber 클래스
    Observer를 구현한 클래스들. notifyObserver()를 호출하면서 알려줄 때마다 update가 호출
public class EventSubscriber implements Observer{
	private String newsString;
    private Publisher publisher;
    
    public EventSubscriber(Publisher publisher){
    	this.publisher = publisher;
        publisher.add(this);
    }
    
    @Override 
    public void update(String title, String news){
    	newsString = title + " " + news;
        display();
    }
    
    public void withdraw(){
    	publisher.delete(this);
    }
    
    public void display(){
    	System.out.println("이벤트 유저");
        System.out.println(newsString);
    }
   
}

2. 스트레티지 패턴

새로운 로직을 추가하거나 변경할 때 한 번에 효율적으로 변경하도록 하기 위해, 어떤 동작을 하는 로직을 하나로 묶어 캡슐화하는 방식

  • 사용하는 이유
    • 클래스 간의 차이가 단순히 어떤 공동의 연산을 수행하는 데에 전략 차이인 경우가 많다. 예를 들어, '사자'라는 클래스, '호랑이', '독수리'라는 클래스가 있다면 큰 차이는 '울음소리', '날아가기' 등이 있을 것이다.
      상속을 통해 해당 클래스를 분리하다 보면 중복코드나 수정이 힘든 경우가 생길 수도 있다. 아래의 사진과 같은 경우가 그 예이다.

      이런 경우, 스트레티지 패턴을 이용하면 된다. 'Cry' 인터페이즈(Strategy)를 정의하고, LionCry, TigerCry, EagerCry로 나눌 수 있다. 'Fly'같은 경우 FlyNoWay, FlyWithWings로 구현하여 각각의 '사자', '호랑이', '독수리' 클래스에서 사용할 수 있다.

  • 장점
    • 파생 클래싱을 구현할 때 유리하다. 클래스를 상속하고 메소드를 오버라이딩 하는 대신 단순한 인터페이스를 구현만 하면 되기 때문이다.
    • 시스템에 새로운 Strategy 를 추가하기 쉽다. (OCP원칙 보장)
  • 단점
    • 통신 오버헤드가 클 수 있다. 즉, Strategy 객체에 전달된 인자의 일부가 사용되지 않을 가능성이 생긴다.

✔ 적용 예제

수정 전

  • 현재 시스템의 캡슐화의 단위가 'Robot' 자체이므로 새로운 로봇 추가는 쉽다. 하지만 새로운 로봇에서 기존의 attack/move가 추가되거나 변경하거나 할 때 문제가 발생한다. (코드 중복)

수정 후

  • attack, move을 위한 인터페이스를 만들고 각각 실현한 클래스를 만들어야 한다. 즉, 구체적인 공격, 이동 방식이 MovingStrategy, AttackStrategy 인터페이스에 의해 캡슐화되어 있다.

  • 외부에서 로봇 객체으 attack, move방식을 임의로 바꾸도록 변경해주는 setter메서드가 필요하다.

  • 로봇 클래스

public abstract class Robot {
private String name;
private AttackStrategy attackStrategy;
private MovingStrategy movingStrategy;

public Robot(String name) { this.name = name; }
public String getName() { return name; }
public void attack() { attackStrategy.attack(); }
public void move() { movingStrategy.move(); }

// 집약 관계, 전체 객체가 메모리에서 사라진다 해도 부분 객체는 사라지지 않는다.
// setter 메서드
public void setAttackStrategy(AttackStrategy attackStrategy) {
  this.attackStrategy = attackStrategy; }
public void setMovingStrategy(MovingStrategy movingStrategy) {
  this.movingStrategy = movingStrategy; }
}
https://gmlwjd9405.github.io/2018/07/06/strategy-pattern.html
  • 구체적인 Robot 클래스
public class TaekwonV extends Robot {
public TaekwonV(String name) { super(name); }
}
public class Atom extends Robot {
public Atom(String name) { super(name); }
}
https://gmlwjd9405.github.io/2018/07/06/strategy-pattern.html
  • attack, move 기능에 대한 인터페이스와 구체적인 클래스
// Attack 인터페이스
interface AttackStrategy { public void attack(); }
// Attack 구체적인 클래스
public class MissileStrategy implements AttackStrategy {
  public void attack() { System.out.println("I have Missile."); }
}
public class PunchStrategy implements AttackStrategy {
  public void attack() { System.out.println("I have strong punch."); }
}
https://gmlwjd9405.github.io/2018/07/06/strategy-pattern.html



// Moving 인터페이스
interface MovingStrategy { public void move(); }
// Moving 구체적인 클래스
public class FlyingStrategy implements MovingStrategy {
  public void move() { System.out.println("I can fly."); }
}
public class WalkingStrategy implements MovingStrategy {
  public void move() { System.out.println("I can only walk."); }
}
https://gmlwjd9405.github.io/2018/07/06/strategy-pattern.html

  • 클라이언트에서의 사용
public class Client {
public static void main(String[] args) {
  Robot taekwonV = new TaekwonV("TaekwonV");
  Robot atom = new Atom("Atom");

  /* 수정된 부분: 전략 변경 방법 */
  taekwonV.setMovingStrategy(new WalkingStrategy());
  taekwonV.setAttackStrategy(new MissileStrategy());
  atom.setMovingStrategy(new FlyingStrategy());
  atom.setAttackStrategy(new PunchStrategy());

  /* 아래부터는 동일 */
  System.out.println("My name is " + taekwonV.getName());
  taekwonV.move();
  taekwonV.attack();

  System.out.println()
  System.out.println("My name is " + atom.getName());
  atom.move();
  atom.attack();
}
}
https://gmlwjd9405.github.io/2018/07/06/strategy-pattern.html






profile
꾸준함의 힘을 아는 개발자📍

0개의 댓글