관리 메뉴

bright jazz music

Compound Pattern: 5. Observer 패턴 본문

Design Pattern/Compound Pattern

Compound Pattern: 5. Observer 패턴

bright jazz music 2022. 11. 3. 22:49

개별 (오리, QuackableInterfaceForCompoundPattern)객체의 상태 변경을 체크하고 싶어졌다.

따라서 Observer 패턴을 사용한다.

 

  • 우선 Observable 인터페이스를 생성한다. 여기서는 QuackObservable이라고 명명했다.
  • Observable이 바로 관찰 대상이 된다.
  • Observable에는 옵저버를 등록하는 메소드와 옵저버에게 연락하는 메소드가 존재해야 한다.
  • 필요시 옵저버 제거 메소드를 포함할 수도 있다.
//QuackObservable.java

package com.example.designpatternstudy.compoundPattern;



//개별 객체의 동작을 감시하고자 할 때 사용
//여기서는 QuackableInterfaceForCompoundPattern을 의미한다.
public interface QuackObservable {

    //옵저버를 등록하는 메소드
    //Observer 인터페이스를 구현하는 객체라면 어떤 객체든 꽥꽥 소리 내는 걸 감시할 수 있다.
    //여기서는 QuackableInterfaceForCompoundPattern에서 구현한다.
    public void registerObserver(Observer observer);

    //옵저버에 연락을 돌리는 메소드
    public void notifyObservers();
}

 

  • 오리는 QuackableInterfaceForCompound 인터페이스와 그 인터페이스를 상속한 클래스이다.
  • 우리는 오리를 감시하고 싶으므로 QuacableInterfaceForCompound 인터페이스가 QuackObservable(옵저버블 인터페이스)를 확장하도록 한다.
//QuackableInterfaceForCompoundPattern.java

package com.example.designpatternstudy.compoundPattern;
//개별 객체(오리)를 감시하고 싶어서 이 인터페이스로하여금 Observable 인터페이스를 확장하도록 하였다.
public interface QuackableInterfaceForCompoundPattern extends QuackObservable {
    public void quack();
}

QuackableInterfaceForCompoundPattern인터페이스 뿐만 아니라 이 인터페이스를 구현하는 모든 구상 클래스에서도 QuackObservable에 있는 메소드를 구현하도록 만든다.

 

  • 이 때 모든 클래스에서 일일이 등록하고 옵저버를 등록하고 연락하는 메소드를 구현할 수도 있다.
  • 그러나 등록 및 연락용 코드를 Observable 클래스에 캡슐화 하고 구성으로 QuackObservable에 포함시키는 방법도 있다.
  • 이렇게 하면 실제 코드는 한 군데에만 작성해 놓고 QuackObservable이 필요한 작업을 Observable 보조 클래스에 전부 위임하게 만들 수 있다.

 

Observable 보조 클래스를 만들자.

  • Observable에서는 QuackableInterfaceForCompoundPattern이 관찰대상이 되는 데 필요한 모든 기능을 구현한다.
  • Observable 객체를 다른 클래스에 넣은 다음 필요한 작업을 Observable에 위임하도록 만들면 된다.

 

//Observable.java

package com.example.designpatternstudy.compoundPattern;

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


//Observable은 반드시 QuackObservable을 구현해야 한다.
//그래야 QuackObservable에서 정의한 메소드를 Observable에 위임할 수 있다.

public class Observable implements QuackObservable{

	/*
    생성자의 인자로 이 객체를 써서 QuackObservable로서의 행동을 구현할 QuackObservable객체가 전달된다.
    notifyObservers()에서 연락을 돌릴 때 duck을 인자로 전달해서 옵저버에 그 QuackObservable 객체가
    연락을 돌린다는 사실을 알린다.
    */
	List<Observer> observers = new ArrayList<Observer>();
    QuackObservable duck;

    public Observable(QuackObservable duck){
        this.duck = duck;
    }

