swift 디자인 패턴 - 옵저버 패턴

swift 디자인 패턴 - 옵저버 패턴

 

목표 : 데이터의 변화를 통보하는 방법 이해하기

옵서버 패턴을 통한 통보의 캡슐화 방법 이해하기

 

 

✅여러가지 방식으로 성적 출력하기

 

입력된 성적값을 출력하는 프로그램 코드 작성해보자

 

필요한 클래스는 입력된 점수를 저장하는 ScoreRecord 클래스와 점수를 목록의 형태로 출력하는 DataSheetVIew 클래스가 필요하다.

 

그리고 성적이 입력된 경우에는, 즉 ScoreRecord 클래스의 addScore 메서드가 실행될 때 성적을 출력하려면 ScoreRecord 클래스는 DataSheetView 객체를 참조해야 한다.

 

 

ScoreRecord 클래스의 addScore 메서드가 호출되면 ScoreRecord 클래스는 자신의 필드인 scores 객체에 점수를 추가한다.

그리고 DataSheetView 클래스의 update 메서드를 호출함으로써 성적을 출력하도록 요청한다.

 

또한 DataSheetView 클래스는 ScoreRecord 클래스의 getScoreRecord 메서드를 호출해 출력할 점수를 구한다.

이때 DataSheetView 클래스의 update 메서드에서는 구한 점수 중에서 명시된 개수(viewCount 변수)만큼의 점수만 출력한다.

 

[예제 코드]

import Foundation
import CoreLocation

class ScoreRecord{
    private var scores:[Int] = [Int]()                          // 점수를 저장함
    private var dataSheetView:DataSheetView? = nil               // 목록 형태로 점수를 출력하는 클래스 참조 변수
    
    func setDataSheetView(dataSheetView: DataSheetView){
        self.dataSheetView = dataSheetView
    }
    
    
    //새로운 점수를 추가함
    func addScore(score:Int){
        scores.append(score)        //scores 목록에 주어진 점수를 추가함
        dataSheetView?.update()     //scores 가 변경됨을 통보함
    }
    
    
    func getScoreRecord()->[Int]{
        return scores
    }
}

class DataSheetView{
    
    private var scoreRecord:ScoreRecord?        //점수 저장 클래스 참조변수
    private var viewCount:Int?                  //저장된 점수의 개수
        
    init(scoreRecord: ScoreRecord? = nil, viewCount: Int? = nil) {
        self.scoreRecord = scoreRecord
        self.viewCount = viewCount
    }
    
    //점수의 변경을 통보 받아서 갱신하는 역할
    func update(){
        
        //점수를 조회함
        if let record = scoreRecord?.getScoreRecord() ,
            let viewCount = viewCount{
            
            self.displayScores(record: record, viewCount: viewCount)
        }
    }
    
    //점수를 출력하는 역할
    func displayScores(record:[Int] ,viewCount:Int){
        print("List of \(viewCount) entries ")
        
        var i = 0
        
        while i < record.count && i < viewCount {
            print("\(record[i]) ")
            i += 1
        }
        
        print("")
    }
    
}

//점수저장 객체
let scoreRecord = ScoreRecord()

//3개까지의 점수만 출력하는 객체
let dataSheetView = DataSheetView(scoreRecord: scoreRecord, viewCount: 3)

//점수 시트 설정
scoreRecord.setDataSheetView(dataSheetView: dataSheetView)

for i in 1...5{
    let score = i * 10
    
    print("adding \(score)")                                                //점수 추가
    
    //10 20 30 40 50을 추가함. 추가할 때마다 최대 3개의 점수만 출력함
    scoreRecord.addScore(score: score)                                      //저장된 점수 목록 출력
}

/*
 [출력]
 adding 10
 List of 3 entries
 10

 adding 20
 List of 3 entries
 10
 20

 adding 30
 List of 3 entries
 10
 20
 30

 adding 40
 List of 3 entries
 10
 20
 30

 adding 50
 List of 3 entries
 10
 20
 30
 */

 

DataSheetView 클래스의 update 메서드가 실행될 때는 ScoreRecord 클래스에서 점수를 조회하는 getScoreRecord 메소드를 호출하려고 ScoreRecord 객체를 DataSheetView 클래스의 생성자에 전달한다.

 

