본문 바로가기
DesignPattern

[Design Pattern] Observer Pattern

by 봄석 2019. 10. 15.

[Design Pattern] Observer Pattern 알아보기

 

Observer Pattern에 대한 개념에 대하여 알아보고

예시를 통해 알아보도록 하겠습니다.

 

 

Observer Pattern이란?

한 객체의 상태 변화에 따라 다른 객체의 상태도 연동되도록 일대다 객체 의존 관계를 구성하는 패턴입니다.

 

 

Observer Pattern의 상태를 전달하는 방법

 

Observer Pattern의 상태를 전달하는 방법은 두 가지가 있습니다.

  • PUSH : 주제 객체가 구독 객체에게 상태를 보내는 방식

  • PULL : 구독 객체가 주제 객체에게서 상태를 가져가는(요청하는) 방식

 

어떨 때 쓰면 좋을까??

  • 데이터의 변경이 발생하였을 때 상대 클래스나 객체에 의존하지 않으면서 데이터 변경을 통보하고자 할 때 사용합니다.

 

 

Observer Pattern with Java

 

 

Ex 1 : push 방식

 

public interface Subject {
	public void add(Observer observer);
	public void remove(Observer observer);
	public void notifyObservers();
}
public class DashBoard implements Subject {
    private List<Observer> displays;
    private String content;

    public DashBoard() {
        System.out.println("DashBoard(Display Subject)Create");
        displays = new LinkedList<Observer>();
    }

    @Override
    public void add(Observer observer) {
        displays.add(observer);
    }

    @Override
    public void remove(Observer observer) {
        if (displays.contains(observer)) {
            displays.remove(observer);
        }
    }

    @Override
    public void notifyObservers() {
        for (Observer display : displays) {
            display.update(content);
        }
    }

    public void setMessage(String content) {
        this.content = content;
        notifyObservers();
    }
}
public interface Observer {
	public void update(String content);
}
public class Subscriber implements Observer {

    private String content;

    public Subscriber(Subject subject) {
        subject.add(this);
    }

    @Override
    public void update(String content) {
        this.content = content;
        display();
    }


    private void display() {
        System.out.println("subscriber 1");
        System.out.println("content : " + content);
        System.out.println("\n");
    }
}
public class Subscriber2 implements Observer {

    Subject subject;
    private String content;

    public Subscriber2(Subject subject) {
        subject.add(this);
    }

    @Override
    public void update(String content) {
        this.content = content;
        display();
    }

    private void display() {
        System.out.println("subscriber 2");
        System.out.println("content : " + content);
        System.out.println("\n");
    }
}
public class Messenger {
    public static void main(String[] args) {
DashBoard dashBoard = new DashBoard(); //Subject class create
        new Subscriber(dashBoard);
        new Subscriber2(dashBoard);

        dashBoard.setMessage("some event"); //Subject new message send
    }
}
//result

DashBoard(Display Subject)Create
subscriber 1
content : some event


subscriber 2
content : some event

Subject클래스를 상속하는 DashBoard에 Observer를 등록하고 setMessage메서드를 호출하면 

subscriber에게 이벤트가 전달됩니다.

 

Ex 2 : 마지막 데이터 Pull 기능 추가

public interface Subject {
	public void add(Observer observer);
	public void remove(Observer observer);
	public void notifyObservers();
	public void notifyObserver(Observer observer);
}

위에서 봤던 예의 Subject Interface에 notifyObserver(Observer observer)를 추가하여줍니다.

위 메서드는 특정 옵서버에게만 상태를 전달하여 갱신할 수 있게 하는 메서드입니다.

public class DashBoard implements Subject {
    private List<Observer> displays;
    private String content;

    public DashBoard() {
        System.out.println("DashBoard(Display Subject)Create");
        displays = new LinkedList<Observer>();
    }

    @Override
    public void add(Observer observer) {
        displays.add(observer);
    }

