[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가 있습니다.
- Java8 Observer 인터페이스 문서 : 옵서버 객체를 구현하기 위한 인터페이스 ( java.util.Observer )
- Java8 Observable 클래스 문서 : 주제 객체를 구현하기 위한 슈퍼 클래스( java.util.Observable )
위에서 구현해 본 옵서버 패턴의 기능들이 구현되어 있고 옵서버의 카운팅, 일괄 삭제 등의 기능과 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
'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 |
댓글