마찬가지로 ScoreRecord 클래스는 점수가 추가될 때, 즉, addScore 메서드가 호출될 때 DataSheetView의 update 메서드를 호출할 필요가 있다. 

 

이를 위해 Client 클래스에서는 ScoreRecord 클래스의 setDataSheetView 메서드를 호출해 DataSheetView 객체를 전달한다.

 

 

문제점

[시나리오1]성적을 다른 형태로 출력하고 싶다면 어떤 작업을 해야 하나? 

예) 성적을 목록 형태로 출력 하지 않고, 성적의 최소/최대 값만 출력하려면?

 

 

[시나리오2]여러가지 형태의 성적을 동시 혹은 순차적으로 출력하려면 어떤 변경 작업을 해야 하나?

예) 성적이 입력되었을 때 최대 3개 목록, 최대 5개 목록, 최소/최대 값을 동시에 출력하거나 처음에는 목록으로 출력하고 나중에는 최소 /최대 값을 출력하려면?

 

 

✅ [시나리오1] 

점수가 입력되면 점수 목록을 출력하는 대신 최소/최대 값만 출력하려면 기존의 DataSheetView 클래스 대신 최소/최대 값을 출력하는 MinMaxView 클래스를 추가할 필요가 있다.

 

그리고 ScoreRecord 클래스는 DataSheetView 클래스가 아니라 MinMaxView 클래스에 성적 변경을 통보할 필요가 있다.

 

[예제 코드2]

import Foundation
import CoreLocation



class ScoreRecord{
    private var scores:[Int] = [Int]()                          // 점수를 저장함
    private var minMaxView:MinMaxView? = nil
    
    //MinMaxView를 설정함
    func setMinMaxView(minMaxView: MinMaxView){
        self.minMaxView = minMaxView
    }
    
    
    //새로운 점수를 추가함
    func addScore(score:Int){
        scores.append(score)        //scores 목록에 주어진 점수를 추가함
        minMaxView?.update()        //MinMaxView에게 점수의 변경을 통보함
    }
    
    
    func getScoreRecord()->[Int]{
        return scores
    }
    
}

class MinMaxView{
    
    private var scoreRecord:ScoreRecord?        //점수 저장 클래스 참조변수
    private var viewCount:Int?                  //저장된 점수의 개수
        
    
    init(scoreRecord: ScoreRecord? = nil, viewCount: Int? = nil) {
        self.scoreRecord = scoreRecord
        self.viewCount = viewCount
    }
    
    //점수를 통보 받음
    func update(){
        
        //점수를 조회함
        if let record = scoreRecord?.getScoreRecord() {
            //최소 최대 값을 출력함
            self.displayMinMax(record: record)
        }
    }
    
    //점수를 출력하는 역할
    func displayMinMax(record:[Int]){
        
        let min = record.min() ?? 0
        let max = record.max() ?? 0
        
        print("min: \(min) , max : \(max) ")
        print("")
    }
    
}



//점수저장 객체
let scoreRecord = ScoreRecord()

let minMaxView = MinMaxView(scoreRecord: scoreRecord)

//점수 출력 시트 설정
scoreRecord.setMinMaxView(minMaxView: minMaxView)

for i in 1...5{
    let score = i * 10
    
    print("adding \(score)")                                                //점수 추가
    
    //10 20 30 40 50을 추가함. 추가할 때마다 최대 3개의 점수만 출력함
    scoreRecord.addScore(score: score)                                      //저장된 점수 목록 출력
}




/*
 [출력]
 
 adding 10
 min: 10 , max : 10

 adding 20
 min: 10 , max : 20

 adding 30
 min: 10 , max : 30

 adding 40
 min: 10 , max : 40

 adding 50
 min: 10 , max : 50

 */

 

MinMaxView 클래스의 update 메서드는 ScoreRecord 클래스의 getScoreRecord 메서드를 통해 구한 점수를 바탕으로 displayMinMax 메서드를 호출해 최소 값과 최대 값을 출력한다.

 

이때 displayMinMax 메서드는 min 메서드와 max 메서드를 이용해 최소값과 최대값을 구한다.

 

