반응형

옵저버(Observer) 패턴 : 뭔가 중요한 일이 일어났을 때 객체들한테 새소식을 알려줄 수 있는 패턴

 

- 객체 쪽에서는 계속해서 정보를 받을지 여부를 실행중에 결정할 수 있다.

 

 

 

가정)

기상청에서 근무하는 개발자 A는 기상 정보 스테이션을 구축하는 프로젝트를 맡게 되었다.

 

기상 시스템에 필요한 정보 목록

- 기상 스테이션 (실제 기상 정보를 수집하는 장비)

- Weather Data (기상 스테이션으로부터 오는 데이터를 추적하는 객체)

- 사용자에게 현재 기상 조건을 보여주는 디스플레이

 

즉,

1. 기상 스테이션에서 습도, 온도, 압력 센서를 수집하고,

2. Weather Data객체가 기상 스테이션에서 수집한 데이터를 Observer(관찰) 하고,

(관찰자 이기 때문에, 데이터 변경이 있으면 업데이트 함.) 

3. 디스플레이에 데이터를 띄어주는 로직이다.

 

.

.

.

 

class WeatherData {
	
   	//온도를 리턴하는 메소드(알아서 가져와준다. 습도, 기압도 동일)
    
	String 온도가져오는메소드() {
    	return 온도
    }
    //습도
    String 습도가져오는메소드() {
    	return 습도
    }

    //기압
   	String 기압가져오는메소드() {
    	return 기압
    }
    
    measurementsChanged() {
    }

}

 

.

.

.

지금 알고 있는 것들!

- Weather 클래스에는 세 가지 측정값(온도,습도,기압)을 알아내기 위한 getter메소드가 있습니다.

- 새로운 기상 측정 데이터가 나올 때마다 measurementsChanged() 메소드가 호출됩니다.

- 세 개의 디스플레이 항목을 구현해야합니다. (현재조건, 기상통계, 기상예보)

- 시스템이 확장 가능해야합니다.

 

.

.

.

 

생각나는대로 대강 예제를 만들어보겠습니다.

public class weatherData {

	//인스턴스 변수 선언
    
    public void measurementsChanged() {
    	
        //이미 구현되어 있는 weatherData의 게터메소드를 써서 최신 측정값을 가져옵니다.
    	float 온도 = getTemperatory();
        float 습도 = getHumidity();
        float 기압 = getPressure();
        
        현재조건디스플레이.update(온도,습도,기압);
        기상통계.update(온도,습도,기압);
        기상예보.update(온도,습도,기압);
        
    }

}


// 해야 할 일.
1. 바뀔 수 있는 부분(즉, 데이터 값이 달라질 수 있는 부분)을 캡슐화 해야합니다.
2. 기상예보.update(온도,습도,기압); -> 데이터를 추가/제거 하기 위한 코드를 재작성해야하는 문제를 고쳐줘야합니다.

 

옵저버(Observer) 패턴의 이해

 

예시)신문사와 신문을 읽는 구독자

 

1. 신문사가 사업을 시작하고 신문을 찍어내기 시작합니다.

2. 신문을 읽고 싶은 사람은 신문사에 구독 신청을 하면 매번 새로운 신문을 배달 받을 수 있습니다.

3. 신문을 더이상 보고싶지 않으면, 구독 해지 신청을 합니다. 그러면, 더이상 신문이 오지 않습니다.

 

옵저버 패턴(Observer)에서는 한 객체의 상태가 바뀌면, 그 객체에 의존하는 다른 객체들에게 연락이 가고,
자동으로 내용이 갱신되는 방식으로 1:N(일대다) 의존성을 정의합니다.

.

.

 


// 옵저버 패턴 : 클래스 다이어그램


public interface Subject {
    
    //구독을 도와주는 옵저버
    void registerObserver(Observer o);
    //구독취소를 도와주는 옵저버
    void removeObserver(Observer o);
    //데이터 변경에 대해서 알림을 주는 옵저버
    void notifyObserver(Observer o);
}

//신문사 인터페이스를 구현하는 클래스
public class ConcreteSubject implements Subject{
    @Override
    public void registerObserver(Observer o) {
    }
    
    @Override
    public void removeObserver(Observer o) {
    }
    
    @Override
    public void notifyObserver(Observer o) {

    }
}

//옵저버 인터페이스
public interface Observer {
    void update();//업데이트 할 인자 값
}

//옵저버 인터페이스를 구현하는 클래스
public class ConcreteObserver implements Observer {
    @Override
    public void update() {
    }
    //기타 옵저버용 메소드
}

 

디자인 원칙 : 서로 상호작용을하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

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

 

 

기상 스테이션 구현