    @Override //옵저버 등록 코드
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void notifyObservers() {
//        Iterator iterator = observers.iterator();
//        위 코드대로 하면 경고 발생 : Raw use of parameterized class 'Iterator'
        Iterator<Observer> iterator = observers.iterator();

        while (iterator.hasNext()){
            Observer observer = iterator.next(); //연락을 돌리는 코드
            observer.update(duck);
        }
    }
}

 

Observable 보조 객체와 QuackableInterfaceForCompoundPattern 클래스 결합.

  • QuackableInterfaceForCompoundPattern클래스에 Observable 레퍼런스를 추가하고 메소드 실행을 넘겨준다.
//MallardDuck.java


package com.example.designpatternstudy.compoundPattern;



//청둥오리
public class MallardDuck implements QuackableInterfaceForCompoundPattern {

    Observable observable; //Observable 인스턴스 변수를 삽입

    public MallardDuck(){
    	//생성자에서 Obserbable 객체 생성
        //이 때 MallardDuck 객체의 레퍼런스를 인자로 전달.
        observable = new Observable(this);
    }

    @Override
    public void quack() {
        System.out.println("꽥꽥");
        notifyObservers(); //quack() 메소드 호출 시 옵저버들에게 알린다.
    }

    @Override //QuackObservable에서 정의한 메소드. 보조객체에 위임
    public void registerObserver(Observer observer) {
        observable.registerObserver(observer);
    }

    @Override //QuackObservable에서 정의한 메소드. 보조객체에 위임
    public void notifyObservers() {
        observable.notifyObservers();
    }
}

 

 

QuackableInterfaceForCompoundPattern을 구현하는 나머지 클래스들도 아래와 똑같이 해준다.

 

//DuckCall.java

package com.example.designpatternstudy.compoundPattern;



public class DuckCall implements QuackableInterfaceForCompoundPattern {
    Observable observable;

    public DuckCall(){
        observable = new Observable(this);
    }

    @Override
    public void quack(){
        System.out.println("꽉꽉");
        notifyObservers();
    }

    @Override
    public void registerObserver(Observer observer) {
        observable.registerObserver(observer);
    }

    @Override
    public void notifyObservers() {
        observable.notifyObservers();
    }
}

 

//Flock.java
//오리 무리를 만드는 클래스
//composite패턴 사용: 객체들로 구성된 컬렉션을 개별 객체와 같은 방식으로 다룰 수 있게 해준다.
package com.example.designpatternstudy.compoundPattern;

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


//복합객체와 잎 원소에서 같은 인터페이스를 구현해야 한다.
//여기서는 QuackableInterfaceForCompoundPattern이 잎 원소가 된다.
public class Flock implements QuackableInterfaceForCompoundPattern {

    List<QuackableInterfaceForCompoundPattern> quackers
            = new ArrayList<QuackableInterfaceForCompoundPattern>();

    //Flock에 QuackableInterfaceForCompoundPattern(오리)을 추가하는 메서드
    public void add(QuackableInterfaceForCompoundPattern quacker){
        //List에 새로운 QuackableInterfaceForCompoundPattern(오리)추가
        quackers.add(quacker);
    }

    @Override
    public void quack() {
        //반복자(Iterator)패턴 사용
        //오리 리스트(quackers)를 이터레이터로 만든다.
        Iterator<QuackableInterfaceForCompoundPattern> iterator
                = quackers.iterator();

        while (iterator.hasNext()) {
            //이터레이터에 남은 원소가 없을 때까지 반복한다.
            QuackableInterfaceForCompoundPattern quacker = iterator.next();
            quacker.quack();
        }

    }

    @Override
    public void registerObserver(Observer observer) {
        Iterator<QuackableInterfaceForCompoundPattern> iterator
                = quackers.iterator();
        while(iterator.hasNext()){
            QuackableInterfaceForCompoundPattern quacker = iterator.next();
            quacker.registerObserver(observer);
        }
    }

    @Override
    public void notifyObservers() {

    }
}
//GooseAdapter.java

package com.example.designpatternstudy.compoundPattern;
//Goose를 DuckSimulator를 사용해 시뮬레이션 하기 위해 사용하는 어댑터



//어댑터 클래스에서는 타겟 인터페이스를 구현해야 한다.
// 여기서는 QuackableInterfaceForCompoundPattern이 타겟
public class GooseAdapter implements QuackableInterfaceForCompoundPattern {