이제 ScoreRecord 클래스는 점수가 입력되면 MinMaxView 클래스를 이용해서 최소/최대 값을 출력할 수가 있다.

 

기존 ScoreRecord 클래스의 addScore 메서드를 직접적으로 변경해서 MinMaxView 클래스의 update 메서드를 호출했기 때문이다.

 

문제는 이러한 수정은 OCP에 위배된다. 점수가 입력되었을 때 지정된 특정 대상 클래스(DataSheetView) 에게 고정적으로 통보하도록 코드가 작성되어 있는데, 

 

다른 대상 클래스(MixMaxView 클래스)에게 점수가 입력되었음을 통보하려면 ScoreRecord 클래스의 변경이 불가피하기 때문이다.

 

 

[시나리오2] 동시 혹은 순차적으로 성적을 출력하는 경우

성적이 입력되었을 때 최대 3개목록, 최대 5개 목록, 최소/최대 값을 동시에 출력하거나 처음에는 목록으로 출력하고 나중에는 최소/최대 값을 출력하려면 어떻게 해야 할까?

 

실제 출력 기능을 고려하기 전에 점수가 입력되면 복수개의 대상 클래스를 갱신하는 구조를 먼저 생각해봐야 한다.

 

목록으로 출력하는 것은 DataSheetView 클래스를 활용하고, 최소/최대 값을 출력하는 것은 MinMaxView 클래스를 활용할 수 있다.

 

그러므로 ScoreRecord 클래스는 2개의 DataSheetView 객체(3개목록, 5개 목록 출력을 위한 객체)와 1개의 MinMaxView 객체에게 성적 추가를 통보할 필요가 있다. 이를 위해서 ScoreRecord 클래스는 다시 변경되어야 한다.

 

[예제 코드 3]

import Foundation
import CoreLocation



class ScoreRecord{
    
    private var scores:[Int] = [Int]()                          // 점수를 저장함
    
    
    //복수 개의 DataSheetView
    private var dataSheetViews:[DataSheetView] = [DataSheetView]()

    private var minMaxView:MinMaxView? = nil
    
    
    //DataSheetView를 추가
    func addDataSheetView(dataSheetView:DataSheetView){
        dataSheetViews.append(dataSheetView)
    }
    
    //MinMaxView를 설정함
    func setMinMaxView(minMaxView: MinMaxView){
        self.minMaxView = minMaxView
    }
    
    
    //새로운 점수를 추가함
    func addScore(score:Int){
        
        scores.append(score)        //scores 목록에 주어진 점수를 추가함
       
        for dataSheetView in dataSheetViews{

            //각 DataSheetView에 값의 변경을 통보함
            dataSheetView.update()
        }
        
        //MinMaxView에 값의 변경을 통보함
        minMaxView?.update()
    }
    
    func getScoreRecord()->[Int]{
        return scores
    }
    
}


//위의 예제와 동일
class DataSheetView{
    
    private var scoreRecord:ScoreRecord?        //점수 저장 클래스 참조변수
    private var viewCount:Int?                  //저장된 점수의 개수
        
    
    init(scoreRecord: ScoreRecord? = nil, viewCount: Int? = nil) {
        self.scoreRecord = scoreRecord
        self.viewCount = viewCount
    }
    
    //점수의 변경을 통보 받아서 갱신하는 역할
    func update(){
        
        //점수를 조회함
        if let record = scoreRecord?.getScoreRecord() ,
            let viewCount = viewCount{
            
            self.displayScores(record: record, viewCount: viewCount)
        }
    }
    
    
    //점수를 출력하는 역할
    func displayScores(record:[Int] ,viewCount:Int){
        print("List of \(viewCount) entries ")
        
        var i = 0
        
        while i < record.count && i < viewCount {
            print("\(record[i]) ")
            i += 1
        }
        
        print("")
    }
    
}

//위의 예제와 동일
class MinMaxView{
    
    private var scoreRecord:ScoreRecord?        //점수 저장 클래스 참조변수
    private var viewCount:Int?                  //저장된 점수의 개수
        
    
    init(scoreRecord: ScoreRecord? = nil, viewCount: Int? = nil) {
        self.scoreRecord = scoreRecord
        self.viewCount = viewCount
    }
    