본격적으로 기상스테이션을 구현해보도록 하겠습니다.

 

 

인터페이스

//옵저버 주체(기상청)
public interface Subject {

    //구독을 도와주는 옵저버
    void registerObserver(com.example.myapplication.Observer o);
    //구독취소를 도와주는 옵저버
    void removeObserver(com.example.myapplication.Observer o);
    //데이터 변경에 대해서 알림을 주는 옵저버
    void notifyObserver();
}


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


public interface DisplayElement {
    //디스플레이 항목을 화면에 표시해야하는 경우에 메소드 호출
    void display();
}

인터페이스 구현

public class WeatherData implements Subject{
    private ArrayList observers;
    private float temperacture;
    private float humidity;
    private float pressure;

    public WeatherData() {
        //옵저버 객체들을 저장하기 위해 생성자에서 객체를 생성해줌.
        observers = new ArrayList();
    }

    @Override
    public void registerObserver(com.example.myapplication.Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(com.example.myapplication.Observer o) {
        int i = observers.indexOf(o);
        if(i >= 0) {
            observers.remove(i);
        }
    }

    //데이터 변경을 관찰 후, 업데이트 해주는 메소드
    @Override
    public void notifyObserver() {
        for(int i =0; i<observers.size(); i++) {
            //옵저버 인터페이스 객체
            com.example.myapplication.Observer observer = (com.example.myapplication.Observer) observers.get(i);
            observer.update(temperacture,humidity,pressure);

        }
    }

    // update된 값을 받으면 옵저버들한테 알림.
    public void measurementsChanged() {
        notifyObserver();
    }

    public void setMesasurementsChanged(float temperacture, float humidity, float pressure) {
        this.temperacture = temperacture;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

f

// 디스플레이 : weather객체로부터 변경 사항을 받기 위해서 Observer를 구현합니다.
public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperacture;
    private float humidity;
    private Subject weatherData;

    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        //객체생성시, observer등록
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        Log.d("abcd","결과 : "+temperacture+"\n"+humidity);
    }

    //업데이트 후 띄어줌
    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperacture = temp;
        this.humidity = humidity;
        display();
    }
}

 

public class MainActivity2 extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay display = new CurrentConditionsDisplay(weatherData);

        weatherData.setMesasurementsChanged(12,13,14);
    }
}

D/abcd: 결과 : 12.0 13.0

 

---------- 코틀린(kotlin) ----------

 

 

 

interface Subject {

    //Unit은 자바의 void와 같아 (리턴 값이 없다는 뜻.)
      fun registerObserver(observer: Observer) : Unit
      fun removeObserver(observer: Observer) : Unit
      fun notifyObserver() : Unit
}


//
interface Observer {
    fun update(temp:Float, humidity:Float, pressure:Float)
}

//
interface DisplayElement {
    fun display() : Unit
}
class WeatherData : Subject{

    var observers:ArrayList<Observer>? = null
    var temp:Float? = null
    var humidity:Float? = null
    var pressure:Float? = null

    //인스턴스 생성시에 실행 됨.
    init {
        observers = ArrayList()
    }

    override fun registerObserver(observer: Observer) {
        observers?.add(observer)
    }

    override fun removeObserver(observer: Observer) {
        //인덱스 값을 찾아 줌.
        var index = observers?.indexOf(observer)
        if (index!!  >= 0) {
            observers?.removeAt(index)
        }
    }

    override fun notifyObserver() {
        for(i in 0 until observers?.size!!) {
            val observer = observers?.get(i)
            observer?.update(temp!!,humidity!!,pressure!!)
        }
    }

    fun measurementsChanged() {
        notifyObserver()
    }

    fun setMeasurementsChanged(temp:Float, humidity:Float, pressure:Float) {
        this.temp = temp
        this.humidity = humidity
        this.pressure = pressure
        measurementsChanged()
    }
}
//주생성자와 생성시 메소드 정의
class CurrentConditionDisplay constructor(weatherData: Subject) : Observer,DisplayElement {
    var temp:Float? = null
    var humidity:Float? = null
    var weatherData:Subject? = null

    init {
        this.weatherData = weatherData
        weatherData.registerObserver(this)
    }

    override fun display() {
        Log.d("abcd","결과 : "+temp+"\n"+humidity);
    }

    override fun update(temp: Float, humidity: Float, pressure: Float) {
        this.temp = temp
        this.humidity = humidity
        display()
    }
}
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var weatherData = WeatherData()
        var display = CurrentConditionDisplay(weatherData)
        weatherData.setMeasurementsChanged(12F,13f,15f)
    }
}

D/abcd: 결과 : 12.0 13.0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

+ Recent posts