참고: Head First Design Patterns
Compound [동사]
섞다, 혼합하다
Compound Pattern 이라는 별도의 디자인 패턴 이름이 존재하는 것은 아니다. 말 그대로 여러 디자인 패턴들을 섞었다는 의미이다. 디자인 패턴은 하나만 사용할 때 보다 여러 패턴을 함께 사용했을 때 더 의미가 있는 경우가 많다. 여러 패턴들이 어떻게 함께 사용될 수 있는지 살펴보자
Strategy 패턴에서 살펴보았던 오리 예제를 다시 이용해보자.
public interface Quackable {
public void quack();
}
우선 Quackable
인터페이스이다. 이전과 달리 이번엔 오리들이 이 인터페이스를 구현하도록 만들어보자.
public class MallardDuck implements Quackable {
public void quack() {
System.out.println(“Quack”);
}
}
public class RedheadDuck implements Quackable {
public void quack() {
System.out.println(“Quack”);
}
}
이제 오리 구현체들을 몇가지 더 추가해보자. 가령, 사냥꾼들이 쓰는 오리 피리나 러버덕 이라던가.
public class DuckCall implements Quackable {
public void quack() {
System.out.println(“Kwak”);
}
}
public class RubberDuck implements Quackable {
public void quack() {
System.out.println(“Squeak”);
}
}
이번엔 거위를 한번 구현해보자.
public class Goose {
public void honk() {
System.out.println(“Honk”);
}
}
오리의 quack
와 달리 거위의 울음은 honk
라고 묘사한다. 따라서, 메소드가 호환이 되지 않으므로 거위를 오리처럼 다루기 위해서는 어댑터(Adapter) 클래스가 필요하다.
public class GooseAdapter implements Quackable {
Goose goose;
public GooseAdapter(Goose goose) {
this.goose = goose;
}
public void quack() { goose.honk(); }
}
어떤 이유에서인지 프로그램의 오리들의 quack()
호출의 총 횟수를 세고 싶어 졌다. 우리의 구현체들을 수정하지 않고 어떻게 이 기능을 구현할 수 있을까?
quack()
의 횟수를 세는 로직을 추가하자!public class QuackCounter implements Quackable {
Quackable duck;
static int numberOfQuacks;
public QuackCounter (Quackable duck) {
this.duck = duck;
}
public void quack() {
duck.quack();
numberOfQuacks++;
}
public static int getQuacks() { return numberOfQuacks; }
}
전역 변수 numberOfQuacks
를 이용해 quack()
호출 시 마다 1씩 증가하도록 만들고, getter
메소드를 추가하였다. 이 데코레이터 클래스는 Quackable
타입을 장식하여 우리가 원하는 횟수 세기 기능을 추가해 줄 것이다.
따라서, 클라이언트는 오리를 아래와 같이 선언하게 된다.
Quackable mallardDuck = new QuackCounter(new MallardDuck());
Quackable redheadDuck = new QuackCounter(new RedheadDuck());
...
아, 물론 거위는 quack()
하지 않으므로 포함하지 않는다. 그래도 어댑터를 이용해 Quackable
타입으로 선언해주자.
Quackable gooseDuck = new GooseAdapter(new Goose());
총 quack()
횟수는 아래와 같이 호출할 수 있다.
System.out.println("총 횟수: “ + QuackCounter.getQuacks() + “ 번”);
위 예제에는 한 가지 맹점이 있다. quack()
을 카운팅하기 위해서는 Duck
구현체들이 반드시 QuackCounter
데코레이터 클래스로 감싸져있어야만 한다. 만일 그렇지 못한 구현체가 있다면 카운팅을 할 수 없다.
Duck
의 생성 로직을 한 객체에 몰아넣자! 마침 우리가 배운 Factory 패턴이 적당하다. 추상 팩토리 패턴을 이용해보자.
public abstract class AbstractDuckFactory {
public abstract Quackable createMallardDuck();
public abstract Quackable createRedheadDuck();
public abstract Quackable createDuckCall();
public abstract Quackable createRubberDuck();
}
이제 구현체를 만들어보자.
public class DuckFactory extends AbstractDuckFactory {
public Quackable createMallardDuck() { return new MallardDuck(); }
public Quackable createRedheadDuck() { return new RedheadDuck(); }
public Quackable createDuckCall() { return new DuckCall(); }
public Quackable createRubberDuck() { return new RubberDuck(); }
}
public class CountingDuckFactory extends AbstractDuckFactory {
public Quackable createMallardDuck() { return new QuackCounter(new MallardDuck()); }
public Quackable createRedheadDuck() { return new QuackCounter(new RedheadDuck()); }
public Quackable createDuckCall() { return new QuackCounter(new DuckCall()); }
public Quackable createRubberDuck() { return new QuackCounter(new RubberDuck()); }
}
Duck
구현체와 Counter
구현체의 팩토리 클래스를 각각 만들었다. 이제 아래와 같이 클라이언트 코드를 수정할 수 있겠다.
AbstractDuckFactory duckFactory = new CountingDuckFactory();
Quackable mallardDuck = duckFactory.createMallardDuck();
Quackable redheadDuck = duckFactory.createRedheadDuck();
...
이제 생성 로직까지 잘 정리하였다. 그런데, 이 수많은 오리들을 어떻게 효율적으로 관리할 수 있을까? 역시 Iterator 만한 게 없다. 여기에 함께 배웠던 Composite 패턴도 적용하여 오리들의 quack()
을 일괄 호출할 수 있는 오리 군집을 구현해보자.
public class Flock implements Quackable {
ArrayList quackers = new ArrayList();
public void add(Quackable quacker) { quackers.add(quacker); }
public void quack() {
Iterator iterator = quackers.iterator();
while (iterator.hasNext()) {
Quackable quacker = (Quackable)iterator.next();
quacker.quack();
}
}
}
이제 Flock
의 quack()
한 번으로 군집이 담고있는 모든 오리들의 quack()
을 일괄적으로 호출할 수 있게 되었다. 물론, Flock
역시 Quackable
의 구현체이므로, Flock
은 또 다른 Flock
인스턴스들을 자식으로 가질 수 있다. Composite 패턴의 장점을 잘 담고 있다.
오리들을 한 번에 제어하는 것도 물론 중요하지만, 각각의 오리들을 관찰할 필요도 있다. 당연히, Observer 패턴이 등장 할 차례이다.
public interface QuackObservable {
public void registerObserver(Observer observer);
public void notifyObservers();
}
우선 Observable
클래스, 즉 Subject에 해당하는 인터페이스를 만들어보자.
public interface Quackable extends QuackObservable {
public void quack();
}
그리고, 구현해두었던 Quackable
인터페이스가 이를 상속하도록 extends
코드를 추가하자. 이렇게 함으로써 구현체들의 코드에는 손 대지 않아도 된다.
이전 Observer 패턴을 다루면서 구현하였던 내용과는 조금 다르게 구현해보자. Observable
구현체를 만들어보겠다.
public class Observable implements QuackObservable {
ArrayList observers = new ArrayList();
QuackObservable duck;
public Observable(QuackObservable duck) {
this.duck = duck;
}
public void registerObserver(Observer observer) { observers.add(observer); }
public void notifyObservers() {
Iterator iterator = observers.iterator();
while (iterator.hasNext()) {
Observer observer = (Observer)iterator.next();
observer.update(duck);
}
}
}
이제, 구성(Composition)을 이용하여 Quackable
구현체들이 이 객체를 갖도록 만들것이다.
public class MallardDuck implements Quackable {
Observable observable;
public MallardDuck() {
observable = new Observable(this);
}
public void quack() {
System.out.println(“Quack”);
notifyObservers();
}
public void registerObserver(Observer observer) {
observable.registerObserver(observer);
}
public void notifyObservers() {
observable.notifyObservers();
}
}
Observable
은 생성자를 통해 주어지고, Quackable
구현체들은 이 주어진 Observable
을 통해 관찰자들을 notify()
해준다.
추가로, QuackCounter
클래스도 마찬가지로 register
, notify
메소드를 구현해주어야 한다. Quackable
인터페이스를 구현하기 때문이다.
public interface Observer {
public void update(QuackObservable duck);
}
public class Quackologist implements Observer {
public void update(QuackObservable duck) {
System.out.println(“Quackologist: “ + duck + “ just quacked.”);
}
}
마지막으로 옵저버 인터페이스와 구현체이다. update()
메소드를 통해 quack()
을 한 객체, 즉 울음소리를 낸 오리를 전달 받는다.
위 예제를 통해 하나의 프로그램에 어떻게 서로 다른 여러 디자인 패턴들이 적용될 수 있는지 알아보았다. 이런 디자인 패턴을 적절하게 적용할 줄 안다면 보다 명료하고 효율적인 프로그램을 만들 수 있을 것이다.
스프링 부트 개발 경험이 있다면 MVC 패턴이라는 표현이 아주 친숙할 것이다. 그렇지 않은 사람들에게는 다소 생소할 수 있다. 물론 스프링 웹 프로젝트 경험이 있어도 생소할 수 있지만...
MVC패턴은 Model - View - Controller 3단계로 UI(유저 인터페이스)를 나누어 구현하는 구조이다. 그러나, 너무 어렵게 생각할 필요 없다. 결국 MVC 패턴도 디자인 패턴들의 모둠이니까!
뷰(View)는 실제 사용자에게 보여질 UI이다.
모델(Model)은 데이터와 서비스 로직을 담고 있다.
컨트롤러(Controller)는 유저에게서 입력 값을 받아 모델에게 전달해준다.
이들의 관계를 지금까지 배운 디자인 패턴으로 나타내자면, 아래와 같다.
뷰와 컨트롤러는 전형적인 Strategy 패턴이다. 뷰는 전적으로 컨트롤러가 부여하는 Strategy에 의존한다.
뷰는 Composite 패턴으로 구현되어있다. UI의 창, 패널 등의 요소는 Composite이며, 버튼 등은 leaf라고 할 수 있다.
모델은 Observer 패턴에 해당한다. 상태 변화가 생기면 객체 정보를 update
받을 수 있도록 되어있다.