    Goose goose;
    Observable observable; //추가

    //생성자
    public GooseAdapter(Goose goose){
        this.goose = goose;
        observable = new Observable(this);
    }

    @Override
    public void quack() {
        goose.honk();
    }

    @Override
    public void registerObserver(Observer observer) {
        observable.registerObserver(observer);
    }

    @Override
    public void notifyObservers() {
        observable.notifyObservers();
    }
}
//RedheadDuck.java

package com.example.designpatternstudy.compoundPattern;



public class RedheadDuck implements QuackableInterfaceForCompoundPattern {

    Observable observable;

    public RedheadDuck(){
        observable = new Observable(this);
    }

    @Override
    public void quack() {
        System.out.println("꽥꽥");
        notifyObservers();
    }

    @Override
    public void registerObserver(Observer observer) {
        observable.registerObserver(observer);
    }

    @Override
    public void notifyObservers() {
        observable.notifyObservers();
    }
}
//RubberDuck.java

package com.example.designpatternstudy.compoundPattern;


public class RubberDuck implements QuackableInterfaceForCompoundPattern {
    Observable observable;

    public RubberDuck(){
        observable = new Observable(this);
    }

    @Override
    public void quack(){
        System.out.println("삑삑");
        notifyObservers();
    }

    @Override
    public void registerObserver(Observer observer) {
        observable.registerObserver(observer);
    }

    @Override
    public void notifyObservers() {
        observable.notifyObservers();
    }
}

-----

 

 

 

옵저버 생성

  • 이 인터페이스에는 QuackObservable을 인자로 전달받는 update() 메소드 하나만 존재한다.
//Observer.java

package com.example.designpatternstudy.compoundPattern;

public interface Observer {
    public void update(QuackObservable duck);
}

 

옵저버 인터페이스를 구현하는 옵저버 클래스 생성

  • Observer인터페이스를 구현해야만 QuackObservable 객체를 만들 수 있다.
//Quackologist.java


package com.example.designpatternstudy.compoundPattern;

public class Quackologist implements Observer {
    @Override
    public void update(QuackObservable duck) {
        System.out.println("꽥꽥학자: " + duck + " 가 방금 소리냈다.");
    }
}

 

 

DuckSimulator를 수정하고 테스트

  • Quackologist 를 만들고 오리 무리의 옵저버로 설정
//DuckSimulator.java

package com.example.designpatternstudy.compoundPattern;

public class DuckSimulator {

    public static void initDuckSimul(){
        DuckSimulator simulator = new DuckSimulator();
        //simulate()메서드에 전달할 팩토리 생성(데코레이터 사용)
        AbstractDuckFactory duckFactory = new CountingDuckFactory();

        //파라미터 추가
        simulator.simulateAll(duckFactory);
    }