    //점수를 통보 받음
    func update(){
        
        //점수를 조회함
        if let record = scoreRecord?.getScoreRecord() {
            //최소 최대 값을 출력함
            self.displayMinMax(record: record)
        }
    }
    
    //점수를 출력하는 역할
    func displayMinMax(record:[Int]){
        
        let min = record.min() ?? 0
        let max = record.max() ?? 0
        
        print("min: \(min) , max : \(max) ")
        print("")
    }
    
}



//점수저장 객체
let scoreRecord = ScoreRecord()

//3개 목록의 DataSheetView 생성
let dataSheetView3 = DataSheetView(scoreRecord: scoreRecord, viewCount: 3)

//5개 목록의 DataSheetView 생성
let dataSheetView5 = DataSheetView(scoreRecord: scoreRecord, viewCount: 5)

//최대값, 최소값 출력 객체 생성
let minMaxView = MinMaxView(scoreRecord: scoreRecord)

//목록출력형식의 객체 설정
scoreRecord.addDataSheetView(dataSheetView: dataSheetView3)
scoreRecord.addDataSheetView(dataSheetView: dataSheetView5)


//점수 출력 시트 설정
scoreRecord.setMinMaxView(minMaxView: minMaxView)

for i in 1...5{
    let score = i * 10
    
    print("✅adding \(score)")
    
    //10 20 30 40 50을 추가함.
    //추가할 때마다 최대 3개 , 최대 5개 목록, 그리고 최소/최대 값이 출력됨
    scoreRecord.addScore(score: score)
}


/*
 ✅adding 10
 List of 3 entries
 10

 List of 5 entries
 10

 min: 10 , max : 10

 ✅adding 20
 List of 3 entries
 10
 20

 List of 5 entries
 10
 20

 min: 10 , max : 20

 ✅adding 30
 List of 3 entries
 10
 20
 30

 List of 5 entries
 10
 20
 30

 min: 10 , max : 30

 ✅adding 40
 List of 3 entries
 10
 20
 30

 List of 5 entries
 10
 20
 30
 40

 min: 10 , max : 40

 ✅adding 50
 List of 3 entries
 10
 20
 30

 List of 5 entries
 10
 20
 30
 40
 50

 min: 10 , max : 50

 */

 

✅ 해결책

 

문제 해결의 핵심은 성적 통보 대상이 변경되더라도 ScoreRecord 클래스를 그대로 재사용할 수 있어야 한다는 점이다.

 

따라서 ScoreRecord 클래스에서 변화되는 부분을 식별하고 이를 일반화 시켜야 한다.

 

ScoreRecord 클래스에서는 통보 대상인 객체를 참조하는 것을 관리해야 하며 addScore 메서드는 각 통보 대상인 객체의 update 메서드를 호출할 필요가 있다.

 

이런 통보 대상 객체의 관리와 각 객체에 update 메서드를 호출하는 기능은 성적 변경뿐만 아니라 임의의 데이터가 변경되었을 때 이에 관심을 가지는 모든 대상 객체에 통보하는 경우에도 동일하게 발생하는 기능이다.

 

따라서 이러한 공통 기능을 상위 클래스 및 인터페이스로 일반화하고 이를 활용해 ScoreRecord를 구현하는 방식으로 설계를 변경하는 편이 좋다.

 

위의 그림을 살펴보면 성적 변경에 관심이 있는 대상 객체를 관리하는 기능을 구현하는 Subject(발행자) 라는 클래스를 정의한 것을 확인 할 수 있다.

 

Subject(발행자) 클래스는 attach 메서드와 detach 메서드로 성적 변경에 관심이 있는 대상 객체를 추가하거나 제거한다.

 

이때 성적 변경의 통보 수신이라는 측면에서 DataSheetView(구독자) 클래스와 MinMaxView(구독자) 클래스는 동일하므로 Subject 클래스는 Observer 인터페이스를 구현함으로써 성적 변경에 관심이 있음을 보여준다.

 