    @Override
    public void remove(Observer observer) {
        if (displays.contains(observer)) {
            displays.remove(observer);
        }
    }

    @Override
    public void notifyObservers() {
        for (Observer display : displays) {
            display.update(content);
        }
    }

    @Override
    public void notifyObserver(Observer observer) {
        observer.update(content);
    }

    public void setMessage(String content) {
        this.content = content;
        notifyObservers();
    }
}
 

Subject 인터페이스를 구현하고 있는 DashBoard클래스에도 메서드를 구현해줍니다.

전달받은 특정 observer 에게만 update를 합니다.

 

public class Subscriber implements Observer {

    private String content;
    private Subject subject;

    public Subscriber(Subject subject) {
        this.subject = subject;
        subject.add(this);
    }

    public void pullContent() {
        System.out.println("Pull Contents is");
        subject.notifyObserver(this);
    }

    @Override
    public void update(String content) {
        this.content = content;
        display();
    }


    private void display() {
        System.out.println("subscriber 1");
        System.out.println("content : " + content);
        System.out.println("\n");
    }
}

Subscriber 클래스에는 subject를 pull 하는 메서드를 호출하여줍니다.

public class Messenger {

    public static void main(String[] args) {
     DashBoard dashBoard = new DashBoard(); //Subject class create
        Subscriber subscriber = new Subscriber(dashBoard);
        Subscriber2 subscriber2 = new Subscriber2(dashBoard);

        dashBoard.setMessage("some event"); //Subject new message send

        subscriber.pullContent();
        subscriber2.pullContent();
    }

}
//result

DashBoard(Display Subject)Create

subscriber 1
content : some event


subscriber 2
content : some event


Pull Contents is
subscriber 1
content : some event


Pull Contents is
subscriber 2
content : some event

이벤트를 전달하였을 때 subscriber에게 데이터를 갱신할 수 있도록 데이터를 전달하고 , 

 

pullContent 하였을 때는 마지막으로 갱신했던 데이터를 보여줍니다.

 

 

 

Ex 3 : Java 내장 API , Observer Interface  & Observable 클래스 사용하기

 

Java에서도 옵서버 패턴을 구현한 내장 API가 있습니다.

위에서 구현해 본 옵서버 패턴의 기능들이 구현되어 있고 옵서버의 카운팅,  일괄 삭제 등의 기능과  setChange() 메서드를 활용해 상태가 업데이트되는 시점을 최적화할 수도 있습니다.

 

public class DashBoard extends Observable {

    private String content;

    public DashBoard() {
        System.out.println("DashBoard(Display Subject)Create");
    }

    public void messageChanged() {
        setChanged();
        notifyObservers();
    }

    public void setMessage(String content) {
        this.content = content;
        messageChanged();

    }
    
    public String getContent() {
        return content;
    }
}
public class Subscriber implements Observer {

    private String content;

    public Subscriber(Observable observable) {
        observable.addObserver(this); //setObserver
    }

    public void display() {
        System.out.println("subscriber 1");
        System.out.println("content : " + content);
        System.out.println("\n");
    }

    @Override
    public void update(Observable obs, Object arg) {
        // received new source

        if (obs instanceof DashBoard) {
            DashBoard company = (DashBoard) obs;
            this.content = company.getContent();
            display();
        }
    }
}
public class Subscriber2 implements Observer{

    private String content;

    public Subscriber2(Observable observable) {
        observable.addObserver(this); //setObserver
    }

    public void display() {
        System.out.println("subscriber 2");
        System.out.println("content : " + content);
        System.out.println("\n");
    }

    @Override
    public void update(Observable obs, Object args) {
        // received new source

        if (obs instanceof DashBoard) {
            DashBoard company = (DashBoard) obs;
            this.content = company.getContent();
            display();
        }
    }
}
public class Messenger {
    public static void main(String[] args) {
        DashBoard company = new DashBoard(); //Subject class create
        Subscriber subscriber1 = new Subscriber(company);
        Subscriber2 subscriber2 = new Subscriber2(company);
        
        company.setMessage("some event"); //Subject new message send
    }
}
//result

