[DesignPattern] Observer Pattern

윰진·2023년 3월 21일
0

면접

목록 보기
5/11

Reference

📢 공부한 내용을 바탕으로 작성하였습니다. 내용에 문제가 있다면 댓글 또는 메일 jnw__@naver.com 주시면 확인 후 빠른 수정하겠습니다. !

✨ Observer Pattern : 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 (one-to-many) 의존성을 정의

  • 일반적으로 주제 인터페이스옵저버 인터페이스 가 들어있는 클래스 디자인으로 구현

01. 개요

데이터가 업데이트 될 때, 디스플레이를 업데이트 하는 코드를 작성해보자.

PLUS

  • 확장성 : 새로운 디스플레이가 얼마든지 추가되어도 되게 고려해보자.

코드 문제점 찾아보기

public class WeatherData {
	// 인스턴스 변수 선언
    public void measurementChanged() {
    	
        // 게터 메소드를 호출하여 최신 측정값을 가져온다.
        float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        
        // 각 디스플레이를 갱신한다.
        currentConditionsDisplay.update(temp, humidity, pressure);
        statisticsDisplay.update(temp, humidity, pressure);
        forecastDisplay.update(temp, humidity, pressure);
        // ... 
    }
}

a ) 디스플레이가 추가될 때 마다 update 코드를 넣어줘야 한다. {\rightarrow} 캡슐화
b ) 디스플레이 항목과 데이터를 주고 받을 때 공통된 인터페이스를 사용해야 한다.

  • 온도, 습도, 기압을 전달해줘야 한다.

c ) 실행 중에 디스플레이를 더하거나 빼기 어렵다.

02. 옵저버 패턴

1 ) 구조

✨ Subject Interface 와 Observer Interface 로 구성된다. Observer Interface 를 구현한 객체는 모두 Observer Class가 된다.

  • 각 옵저버는 특정 주제(Subject)에 등록하여 연락을 받을 수 있다.

2 ) 느슨한 결합 (Loose Coupling)

💍 객체들이 상호작용할 수 있지만, 서로를 잘 모르는 관계

  • 느슨한 결합을 사용하면 유연성이 좋아진다.

옵저버 패턴에서는

  • Subject 는 Observer 가 특정 interface (Observer Interface)를 구현한다는 사실만 안다.
    • Observer 의 구상 클래스나 Observer 가 어떤 일을 하는지는 관심 없음
  • Observer 는 언제든지 새로 추가 할 수 있다.
    • Subject는 Observer Interface를 구현하는 객체의 목록에만 의존하므로 언제든지 새로운 Observer를 추가할 수 있음
      • Subject가 관심있는건 Observer Interface를 구현했다는 사실
      • 어떤 Observer 객체든 상관없음
  • 새로운 형식의 Observer가 추가 되어도 Subject를 변경할 필요가 없음
  • Subject와 Observer는 독립적으로 재사용 가능
  • Subject나 Observer의 구현 내용이 달라져도 서로에게 영향이 없음
    • Subject나 Observer의 Interface를 구현한다는 것만 지키면 수정사항은 영향이 없음

✨ 느슨하게 결합하는 디자인은 변경사항이 생겨도 유연하게 처리할 수 있는 시스템을 구축할 수 잇게 해준다.

  • 객체 사이의 상호의존성을 최소화하기 때문

03. 코드 개선하기 : Push 방식

1 ) 기상 스테이션 구현

public interface Subject{
	//Observer 를 인자로 받아 등록, 제거하는 함수
	public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    // 등록된 Observer에 연락을 돌리는 함수
    public void notifyObservers(); 
}

public interface Observer{
	// 정보가 변경되었을 때 옵저버에게 전달되는 상태값
	public void update(float temp, float humidity, float pressure);
}

// 모든 Display들이 구현할 interface
// Display 항목을 화면에 표시할 때 메소드 호출
public interface DisplayElement{
	public void display();
}

2 ) Subject Interface 구현하기

public class WeatherData implements Subject{
	private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;
    
    public WeatherData(){
    	observers = new ArrayList<Observer>();
    }
    
    // 상태 변화를 알려줄 대상 추가
    public void registerObserver(Observer o){
    	observers.add(o);
    }
    
    // 상태 변화를 알려줄 대상에서 제거
    public void removeObserver(Observer o){
    	observers.remove(o);
    }
    
    // Observer에게 상태 변화를 알려주는 함수
    public void notifyObservers(){
    	for (Observer observer : observers){
        	observer.update(temperature, humidity, pressure);
        }
    }
    
    // 갱신된 측정값을 받으면 옵저버에게 알림
    public void measurementsChanged(){
    	notifyObservers();
    }
    
    public void setMeasurements(float temperature, float humidity, float pressure){
    	this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

3 ) Display 요소 구현하기

// Display 요소는 Observer 객체이면서 DisplayElement를 구현한다. 
public class CurrentConditionsDisplay implements Observer, DisplayElement{
	private float temperature;
    private float humidity;
    private WeatherData weatherData;
    
    // 생성자에 Subject 가 전달되고, 이를 이용해 해당 Display를 Observer 로 등록 
    public CurrentConditionsDisplay(WeatherData weatherData){
    	this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    // update가 호출되면 온도와 습도가 저장되고 display 함수가 호출됨 
    public void update(float temperature, float humidity, float pressure){
    	this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
    
    public void display(){
    	System.out.println("현재 상태: 온도 " + temperature + "F, 습도 " + humidity + "%");
    }
}

4 ) 테스트 코드

public class WeatherStation{

