JUINTINATION

옵서버(Observer) 패턴 본문

JAVA객체지향디자인패턴

옵서버(Observer) 패턴

DEOKJAE KWON 2023. 7. 6. 15:30
반응형

옵서버 패턴이란?

통보 대상 객체의 관리를 Subject 클래스와 Observer 인터페이스로 일반화하여 데이터의 변경이 발생하였을 때 상대 클래스 및 객체에 의존하지 않으면서 데이터 변경을 통보하고자 할 때 데이터 변경을 통보하는 클래스(ConcreteSubject)의 통보 대상 클래스/객체(ConcreteObserver)에 대한 의존성을 제거하여 통보 대상 클래스나 대상 객체의 변경에도 ConcreteSubject 클래스를 수정 없이 사용하는 패턴이다.

다음과 같이 여러 가지 방식으로 성적을 출력하는 프로그램이 있다고 하자.

import java.util.ArrayList;
import java.util.List;

public class Client {
    public static void main(String[] args) {
        ScoreRecord scoreRecord = new ScoreRecord();
        DataSheetView dataSheetView = new DataSheetView(scoreRecord, 3);
        scoreRecord.setDataSheetView(dataSheetView);
        for (int index = 1; index <= 5; index++) {
            int score = index * 10;
            System.out.println("Adding " + score);
            scoreRecord.addScore(score);
        }
    }
}

class ScoreRecord {
    private List<Integer> scores = new ArrayList<Integer>();
    private DataSheetView dataSheetView;
    public void setDataSheetView(DataSheetView dataSheetView) {
        this.dataSheetView = dataSheetView;
    }
    public void addScore(int score) {
        scores.add(score);
        dataSheetView.update();
    }
    public List<Integer> getScoreRecord() {
        return scores;
    }
}

class DataSheetView {
    private ScoreRecord scoreRecord;
    private int viewCount;
    public DataSheetView(ScoreRecord scoreRecord, int viewCount) {
        this.scoreRecord = scoreRecord;
        this.viewCount = viewCount;
    }
    public void update() {
        List<Integer> record = scoreRecord.getScoreRecord();
        displayScores(record, viewCount);
    }
    private void displayScores(List<Integer> record, int viewCount) {
        System.out.print("List of " + viewCount + " entries: ");
        for (int i = 0; i < viewCount && i < record.size(); i++) {
            System.out.print(record.get(i) + " ");
        }
        System.out.println();
    }
}
  • 위 코드의 문제점
    • 성적을 다른 방식으로 출력하고 싶다면 기존의 코드를 수정해야 한다.
      • 유지 보수의 어려움
      • OCP 위반

예를 들어 성적의 최소/최대 값만 출력하기 위해선 코드를 다음과 같이 수정해야 한다.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Client {
    public static void main(String[] args) {
        ScoreRecord scoreRecord = new ScoreRecord();
        MinMaxView minMaxView = new MinMaxView(scoreRecord);
        scoreRecord.setDataSheetView(minMaxView);
        for (int index = 1; index <= 5; index++) {
            int score = index * 10;
            System.out.println("Adding " + score);
            scoreRecord.addScore(score);
        }
    }
}

class ScoreRecord {
    private List<Integer> scores = new ArrayList<Integer>();
    private MinMaxView minMaxView;

    public void setDataSheetView(MinMaxView minMaxView) {
        this.minMaxView = minMaxView;
    }

    public void addScore(int score) {
        scores.add(score);
        minMaxView.update();
    }

    public List<Integer> getScoreRecord() {
        return scores;
    }
}

class MinMaxView {
    private ScoreRecord scoreRecord;
    public MinMaxView(ScoreRecord scoreRecord) {
        this.scoreRecord = scoreRecord;
    }
    public void update() {
        List<Integer> record = scoreRecord.getScoreRecord();
        displayMinMax(record);
    }
    private void displayMinMax(List<Integer> record) {
        int min = Collections.min(record, null);
        int max = Collections.max(record, null);
        System.out.println("Min: " + min + " Max: " + max);
    }
}

기능을 변경하거나 새로운 기능을 추가할 때마다 ScoreRecord 클래스를 수정해야 하므로 OCP를 위반한다.

이를 해결하기 위해 다음과 같이 클래스 다이어그램을 만들 수 있다.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Client {
    public static void main(String[] args) {
        ScoreRecord scoreRecord = new ScoreRecord();
        DataSheetView dataSheetView3 = new DataSheetView(scoreRecord, 3);
        DataSheetView dataSheetView5 = new DataSheetView(scoreRecord, 5);
        MinMaxView minMaxView = new MinMaxView(scoreRecord);
        scoreRecord.attach(dataSheetView3);
        scoreRecord.attach(dataSheetView5);
        scoreRecord.attach(minMaxView);
        for (int index = 1; index <= 5; index++) {
            int score = index * 10;
            System.out.println("Adding " + score);
            scoreRecord.addScore(score);
        }
    }
}

interface Observer {
    void update();
}

