Compound pattern을 만들어보자

안녕하세요! 두두코딩 입니다 ✋
오늘은 Compound pattern 에 대해 알아보겠습니다.

🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

Compound Pattern 이란?

컴파운드 패턴은 일련의 패턴을 함꼐 사용하여, 다양한 디자인 문제를 해결한다. 반복적으로 생길 수 있는 일반적인 문제를 해결하기 위한 용도이다. 쉽게말하면, 다양한 패턴으로 이루어진 패턴이다.

컴파운드 패턴을 쉽게 이해하기 위해, 다양한 패턴을 섞어서 프로그램을 만들어보며 이해해보자.

예시

우리가 가장 먼저 다루었던 “오리”예시를 활용해 시뮬레이터를 만들어보자.

오리 시뮬레이터
🐤 Duck 을 구현해라
🐤 Duck의 특징은 “꽥꽥” 소리를 낸다는 점을 기억해라
🐤 Duck의 종류는 다양할 수 있다는 점을 고려해 만들어라
🐤 종류는 4가지 정도로 만들 것이다. (Mallard, Redhead, DuckCall, Rubber Duck)

위의 특징을 기반으로 코드를 작성해보자.

🌱 코드

Interface로 “꽥꽥” 소리를 작성하도록 만든 후, 새로 생성되는 오리들은 해당 행위를 무조건 작성하도록 함.

public interface Quackable {
	public void Quack();
}

public class MallardDuck implements Quackable {
	public void quack() { System.out.println("Quack"); }
}

public class DuckCall implements Quackable {
	public void quack() { System.out.println("kwak"); }
}

// 다수의 오리를 위와 같이 추가하면 됨
...

시뮬레이션을 위한 코드 부분

// 코드가 길어져 일부분은 생략함
// 자세한 코드는 HeadFirst를 참고하자.
public class DuckSimulator {
    public static void main(String[] args) {
        DuckSimulator simulator = new DuckSimulator();
        simulator.simulate();
    }

    void simulate() {
        Quackable mallardDuck = new MallardDuck();
				...

        System.out.println("\nDuck Simulator");
        simulate(mallardDuck);
				...
    }
    void simulate(Quackable duck) {
        duck.quack();
    }
}

문제1

🙋 위의 오리 시뮬레이터는 잘 작동한다. 만약 거위를 추가하려면 어떻게 해야할까? 해당 문제를 해결하기 위해 가장 적절한 패턴은 무엇일지 생각해보자.

해답은 “어뎁터 패턴” 이다.

Tip 여기서 중요한 점은 “해답”이라고 이야기한 점이다. 디자인 패턴은 정답이 존재하지 않는다. 설계의 차이이고, 접근의 관점 차이이기 때문에 당시 시점에 가장 적절한 행위를 가져다 쓰면 된다는 점을 기억하자.

위의 문제를 해결하기 위한 코드를 작성해보자.

🌱 코드 아래의 거위 코드가 있다고 가정해보자.

// 거위 추가
// DuckSimulator <--- Adaptor ---> Goose
public class Goose {
    public void honk() {
        System.out.println("Honk");
    }
}

위의 거위 코드를 “오리 시뮬레이터”에서 작동하기 위해서는 Quackable 인터페이스를 구축하는 Adaptor를 작성해야한다.

public class GooseAdaptor implements Quackable {
    Goose goose;

    public GooseAdaptor(Goose goose) {
        this.goose = goose;
    }

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

문제2

🙋 위의 시뮬레이터를 통해 만들어진 오리들의 구체적 연구를 위해, “꽥꽥”의 소리를 기록하고 싶다.
이럴 경우 어떻게 코드를 구성해야되며, 어떤 패턴을 사용해야될까?

해답은 “데코레이터 패턴”을 사용하면 된다.

🌱 코드 오리를 카운팅할 수 있는 코드를 작성해보자.

public class QuackCounter implements Quackable{
    Quackable duck;
    // 모든 오리들을 횟수를 세기 위함. 별개로 세고 싶다면, static 사용 안해도 됨.
    static int numberOfQuacks;

    public QuackCounter( Quackable duck) {
        this.duck = duck;
    }

    @Override
    public void quack() {
        duck.quack();
        numberOfQuacks++;
    }