	public static void main(String[] args){
    	// WeatherData 객체 생성
        WeatherData weatherData = new WeatherData();
        
        // Display 생성자에 Subject를 전달
        CurrentConditionDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        
        // 새로운 기상값이 관측되었다고 가정
        weatherData.setMeasurements(80, 65, 30.4f);
    }
}

04. 코드 개선하기 : Pull 방식

🧸 Subject 의 데이터를 Observer가 필요할 때 마다 당겨오는 방식

  • 기존 update 함수의 parameter 를 제거하고 Subject의 게터 메소드를 활용

Subject에서 알림 보내기 : notifyObservers()

public void notifyObservers(){
	for (Observer observer : observers){
    	observer.update();
    }
}

Observer에서 알림받기 : update()

public interface Observer{
	public void update();
}

public void update() {
	// subject의 게터 메소드 활용 
	this.temperature = weatherData.getTemperature();
    this.humidity = weatherData.getHumidity();
    display();
}

05. Observer Pattern 연습하기

다음 설명과 같은 요구사항이 있다.

  • A1 클래스의 인스턴스 a1, A2 클래스의 인스턴스 a2, A3 클래스의 인스턴스 a3가 있다.
  • A1, A2, A3 클래스의 공통 부모 클래스인 A 클래스가 있다.
  • A 클래스에는 정수(Integer) 타입의 state라는 멤버 변수가 있고, 정적(static) 변수가 아니므로 인스턴스마다 고유의 state 변수를 가지고 있다.
  • A 클래스에는 setState (int s) 메서드가 있고, A 타입의 인스턴스에 대해 해당 메서드를 실행하면 그 객체의 state 변수의 값이 s로 바뀐다.
  • a1 인스턴스의 state 멤버 변수가 변하면, a2, a3 인스턴스도 그와 같은 값으로 state 멤버 변수가 변경되어야 한다.

위와 같은 요구사항을 만족하기 위해 observer 패턴을 적용하려고 한다.

(1) observer pattern을 사용한 class diagram을 개략적으로 그려 보세요.

-> 이미지 삽입

(2) observer pattern을 사용하여 설계한 코드를 자바 또는 C++ 으로 구현해 보세요.

// HWSubject.java
public interface HWSubject {
	public void registerObserver(HWObserver o);
	public void removeObserver(HWObserver o);
	public void notifyObservers();
	public void detectChange(int s);
}

// HWObserver.java
public interface HWObserver {
	public void update();
	public void changeState();
}


// HWConcreteSubject.java
import java.util.ArrayList;
import java.util.List;

public class HWConcreteSubject implements HWSubject {
	private List<HWObserver> observers;
    private int s;

    public HWConcreteSubject(){
    	observers = new ArrayList<HWObserver>();
    }
    
	@Override
	public void registerObserver(HWObserver o) {
		observers.add(o);
		// TODO Auto-generated method stub
		
	}

	@Override
	public void removeObserver(HWObserver o) {
		observers.remove(o);
		// TODO Auto-generated method stub
		
	}

	@Override
	public void notifyObservers() {
		for(HWObserver observer : observers) {
			observer.update();
		}
		// TODO Auto-generated method stub
		
	}

	@Override
	public void detectChange(int s) {
		this.s = s;
		this.notifyObservers();
		// TODO Auto-generated method stub
		
	}
	
	public int getState() {
		return s;
	}

}

// HWConcreteObserverA1.java

import java.util.List;

public class HWConcreteObserverA1 implements HWObserver {

	private int s;
	HWConcreteSubject subject;
	
	public HWConcreteObserverA1(HWConcreteSubject concreteSubject) {
		this.subject = concreteSubject;
		concreteSubject.registerObserver(this);
	}
	
	public void setState(int s) {
		this.s = s;
		changeState();
	}
	
	@Override
	public void update() {
		// TODO Auto-generated method stub
		this.s = subject.getState(); 
        display();
	}

	@Override
	public void changeState() {
		this.subject.detectChange(this.s);
		// TODO Auto-generated method stub
		
	}
    
    public void display(){
    	System.out.println("현재 값: " + s);
    }

}

(3) 초기에 a1, a2, a3의 state는 각각 1, 2, 3이고, c1의 state를 5로 변경 후 각 state를 출력하는 client 코드를 작성해 보세요.

(4) 초기에 a1, a2, a3의 state는 각각 1, 2, 3이고, c2의 state를 5로 변경 후 각 state를 출력하는 client 코드를 작성해 보세요.

(5) 초기에 a1, a2, a3의 state는 각각 1, 2, 3이고, c3의 state를 10로 변경 후 각 state를 출력하는 client 코드를 작성해 보세요.

public class HWClientObserver {
	public static void main(String[] args) {
		
		HWConcreteSubject subject = new HWConcreteSubject();
		HWConcreteObserverA a1 = new HWConcreteObserverA(subject,1);
		HWConcreteObserverA a2 = new HWConcreteObserverA(subject,2);
		HWConcreteObserverA a3 = new HWConcreteObserverA(subject,3);
		
		a1.setState(5);
		a2.setState(5);
		a3.setState(10);
		
	}

}

(6) 조건 변경

  • a1 인스턴스의 state 멤버 변수가 변하면 a2 인스턴스의statue 멤버 변수도 함께 변경된다.
    • a1은 a2의 subject
  • a3 인스턴스의 state 멤버 변수가 변하면, a1과 a2 인스턴스도 그와 같은 값으로 state 멤버 변수가 변경되어야 한다.
    • a3는 a1, a2의 subject

0개의 댓글