DashBoard(Display Subject)Create

subscriber 2
content : some event


subscriber 1
content : some event

 결과를 보고 눈여겨보아야 할 점은 addObserver 한 순서와 반대로 데이터를 갱신한 것을 알 수 있습니다

 

그 이유는 Observable클래스의  notifyObservers(Object arg) 메서드를 호출할 때 아래와 같이 호출하기 때문입니다.

    for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);

위와 같은 방법으로 등록된 옵서버를 역순으로 불러와 update를 하는 것입니다.

 

 

 

Observer Pattern with Kotlin

경매장 예시입니다.

 

interface AuctioneerSubject {
    fun register(vararg bidObservers: BidObserver)
    fun notifyNewBid(newBid: Int)
}
class Auctioneer : AuctioneerSubject {
    private var bid: Int = 0
    private val bidders: MutableList<BidObserver> = mutableListOf()

    override fun register(vararg bidObservers: BidObserver) {
        bidders.addAll(bidObservers)
    }

    override fun notifyNewBid(newBid: Int) {
        val oldBid = bid
        bid = newBid
        bidders.forEach { it.updateBid(oldBid, newBid) }
    }
}

경매인 클래스는 경매인 서브젝트를 구현하며 , 

Observer를 register(등록) , notify(갱신합니다)

 

interface BidObserver {
    fun updateBid(oldBid: Int, newBid: Int)
}
open class Bidder(val number: Int = 0) : BidObserver {
    override fun updateBid(oldBid: Int, newBid: Int) {
        println("Bidder${number} : oldBid is $oldBid, newBid is $newBid")
    }
}

경매 참가자는 이전의 매긴 값(oldBid)과 현재 매긴값 (newBid)를 출력합니다.

fun main() {
    val auctioneer = Auctioneer()
        .apply {
            register(Bidder(1))
            register(Bidder(2))
        }

    auctioneer.notifyNewBid(1)
    auctioneer.notifyNewBid(2)
    auctioneer.notifyNewBid(3)
}
//result
Bidder1 : oldBid is 0, newBid is 1
Bidder2 : oldBid is 0, newBid is 1
Bidder1 : oldBid is 1, newBid is 2
Bidder2 : oldBid is 1, newBid is 2
Bidder1 : oldBid is 2, newBid is 3
Bidder2 : oldBid is 2, newBid is 3

 

 

 

정리

  • Subject는 Observer 구상클래스가 무엇인지, 무엇을 하는지를 알지 못해도 됩니다.
  • Observer의 추가, 제거가 자유롭습니다.
  • 새로운 형식의 Observer를 추가해도 Subject를 변경할 필요가 없습니다.
  • Subject와 Observer를 독립적으로 재사용할 수 있습니다.
  • 변경이 생겨도 서로에게 영향을 주지 않습니다.
  • Subejct와 Observer는 1:n 관계입니다.

즉 , 상호 의존하지 않고 주제는 Observer에게 데이터를 전달할 수 있습니다.

 

 

샘플 보러 가기

https://github.com/qjatjr1108/DesignPattern

 

qjatjr1108/DesignPattern

DesignPattern Sample. Contribute to qjatjr1108/DesignPattern development by creating an account on GitHub.

github.com

 

'DesignPattern' 카테고리의 다른 글

[Design Pattern] Mediator Pattern  (0) 2019.10.18
[Design Pattern] Memento Pattern  (0) 2019.10.17
[Design Pattern] Strategy Pattern  (4) 2019.10.11
[Design Pattern] Flyweight pattern  (4) 2019.10.10
[Design Pattern] Composition Pattern  (2) 2019.10.10

댓글