class Subject {
    private List<Observer> observers = new ArrayList<>();
    public void attach(Observer observer) {
        observers.add(observer);
    }
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

class ScoreRecord extends Subject {
    private List<Integer> scores = new ArrayList<Integer>();
    public void addScore(int score) {
        scores.add(score);
        notifyObservers();
    }
    public List<Integer> getScoreRecord() {
        return scores;
    }
}

class DataSheetView implements Observer {
    private ScoreRecord scoreRecord;
    private int viewCount;
    public DataSheetView(ScoreRecord scoreRecord, int viewCount) {
        this.scoreRecord = scoreRecord ;
        this.viewCount = viewCount ;
    }
    @Override
    public void update() {
        List<Integer> record = scoreRecord.getScoreRecord() ;
        displayScores(record, viewCount);
    }
    private void displayScores(List<Integer> record, int viewCount) {
        System.out.print("List of " + viewCount + " entries: ");
        for (int i = 0 ; i < viewCount && i < record.size(); i++) {
            System.out.print(record.get(i) + " ");
        }
        System.out.println();
    }
}

class MinMaxView implements Observer {
    private ScoreRecord scoreRecord;
    public MinMaxView(ScoreRecord scoreRecord) {
        this.scoreRecord = scoreRecord;
    }
    @Override
    public void update() {
        List<Integer> record = scoreRecord.getScoreRecord();
        displayMinMax(record);
    }
    private void displayMinMax(List<Integer> record) {
        int min = Collections.min(record, null);
        int max = Collections.max(record, null);
        System.out.println("Min: " + min + " Max: " + max);
    }
}

옵서버 패턴 컬레보레이션

 

Subject : ConcreteObserver 객체를 관리하는 클래스

Observer : 데이터의 변경을 통보받는 인터페이스

ConcreteObserver : ConcreteSubject의 변경을 통보받는 Observer 하위 클래스

ConcreteSubject : 변경 관리 대상이 되는 데이터가 있는 클래스

 

 


옵서버 패턴을 이용한 간단한 예제

  • 방범 센서는 침입자를 감지한다.
    • 관리자가 방범 센서의 경보 수준(1~3)을 설정할 수 있다.
    • 침입자가 감지되었다면, 방 안의 불을 켠다. (경보수준 1, 2, 3에서만 동작)
    • 침입자가 감지되었다면, 알람을 작동한다. (경보수준 2, 3에서만 동작)
    • 침입자가 감지되었다면, 경찰에게 연락한다. (경보수준 3에서만 동작)

클래스 다이어그램

소스코드

import java.util.ArrayList;
import java.util.List;

public class Client {
    public static void main(String[] args) {
        SensorSubject sensorSubject = new SensorSubject();
        sensorSubject.setSecurityLevel(2);
        LightObserver lightObserver = new LightObserver(sensorSubject, 1);
        AlarmObserver alarmObserver = new AlarmObserver(sensorSubject, 2);
        CallPoliceObserver callPoliceObserver = new CallPoliceObserver(sensorSubject, 3);
        sensorSubject.attach(lightObserver);
        sensorSubject.attach(alarmObserver);
        sensorSubject.attach(callPoliceObserver);
        boolean[] isInvadeds = new boolean[] {false, false, true, false, true};
        for (boolean isInvaded : isInvadeds) {
            System.out.println(isInvaded ? "침입 발생" : "평화로운 상태");
            sensorSubject.setIsInvaded(isInvaded);
        }
    }
}

class SensorSubject extends Subject {
    private int securityLevel = 1;
    private boolean isInvaded = false;

    public void setSecurityLevel(int securityLevel) {
        this.securityLevel = securityLevel;
    }

    public int getSecurityLevel() {
        return securityLevel;
    }

    public void setIsInvaded(boolean isInvaded) {
        this.isInvaded = isInvaded;
        notifyObservers();
    }

    public boolean getIsInvaded() {
        return isInvaded;
    }
}

class LightObserver implements Observer {
    private SensorSubject sensorSubject;
    private int securityLevel;

    public LightObserver(SensorSubject sensorSubject, int securityLevel) {
        this.sensorSubject = sensorSubject;
        this.securityLevel = securityLevel;
    }

    @Override
    public void update() {
        if (sensorSubject.getIsInvaded()) {
            alert();
        }
    }

    private void alert() {
        if (sensorSubject.getSecurityLevel() >= 1) {
            System.out.println("방 안의 불을 켠다.");
        }
    }
}

class AlarmObserver implements Observer {
    private SensorSubject sensorSubject;
    private int securityLevel;

    public AlarmObserver(SensorSubject sensorSubject, int securityLevel) {
        this.sensorSubject = sensorSubject;
        this.securityLevel = securityLevel;
    }

    @Override
    public void update() {
        if (sensorSubject.getIsInvaded()) {
            alert();
        }
    }

    private void alert() {
        if (sensorSubject.getSecurityLevel() >= securityLevel) {
            System.out.println("알람을 작동한다.");
        }
    }
}

class CallPoliceObserver implements Observer {
    private SensorSubject sensorSubject;
    private int securityLevel;

    public CallPoliceObserver(SensorSubject sensorSubject, int securityLevel) {
        this.sensorSubject = sensorSubject;
        this.securityLevel = securityLevel;
    }

    @Override
    public void update() {
        if (sensorSubject.getIsInvaded()) {
            alert();
        }
    }

    private void alert() {
        if (sensorSubject.getSecurityLevel() >= securityLevel) {
            System.out.println("경찰에게 연락한다.");
        }
    }
}
728x90
Comments