ScoreRecord 클래스의 addScore 메서드가 호출되면 자신의 성적 값을 저장한 후 Subject 클래스의 notifyObservers 메서드를 호출해 DataSheetView와 MinMaxView 클래스에게 성적 변경을 통보한다.

 

그러면 Subject 클래스는 Observer 인터페이스를 통해서 DataSheetView 와 MinMaxView 객체의 update 메서드를 호출한다.

 

[예제 코드4]


import Foundation


//MARK: 구독자 - subscriber
/// 구독자 인터페이스
/// 통보 대상(구독자)
protocol Observer{
    
    //아이디
    var id: String? { get set }
    
    //데이터 변경을 통보했을 때 처리하는 메서드
    func update()
}



//MARK: 발행자 - publisher
/// 추상 클래서 or 인터페이스 :
/// 성적변경에 관심이 있는 대상 객체를 관리함
class Subject{

    /// 통보 대상 목록 (구독자)
    private var observers = [Observer]()
    
    /// 옵저버(통보대상) 추가
    func attach(observer:Observer){
        observers.append(observer)
    }
    
    //옵저버(통보대상) 삭제
    func detach(observer: Observer){
    
        if let index = self.observers.firstIndex(where: {$0.id == observer.id }){
            self.observers.remove(at: index)
        }
    }
    
    
    /// 통보대상(구독자) 목록에서 각 옵저버(구독자)에게 변경을 통보
    func notifyObservers(){
        for observer in observers{
            observer.update()
        }
    }
}


//MARK: 구체적인 발행자
//구체적인 변경 감시 대상 데이터
class ScoreRecord : Subject{
    
    var scores = [Int]()
    
    // 점수 추가
    func addScore(score:Int){
        
        scores.append(score)
        
        //데이터의 변경을 각 옵저버에 통지
        notifyObservers()
    }
    
    func getScoreRecord() -> [Int]{
        return scores
    }
}


//MARK: 구체적인 통보대상(구독자)1
/// 목록 형식으로 출력하는 뷰
class DataSheetView: Observer{
   
    var id: String?

    private var scoreRecord:ScoreRecord?
    
    private var viewCount:Int?
    
    
    init(id: String? = nil, scoreRecord: ScoreRecord? = nil, viewCount: Int? = nil) {
        self.id = id
        self.scoreRecord = scoreRecord
        self.viewCount = viewCount
    }
    
    // 점수의 변경을 통보받아 갱신하는 메서드
    func update() {
        
        //점수를 조회함
        if let record = scoreRecord?.getScoreRecord() ,
            let viewCount = viewCount{
            
            self.displayScores(record: record, viewCount: viewCount)
        }
    }
    
    
    //점수를 출력하는 역할
    func displayScores(record:[Int] ,viewCount:Int){
        print("List of \(viewCount) entries ")
        
        var i = 0
        
        while i < record.count && i < viewCount {
            print("\(record[i]) ")
            i += 1
        }
        
        print("")
    }
    
}



//MARK: 구체적인 통보대상(구독자)2
/// 최소, 최대값 출력하는 뷰
class MinMaxView :Observer{
    
    var id: String?
    
    private var scoreRecord:ScoreRecord?
    
    init(id: String? = nil, scoreRecord: ScoreRecord? = nil) {
        self.id = id
        self.scoreRecord = scoreRecord
    }
    
    func update() {
        //점수를 조회함
        if let record = scoreRecord?.getScoreRecord() {
            //최소 최대 값을 출력함
            self.displayMinMax(record: record)
        }
    }
    
    //점수를 출력하는 역할
    func displayMinMax(record:[Int]){
        
        let min = record.min() ?? 0
        let max = record.max() ?? 0
        
        print("min: \(min) , max : \(max) ")
        print("")
    }
    
}





//MARK: 구체적인 추가된 통보대상(구독자) 3
///합계, 평균 출력하는 뷰
class StatisticsView: Observer{
    
    var id: String?
    
    var scoreRecord: ScoreRecord?
    
    
    init(id: String? = nil, scoreRecord: ScoreRecord? = nil) {
        self.id = id
        self.scoreRecord = scoreRecord
    }
    
    
    func update() {
        if let record = scoreRecord?.getScoreRecord(){
            // 변경 통보시 조회된 점수의 합과 평균을 출력함
            self.displayStatistics(record: record)
        }
    }
    
