Observer Pattern

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의합니다.

 


 

설명

 

4남매가 식자재를 판매 창업을 하려고 합니다.

4남매가 각각 창고에 재고관리를 하지 않고, 1개의 창고에 모든 재고를 공유하기로 했습니다.

첫째: 정육점

둘째: 과일가게

셋째: 야채가게

넷째: 종합마트

이렇게 각각 다른 창업을 시작했습니다.

4남매 모두 하나의 창고를 이용해 관리하고, 재고가 변동될 때마다 창고에 의존하고 있는 각 매장에 변동 내용을 알려주고 있습니다.

 

 

정육점에고 고기가 판매된다면 남아있는 재고를 각 매장에 알려주고 고기를 팔아야 할 다른 매장에서도 재고 현황을 확인하고 판매할 수 있습니다.

이렇게 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의하는 것이 "옵저버 패턴"입니다.

 

옵저버 패턴은 모든 옵저버 객체를 관리하고 알림을 대신해줍니다.

N : N 관계가 아닌 1 : N입니다.

각 객체마다 엮여있는 결합도 높은 코드가 아닌 1 : N 관계로 결합도가 낮고 응집성은 높아집니다.

얼마든지 옵저버 객체를 추가할 수 있고, 삭제할 수도 있습니다.

설계된 코드를 변경할 필요도 없습니다.

그리고 각 옵저버 객체의 코드가 바뀌어도 서로에게 영향을 미치지 않습니다.

 


 

구현

 

객체 상태가 바뀌면 동작할 인터페이스를 정의합니다.

바뀐 상태를 갱신시켜주는 update와 그 내역을 보여주는 display를 만듭니다.

 

Observer

package cg.park.designpattern.observer;

public interface Observer {
   public void update(int meat, int fruit, int vegetable);
}

 

DisplayElement

package cg.park.designpattern.observer;

public interface DisplayElement {
   public void display();
}

 

재고의 현재 상태를 알림 받는 기능을 인터페이스로 만듭니다.

생성, 삭제, 알림 


Subject

package cg.park.designpattern.observer;

public interface Subject {
   public void registerObserver(Observer o);
   public void removeObserver(Observer o);
   public void notifyObservers();
}

 

Subject를 상속받아서 재고의 현재 상태를 알림 받는 클래스를 구현합니다.

 

PreorderData

package cg.park.designpattern.observer;

import java.util.*;

public class PreorderData implements Subject {
    private List<Observer> observers;

    private int meat;//고기
    private int fruit;//과일
    private int vegetable;//야채

    public PreorderData() {
        observers = new ArrayList<Observer>();
    }

    public void registerObserver(Observer o) {
        observers.add(o);
    }

    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(meat, fruit, vegetable);
        }
    }

    public void foodMaterialsChange(){
        notifyObservers();
    }

    public void setFoodMaterials(int meat, int fruit, int vegetable){
        this.meat = meat;
        this.fruit = fruit;
        this.vegetable = vegetable;
        foodMaterialsChange();
    }

}

 

그리고 처음에 만든 Observer와 DisplayElement를 상속받아 클래스를 구현합니다.

 

MeatShop

package cg.park.designpattern.observer;

public class MeatShop implements Observer, DisplayElement {

    private int meat;//과일
    private PreorderData preorderData;

    public MeatShop(PreorderData preorderData){
        this.preorderData = preorderData;
        preorderData.registerObserver(this);
    }

    public void update(int meat, int fruit, int vegetable) {
        this.meat = meat;
        display();
    }

    public void display() {
        System.out.println("[정육점] 고기재고: "+meat);
    }
}

 

FruitShop

package cg.park.designpattern.observer;

public class FruitShop implements Observer, DisplayElement {

    private int fruit;//과일
    private PreorderData preorderData;

    public FruitShop(PreorderData preorderData){
        this.preorderData = preorderData;
        preorderData.registerObserver(this);
    }

    public void update(int meat, int fruit, int vegetable) {
        this.fruit = fruit;
        display();
    }

    public void display() {
        System.out.println("[과일가게] 과일재고: "+fruit);
    }

}

 

VegetableShop

package cg.park.designpattern.observer;

public class VegetableShop implements Observer, DisplayElement {

    private int meat;//과일
    private PreorderData preorderData;

    public VegetableShop(PreorderData preorderData){
        this.preorderData = preorderData;
        preorderData.registerObserver(this);
    }

    public void update(int meat, int fruit, int vegetable) {
        this.meat = meat;
        display();
    }

    public void display() {
        System.out.println("[야채가게] 야채재고: "+meat);
    }
}

 

FoodShop

package cg.park.designpattern.observer;

public class FoodShop implements Observer, DisplayElement {

    private int meat;//고기
    private int fruit;//과일
    private int vegetable;//야채
    private PreorderData preorderData;

    public FoodShop(PreorderData preorderData){
        this.preorderData = preorderData;
        preorderData.registerObserver(this);
    }

    public void update(int meat, int fruit, int vegetable) {
        this.meat = meat;
        this.fruit = fruit;
        this.vegetable = vegetable;
        display();
    }

    public void display() {
        System.out.print("[종합마트] 고기재고: "+meat);
        System.out.print(" 과일재고: "+fruit);
        System.out.println(" 야채재고: "+vegetable);
    }
}

 

마지막으로 각 기능을 테스트해봅니다.

 

PreOrder

package cg.park.designpattern.observer;

public class PreOrder {

    public static void main(String[] args) {
        PreorderData preorderData = new PreorderData();

        MeatShop meatShop           = new MeatShop(preorderData);
        FruitShop fruitShop         = new FruitShop(preorderData);
        VegetableShop vegetableShop = new VegetableShop(preorderData);
        FoodShop foodShop           = new FoodShop(preorderData);

        preorderData.setFoodMaterials(10, 20, 30);//오픈 시 재고
        preorderData.setFoodMaterials(5, 20, 30);//고기 5개 판매

        preorderData.removeObserver(vegetableShop);//야채가게는 별도로 창고를 구입하여 이제 연락받지 않음.

        preorderData.setFoodMaterials(5, 10, 30);//과일 10개 판매
    }
}

 

창고의 재고가 달라진 만큼 각 가게에 재고가 갱신되는 것을 확인할 수 있습니다.

 


 

후기

 

옵저버 패턴을 직접 알아보고 구현하는 것을 추천합니다.

모든 디자인 패턴을 모든 코드에 녹일 수는 없겠지만, 기본적으로 알고 있어야 필요할 때 효율적인 디자인 패턴을 사용할 수 있습니다.

감사합니다.

 

Git : https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

+ Recent posts