    //파라미터 추가
    void simulateAll(AbstractDuckFactory duckFactory){
        QuackableInterfaceForCompoundPattern redheadDuck = duckFactory.createRedheadDuck();
        QuackableInterfaceForCompoundPattern duckCall = duckFactory.createDuckCall();
        QuackableInterfaceForCompoundPattern rubberDuck = duckFactory.createRubberDuck();
        QuackableInterfaceForCompoundPattern gooseDuck = new GooseAdapter(new Goose());


//        System.out.println("\n오리 시뮬레이션 게임: 무리 (+ 컴포지트)");

        Flock flockOfDucks = new Flock();
        flockOfDucks.add(redheadDuck);
        flockOfDucks.add(duckCall);
        flockOfDucks.add(rubberDuck);
        flockOfDucks.add(gooseDuck);


        //물오리만 들어가는 Flock객체(Composite객체) 생성
        Flock flockOfMallards = new Flock();

        //개별 물오리(mallard) 생성
        QuackableInterfaceForCompoundPattern mallardOne = duckFactory.createMallardDuck();
        QuackableInterfaceForCompoundPattern mallardTwo = duckFactory.createMallardDuck();
        QuackableInterfaceForCompoundPattern mallardThree = duckFactory.createMallardDuck();
        QuackableInterfaceForCompoundPattern mallardFour = duckFactory.createMallardDuck();

        //개별 물오리를 컴포지트 객체에 추가하기
        flockOfMallards.add(mallardOne);
        flockOfMallards.add(mallardTwo);
        flockOfMallards.add(mallardThree);
        flockOfMallards.add(mallardFour);

        //물오리 무리(flockOfMallards)를 아까 만든 오리 무리(flockOfDucks)에 넣는다.
        flockOfDucks.add(flockOfMallards);

//        System.out.println("\n오리 시뮬레이션 게임: 전체 무리");
//        simulate(flockOfDucks);
//
//        System.out.println("\n오리 시뮬레이션 게임: 물오리 무리");
//        simulate(flockOfMallards);

        
        
        System.out.println("\n오리 시뮬레이션 게임 (+옵저버))");
        
        //Quackologist 를 만들고 오리 무리의 옵저버로 설정
        Quackologist quackologist = new Quackologist();

        flockOfDucks.registerObserver(quackologist);

        simulate(flockOfDucks);


        System.out.println("오리가 소리 낸 횟수:" +
                QuackCountingDecorator.getQuacks() + " 번");

    }
    //데코레이터도 QuackableInterfaceForCompoundPatter 인터페이스이다.
    void simulate(QuackableInterfaceForCompoundPattern duck){
        duck.quack();
    }


}

 

 

테스트

//CompoundPatternTests.java

package com.example.designpatternstudy;

import com.example.designpatternstudy.compoundPattern.DuckSimulator;
import org.junit.jupiter.api.Test;

public class CompoundPatternTests {


    @Test
    public void duckSimulTest(){

//        DuckSimulator duckSimulator = new DuckSimulator();
//        duckSimulator.initDuckSimul();
// 이렇게 했더니 아래와 같은 경고 발생
//        Static member 'com.example.designpatternstudy.compositPattern.DuckSimulator.initDuckSimul()' accessed via instance reference
//        Inspection info: Reports references to static methods and fields via a class instance rather than the class itself.
//        Even though referring to static members via instance variables is allowed by The Java Language Specification, this makes the code confusing as the reader may think that the result of the method depends on the instance.
//                The quick-fix replaces the instance variable with the class name.
//        Example:
//        String s1 = s.valueOf(0);
//        After the quick-fix is applied:
//        String s = String.valueOf(0);
//        클래스의 인스턴스를 통해 static 멤버(여기서는 initDuciSimul())에 접근하는 것도 가능하다. 그러나 이는 코드를 헷갈리게 만든다.
//        독자는 해당 메소드의 결과가 객체에 의존적이라고 생각할 수도 있다.
//        ==> 해당 멤버가 static이라면 객체 생성하여 접근할 필요 없이 그냥 클래스로 접근하라는 뜻.

        DuckSimulator.initDuckSimul();
  
    }

}

/*
오리 시뮬레이션 게임 (+옵저버))
꽥꽥
꽥꽥학자: com.example.designpatternstudy.compoundPattern.RedheadDuck@466276d8 가 방금 소리냈다.
꽉꽉
꽥꽥학자: com.example.designpatternstudy.compoundPattern.DuckCall@5ce8d869 가 방금 소리냈다.
삑삑
꽥꽥학자: com.example.designpatternstudy.compoundPattern.RubberDuck@27eedb64 가 방금 소리냈다.
끽끽
꽥꽥
꽥꽥학자: com.example.designpatternstudy.compoundPattern.MallardDuck@64c63c79 가 방금 소리냈다.
꽥꽥
꽥꽥학자: com.example.designpatternstudy.compoundPattern.MallardDuck@31c7528f 가 방금 소리냈다.
꽥꽥
꽥꽥학자: com.example.designpatternstudy.compoundPattern.MallardDuck@2b76ff4e 가 방금 소리냈다.
꽥꽥
꽥꽥학자: com.example.designpatternstudy.compoundPattern.MallardDuck@7a1234bf 가 방금 소리냈다.
오리가 소리 낸 횟수:7 번
*/

 

Comments