    private func displayStatistics(record:[Int]){
        var sum = 0
        for score in record{
            sum += score
        }
        
        var average = sum / record.count
        print("합계 : \(sum) , 평균 : \(average)")
    }
    
}



let scoreRecord = ScoreRecord()
let dataSheetView3 = DataSheetView(id: "d3" , scoreRecord: scoreRecord, viewCount: 3)
let dataSheetView5 = DataSheetView(id: "d5" , scoreRecord: scoreRecord, viewCount: 5)
let minMaxView = MinMaxView(id: "m" , scoreRecord: scoreRecord)

scoreRecord.attach(observer: dataSheetView3)
scoreRecord.attach(observer: dataSheetView5)
scoreRecord.attach(observer: minMaxView)


/// 3목록, DataSheetView, 5개목록 DataSheetView, 그리고 MinMaxView가 Observer로 설정됨
for i in 1...5{
    let score = i * 10
    
    print("✅adding \(score)")

    //10 20 30 40 50을 추가함.
    //추가할 때마다 최대 3개 , 최대 5개 목록, 그리고 최소/최대 값이 출력됨
    scoreRecord.addScore(score: score)
}



/// 3개 목록 DataSheetView는 이제 Observer가 아님.
scoreRecord.detach(observer: dataSheetView3)
let statisticView = StatisticsView(id: "s1" , scoreRecord: scoreRecord)
/// statisticView가 Observer로 설정됨.
scoreRecord.attach(observer: statisticView)


print("####################################################################")
print("####################################################################")

/// 이제 5개목록 DataSheetView, MInMaxView, 그리고 StatisticView가 Observer 임
for i in 1...5{
    let score = i * 10
    
    print("✅adding \(score)")

    //10 20 30 40 50을 추가함.
    //추가할 때마다  최대 5개 목록, 그리고 최소/최대 값 , 합/평균이 출력됨
    scoreRecord.addScore(score: score)
}

/*
✅adding 10
List of 3 entries 
10 

List of 5 entries 
10 

min: 10 , max : 10 

✅adding 20
List of 3 entries 
10 
20 

List of 5 entries 
10 
20 

min: 10 , max : 20 

✅adding 30
List of 3 entries 
10 
20 
30 

List of 5 entries 
10 
20 
30 

min: 10 , max : 30 

✅adding 40
List of 3 entries 
10 
20 
30 

List of 5 entries 
10 
20 
30 
40 

min: 10 , max : 40 

✅adding 50
List of 3 entries 
10 
20 
30 

List of 5 entries 
10 
20 
30 
40 
50 

min: 10 , max : 50 

####################################################################
####################################################################
✅adding 10
List of 5 entries 
10 
20 
30 
40 
50 

min: 10 , max : 50 

합계 : 160 , 평균 : 26
✅adding 20
List of 5 entries 
10 
20 
30 
40 
50 

min: 10 , max : 50 

합계 : 180 , 평균 : 25
✅adding 30
List of 5 entries 
10 
20 
30 
40 
50 

min: 10 , max : 50 

합계 : 210 , 평균 : 26
✅adding 40
List of 5 entries 
10 
20 
30 
40 
50 

min: 10 , max : 50 

합계 : 250 , 평균 : 27
✅adding 50
List of 5 entries 
10 
20 
30 
40 
50 

min: 10 , max : 50 

합계 : 300 , 평균 : 30
*/

 

성적 변경에 관심이 있는 대상 객체들의 관리는 Subject 클래스(구독자 인터페이스 or 추상 클래스)에서 구현했다.

 

ScoreRecord 클래스는 Subject 클래스를 상속 받게 함으로써 ScoreRecord 클래스는 DataSheetView 와 MinMaxView를 직접 참조할 필요가 없게되었다.

 

그러므로 ScoreRecord 클래스의 코드를 변경하지 않고도 새로운 관심 클래스 및 객체를 추가/제거 하는 것이 가능해 졌다.

 

코드 아래쪽에 보면 추가로 합계/평균을 출력하는 기능을 구현했다.