    // static 으로 반환해야하나?
    public static int getQuacks() {
        return numberOfQuacks;
    }
}

작성한 코드를 활용해, simulator에서 데코를 완성해보자.

...

void simulate() {
        // 드리즐 추가를 생각해봅시다.
        Quackable mallardDuck = new QuackCounter(new MallardDuck());
        Quackable redheadDuck = new QuackCounter(new RedheadDuck());
        Quackable duckCall = new QuackCounter(new Duckcall());
        Quackable rubberDuck = new QuackCounter(new RubberDuck());
				...
}

...

문제3

🙋 사용자가 시뮬레이터에 새로운 오리를 추가하다가, 데코하는 부분을 빼먹을수도 있습니다.
해당 부분을 수정하기 위해선 어떤 접근을 해야될까요?

해답은 팩토리패턴이다.

🌱 코드 Factory를 구축하기 위한 Interface 코드이다.

public abstract class AbstractDuckFactory {
    public abstract Quackable createMallardDuck();
    public abstract Quackable createRedheadDuck();
    public abstract Quackable createDuckCall();
    public abstract Quackable createRubberDuck();
}

해당 인터페이스를 갖고 있으면, DuckFactory로 만들 수 있다. 우리는 Counting을 담당하는 Factory와 일반적인 Factory를 두 개 만들고자 한다.

// Counting 담당
public class CountingDuckFactory extends AbstractDuckFactory{
    @Override
    public Quackable createMallardDuck() {
        return new QuackCounter(new MallardDuck());
    }
		...
}

일반 Factory 코드이다.

public class DuckFactory extends AbstractDuckFactory{

    @Override
    public Quackable createMallardDuck() {
        return new MallardDuck();
    }
		...
}

시뮬레이터도 아래와 같이 바뀌게 된다. 즉, 시뮬레이터에서 데코할 필요 없이, 팩토리만 불러주면 자동으로 Counting 될 수 있는 코드가 만들어지며, 좀 더 신뢰성이 올라가게 된다.

public class DuckSimulator {
    public static void main(String[] args) {
        DuckSimulator simulator = new DuckSimulator();
				// 원하는 팩토리 생성
        AbstractDuckFactory duckFactory = new CountingDuckFactory();

        simulator.simulate(duckFactory);
    }

    void simulate(AbstractDuckFactory duckFactory) {
        // 드리즐 추가를 생각해봅시다.
        Quackable mallardDuck = duckFactory.createMallardDuck();
        Quackable redheadDuck = duckFactory.createRedheadDuck();
				...
		}
		...
}

문제4

🙋 여러 오리들에 대해 한꺼번에 묶어서 작업할 수 있는 방법이 있을까요? 예를들면, 특정오리만 빼서 연구하고 싶을때가 있는데요..
해당 부분을 만들기 위해서는 어떤 접근을 해야될까요?

해답은 컴포지트 패턴이다.

🌱 코드 ArrayList를 활용해 오리떼를 묶을 수 있도록 한 코드이다.

public class Flock implements Quackable{
    ArrayList quackers = new ArrayList();

    public void add(Quackable quacker) {
        quackers.add(quacker);
    }
    @Override
    public void quack() {
        Iterator iterator = quackers.iterator();
        while (iterator.hasNext()) {
            Quackable quacker = (Quackable) iterator.next();
            quacker.quack();
        }
    }
}

시뮬레이터 코드이다.

void simulate(AbstractDuckFactory duckFactory) {
        // 드리즐 추가를 생각해봅시다.
        Quackable redheadDuck = duckFactory.createRedheadDuck();
        Quackable duckCall = duckFactory.createDuckCall();
        Quackable rubberDuck = duckFactory.createRubberDuck();

        // Adaptor!!!
        Quackable gooseDuck = new GooseAdaptor(new Goose());

        System.out.println("\nDuck Simulator");

        Flock flockOfDucks = new Flock();

        flockOfDucks.add(redheadDuck);
        flockOfDucks.add(duckCall);
        flockOfDucks.add(rubberDuck);
        flockOfDucks.add(gooseDuck);
				...
}

문제5

마지막이다. 오리가 “꽥꽥” 호출할 때마다 연구원에게 알려줄 수 있는 방법이 있을까?

해답은 옵저버패턴을 활용하면 된다.

옵저버 되는 대상

public interface QuackObserverable {
    public void registerObserver (Observer observer);
    public void notifyObservers ();
}

public class Observerable implements QuackObserverable{
    ArrayList observers = new ArrayList();
    QuackObserverable duck;

    public Observerable(QuackObserverable duck) {
        this.duck = duck;
    }

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

    @Override
    public void notifyObservers() {
        Iterator iterator = observers.iterator();
        while (iterator.hasNext()) {
            Observer observer = (Observer) iterator.next();
            observer.update(duck);
        }
    }
}

위의 코드를 오리 내 구현하면 됨

public class MallardDuck implements Quackable {
    Observerable observerable;
    public MallardDuck() {
        observerable = new Observerable(this);
    }
    @Override
    public void quack() {
        notifyObservers();
    }

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

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

대표적인 컴파운드 패턴

우리가 흔히 사용하는 것중에 가장 대표적으로 쓰이는 Compound Pattern은 MVC 모델이다.

MVC 패턴

적용된 패턴