StatisticsView 클래스(구독자)를 새롭게 추가하고 , Subject 클래스의 attach 메서드를 호출해 StatisticsView 객체를 ScoreRecord 클래스의 관심 객체로 등록하도록 한다.

 

관심 객체의 관리는 Subject 클래스에서 수행하므로 ScoreRecord 클래스는 어떤 변경도 하지 않고 합계/평균 출력을 할 수 있게 된다.

 

✅ 옵저버 패턴

옵저버 패턴은 데이터의 변경이 발생했을 경우 상대 클래스나 객체에 의존하지 않으면서 데이터의 변경을 통보하고자 할때 유용하다.

예) 새로운 파일 추가, 기존 파일 삭제 될 때 탐색기는 이를 즉시 표시해야함.

탐색기를 복수 개 실행하는 상황이나 하나의 탐색기에서 파일 시스템을 변경했을 때는 다른 탐색기에게 즉각적으로 이 변경을 통보해야 한다.

 

또 차량의 연료가 소진될 때까지의 주행가능 거리를 출력하는 클래스, 연료량이 부족하면 경고 메시지를 보내는 클래스, 연료량이 부족하면 자동으로 근처 주유소를 표시하는 클래스 등에 

 

연료량의 변화를 통보하는 경우도 있다.

 

이 경우에 연료량 클래스는 연료량에 관심을 가지는 구체적인 클래스(주행 가능 거리 출력, 연료량 부족 경고, 근처 주유소 검색)에 직접 의존하지 않는 방식으로 설계하는 것이 바람직하다.

 

Ket point 

옵저버 패턴은 통보 대상 객체의 관리를 Subject 클래스(발행자)와 Observer(구독자) 인터페이스로 일반화 한다.

 

그러면 데이터 변경을 통보하는 클래스 (ConcreteSubject)는 통보 대상 클래스나 객체(ConcrereObserver)에 대한 의존성을 없앨 수 있다. 

 

결과적으로 옵저버 패턴은 통보 대상 클래스나 대상 객체의 변경에도 ConcreteSubject 클래스를 수정 없이 그대로 사용할 수 있도록 한다.

 

 

 

 

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

즉, Subject에서는 Observer의 update 메소드를 호출함으로써 ConcrereSubject의 데이터 변경을 ConcreteObserver에게 통보한다.

 

Subject : ConcreteObserver 객체를 관리하는 요소. Observer 인터페이스를 참조해서 ConcreteObserver를 관리하므로 ConcreteObserver의 변화에 독립적일 수 있다.

 

ConcreteSubject: 변경 관리 대상이 되는 데이터가 있는 클래스.(발행자)

데이터 변경을 위한 메서드인 setState가 있으며 setState에서는 자신의 데이터인 subjectState를 변경하고 Subject의 notifyObservers 메서드를 호출해서 ConcreteObserver 객체에 변경을 통보한다.

 

ConcreteObserver: ConcreteSubject의 변경을 통보받는 클래스. (구독자)

Observer 인터페이스의 update 메서드를 구현함으로써 변경을 통보받는다. 변경된 데이터는 ConcreteSubject의 getState 메서드를 호출함으로써 변경을 조회한다.

 

 

ConcreteSubject가 자신의 상태, 즉 데이터의 변경을 통보하려면 ConcreteObserver가 미리 등록되어 있어야 한다.

그림 9-5에서는 ConcreteSubject s1에 o1 과 o2 가 ConcreteObserver로 등록되어 있는 상태다.

 

이때 ConcreteObserver o1이 ConcreteSubject s1의 상태를 변경하면 s1은 등록된 모든 ConcreteObserver에게 자신이 변경되었음을 통보한다.

 

변경 통보는 실제로는 ConcreteSubject의 상위 클래스인 Subject 클래스의 notifyObservers 메서드를 호출해 이루어진다.

 

그러면 notifyObservers 메서드는 등록된 각 ConcreteObserver의 update 메서드를 호출한다.

 

마지막으로 통보받은 ConcreteObserver o1과 o2는 ConcreteSubject s1의 getState 메서드를 호출함으로써 변경된 상태나 데이터를 구한다.

 

 

test4.zip
0.02MB

 

 

 

 

참고 : JAVA 객체지향 디자인 패턴