SW
[개발서적] 헤드퍼스트 디자인 패턴 Ch12. 복합 패턴 본문
1. 복합 패턴
복합 패턴 = 여러 패턴을 함께 사용해서 다양한 문제를 해결하는 패턴 = 패턴으로 이루어진 패턴
오리 시뮬레이션 게임을 처음부터 다시 만들면서 몇 가지 기능을 추가해 봅시다.
이 과정에서 하나의 문제를 해결할 때 여러 패턴이 어떻게 공존하고 협력하는지 볼 수 있습니다.
1. Quackable 인터페이스 정의
public interface Quackable {
public void quack();
}
- Quackable 인터페이스는 quack() 메서드를 선언합니다. 이 메서드는 오리가 소리를 낼 때 사용됩니다.
2. Quackable 구현 클래스들
public class MallardDuck implements Quackable {
public void quack() {
System.out.println("꽥꽥");
}
}
public class RedheadDuck implements Quackable {
public void quack() {
System.out.println("꽥꽥");
}
}
public class DuckCall implements Quackable {
public void quack() {
System.out.println("꽉꽉");
}
}
public class RubberDuck implements Quackable {
public void quack() {
System.out.println("삑삑");
}
}
- 각 오리 클래스는 Quackable 인터페이스를 구현하여 자신의 고유한 quack() 메서드를 정의합니다.
3. DuckSimulator 클래스
public class DuckSimulator {
public static void main(String[] args) {
DuckSimulator simulator = new DuckSimulator();
simulator.simulate();
}
void simulate() {
Quackable mallardDuck = new MallardDuck();
Quackable redheadDuck = new RedheadDuck();
Quackable duckCall = new DuckCall();
Quackable rubberDuck = new RubberDuck();
System.out.println("\n오리 시뮬레이션 게임");
simulate(mallardDuck);
simulate(redheadDuck);
simulate(duckCall);
simulate(rubberDuck);
}
void simulate(Quackable duck) {
duck.quack();
}
}
- DuckSimulator 클래스는 시뮬레이션을 실행하는 메인 클래스로, 다양한 오리 객체를 생성한 후 각 오리의 quack() 메서드를 호출하여 소리를 냅니다.
- simulate() 메서드는 Quackable 객체를 받아서 quack() 메서드를 실행합니다. 이로 인해 다형성을 통해 각 오리의 소리를 동일한 방식으로 처리할 수 있습니다.
4. 거위 추가
public class Goose {
public void honk() {
System.out.println("끽끽");
}
}
- Goose 클래스는 honk() 메서드를 가지고 있으며, 거위가 소리를 낼 때 "끽끽" 소리를 냅니다. 그러나 Goose는 Quackable 인터페이스를 구현하지 않으므로 바로 시뮬레이션에 사용할 수 없습니다.
5. 어댑터 패턴 사용하기 (GooseAdapter)
public class GooseAdapter implements Quackable {
Goose goose;
public GooseAdapter(Goose goose) {
this.goose = goose;
}
public void quack() {
goose.honk();
}
}
- GooseAdapter는 어댑터 패턴을 사용하여 Goose 객체를 Quackable로 감싸줍니다.
- quack() 메서드가 호출되면, 내부적으로 거위의 honk() 메서드가 호출되어 "끽끽" 소리가 나도록 합니다.
6. 시뮬레이터에 거위 추가
public class DuckSimulator {
public static void main(String[] args) {
DuckSimulator simulator = new DuckSimulator();
simulator.simulate();
}
void simulate() {
Quackable mallardDuck = new MallardDuck();
Quackable redheadDuck = new RedheadDuck();
Quackable duckCall = new DuckCall();
Quackable rubberDuck = new RubberDuck();
Quackable gooseDuck = new GooseAdapter(new Goose());
System.out.println("\n오리 시뮬레이션 게임 (거위 어댑터)");
simulate(mallardDuck);
simulate(redheadDuck);
simulate(duckCall);
simulate(rubberDuck);
simulate(gooseDuck);
}
void simulate(Quackable duck) {
duck.quack();
}
}
- 시뮬레이터에 거위를 추가할 때 GooseAdapter를 사용하여 Goose를 Quackable로 변환합니다.
- 이제 거위도 다른 오리들과 마찬가지로 simulate() 메서드에서 quack()을 호출할 수 있습니다. 이때 거위는 "끽끽" 소리를 냅니다.
- Goose는 원래 Quackable을 구현하지 않지만, GooseAdapter를 통해 Quackable처럼 동작할 수 있도록 변환되었습니다.
7. Quack 소리 횟수 세기 (QuackCounter)
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;
}
}
- QuackCounter(데코레이터) 클래스는 데코레이터 패턴을 사용하여 Quackable을 구현한 객체에 소리 횟수를 세는 기능을 추가합니다.
- 데코레이터 패턴은 객체에 새로운 기능을 동적으로 추가할 수 있도록 도와주는 디자인 패턴
- 기존의 Quackable 객체들에 직접 수정 없이 소리 횟수를 세는 기능 추가 가능
- quack() 메서드가 호출될 때, 내부의 duck.quack()이 호출된 후, numberOfQuacks가 증가합니다.
- getQuacks() 메서드는 지금까지 소리가 난 횟수를 반환합니다.
8. 시뮬레이터에서 모든 오리에 데코레이터 적용
public class DuckSimulator {
public static void main(String[] args) {
DuckSimulator simulator = new DuckSimulator();
simulator.simulate();
}
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());
Quackable gooseDuck = new GooseAdapter(new Goose());
System.out.println("\n오리 시뮬레이션 게임 (데코레이터)");
simulate(mallardDuck);
simulate(redheadDuck);
simulate(duckCall);
simulate(rubberDuck);
simulate(gooseDuck);
System.out.println("오리가 소리 낸 횟수: " + QuackCounter.getQuacks() + " 번");
}
void simulate(Quackable duck) {
duck.quack();
}
}
- QuackCounter 데코레이터는 모든 오리 객체에 적용되어 소리 낸 횟수를 기록합니다.
- 시뮬레이션이 끝난 후, QuackCounter.getQuacks()를 통해 총 몇 번 오리들이 소리를 냈는지 출력합니다.
- 이 코드에서는 GooseAdapter를 사용하여 거위도 포함되며, 각 오리가 소리를 낼 때마다 그 횟수가 기록됩니다.
- "오리가 소리 낸 횟수: 4 번"
9. 팩토리 패턴을 통한 오리 생성
public abstract class AbstractDuckFactory {
public abstract Quackable createMallardDuck();
public abstract Quackable createRedheadDuck();
public abstract Quackable createDuckCall();
public abstract Quackable createRubberDuck();
}
- 팩토리 패턴은 여러 종류의 오리를 생성할 때 사용됩니다. (중앙화된 관리, 복잡한 로직 숨김)
- AbstractDuckFactory는 각 오리 클래스를 생성하는 메서드를 선언하고, 이를 구체화한 팩토리 클래스에서 해당 메서드들을 구현하여 오리 객체를 생성할 수 있습니다.
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());
}
}
- CountingDuckFactory는 QuackCounter 데코레이터를 사용해 각 오리의 quack() 메서드가 호출될 때마다 소리 횟수를 세도록 합니다.
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();
Quackable duckCall = duckFactory.createDuckCall();
Quackable rubberDuck = duckFactory.createRubberDuck();
Quackable gooseDuck = new GooseAdapter(new Goose());
System.out.println("\n오리 시뮬레이션 게임 (데코레이터)");
simulate(mallardDuck);
simulate(redheadDuck);
simulate(duckCall);
simulate(rubberDuck);
simulate(gooseDuck);
System.out.println("오리가 소리 낸 횟수: " + QuackCounter.getQuacks() + " 번");
}
void simulate(Quackable duck) {
duck.quack();
}
}
- 팩토리를 사용해 다양한 오리 객체들을 생성하고, 이들을 시뮬레이션에서 사용합니다.
- QuackCounter를 사용한 오리들의 소리 횟수를 기록할 수 있습니다.
10. Flock 패턴 (무리 패턴)
public class Flock implements Quackable {
List<Quackable> quackers = new ArrayList<>();
public void add(Quackable quacker) {
quackers.add(quacker);
}
public void quack() {
Iterator<Quackable> iterator = quackers.iterator();
while (iterator.hasNext()) {
Quackable quacker = iterator.next();
quacker.quack();
}
}
}
- Flock 클래스는 Quackable 객체들의 리스트를 관리하고, 모든 객체의 quack() 메서드를 호출할 수 있습니다.
- 여러 오리 객체를 한 번에 관리할 수 있는 구조를 제공합니다.
11. Flock을 시뮬레이션에 적용
void simulate(AbstractDuckFactory duckFactory) {
Flock flockOfDucks = new Flock();
Quackable redheadDuck = duckFactory.createRedheadDuck();
Quackable duckCall = duckFactory.createDuckCall();
Quackable rubberDuck = duckFactory.createRubberDuck();
Quackable gooseDuck = new GooseAdapter(new Goose());
flockOfDucks.add(redheadDuck);
flockOfDucks.add(duckCall);
flockOfDucks.add(rubberDuck);
flockOfDucks.add(gooseDuck);
System.out.println("\n오리 시뮬레이션 게임: 무리");
simulate(flockOfDucks);
}
- Flock 패턴을 사용하여 오리의 무리를 만들고, 무리 내 모든 오리의 quack() 메서드를 호출합니다.
- 팩토리 패턴은 오리 객체 생성을 중앙화하고, 데코레이터 패턴을 사용하여 추가 기능(예: 소리 횟수 세기)을 쉽게 추가할 수 있습니다.
- Flock 패턴은 오리 무리를 관리하고, 모든 오리가 동일한 행동을 하도록 쉽게 설정할 수 있습니다.
12. 옵저버 패턴 도입
옵저버 패턴은 객체의 행동을 관찰하고 추적하려는 시나리오에 적합한 패턴입니다. 여기서는 오리들이 소리를 낼 때 이를 추적하고자 옵저버 패턴을 적용합니다.
public interface QuackObservable {
public void registerObserver(Observer observer);
public void notifyObservers();
}
- QuackObservable은 옵저버를 등록하고, 소리가 날 때 이를 옵저버에게 알리는 역할을 합니다.
Quackable 인터페이스
// Quackable은 QuackObservable을 확장하며, 오리가 소리를 낼 때 이를 옵저버에게 알릴 수 있도록 합니다.
public interface Quackable extends QuackObservable {
public void quack();
}
// Observable 클래스는 옵저버를 등록하고, 오리가 소리를 낼 때마다 등록된 옵저버들에게 알립니다.
public class Observable implements QuackObservable {
List<Observer> observers = new ArrayList<>();
QuackObservable duck;
public Observable(QuackObservable duck) {
this.duck = duck;
}
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(duck);
}
}
}
// MallardDuck 클래스는 Observable 객체를 가지고 있으며, 소리가 날 때 옵저버들에게 알립니다.
public class MallardDuck implements Quackable {
Observable observable;
public MallardDuck() {
observable = new Observable(this);
}
public void quack() {
System.out.println("꽥꽥");
notifyObservers();
}
public void registerObserver(Observer observer) {
observable.registerObserver(observer);
}
public void notifyObservers() {
observable.notifyObservers();
}
}
// Observer 인터페이스는 옵저버가 업데이트될 때 호출되는 메서드를 정의합니다.
public interface Observer {
public void update(QuackObservable duck);
}
// Quackologist는 Observer를 구현하여 오리가 소리를 낼 때 이를 출력합니다.
public class Quackologist implements Observer {
public void update(QuackObservable duck) {
System.out.println("꽥꽥학자: " + duck + "가 방금 소리냈다.");
}
}
public class DuckSimulator {
public static void main(String[] args) {
DuckSimulator simulator = new DuckSimulator();
AbstractDuckFactory duckFactory = new CountingDuckFactory();
simulator.simulate(duckFactory);
}
void simulate(AbstractDuckFactory duckFactory) {
Flock flockOfDucks = new Flock();
Quackologist quackologist = new Quackologist();
flockOfDucks.registerObserver(quackologist);
simulate(flockOfDucks);
System.out.println("오리가 소리 낸 횟수: " + QuackCounter.getQuacks() + " 번");
}
void simulate(Quackable duck) {
duck.quack();
}
}
- 옵저버인 Quackologist를 무리에 등록하고, 오리들이 소리를 낼 때마다 옵저버가 그 행동을 추적하도록 설정합니다.
- 옵저버 패턴을 통해 오리들이 소리를 낼 때 이를 추적할 수 있습니다.
- 옵저버는 여러 객체의 상태 변화를 실시간으로 반영하며, 이 패턴을 통해 객체 간의 의존성을 줄일 수 있습니다.
2. 정리
이미지에 있는 내용은 옵저버 패턴, 데코레이터 패턴, 반복자 패턴을 활용해 변경된 설계에 대한 설명입니다. 주요 내용을 요약하면 다음과 같습니다.
1. Quackable의 확장
처음에 Quackable 객체들은 기본적인 quack() 메서드를 구현했습니다. 하지만 거위가 등장하면서 거위도 Quackable이 되고 싶어 했고, 어댑터 패턴을 사용하여 거위를 Quackable로 만들었습니다. 이제 거위는 quack()을 호출하면 자동으로 honk()를 실행하게 되었습니다.
2. 데코레이터 패턴으로 소리 횟수 측정
팩퀵학자들이 오리의 소리 횟수를 측정하고 싶어했습니다. 이를 위해 데코레이터 패턴을 적용해 QuackCounter 데코레이터를 추가했고, 이로 인해 각 quack() 호출 시 소리 횟수를 셀 수 있게 되었습니다.
3. 팩토리 패턴을 이용한 객체 생성
데코레이터가 적용되지 않은 Quackable 객체들이 생길까 걱정한 팩퀵학자들은 팩토리 패턴을 사용해 항상 데코레이터가 적용된 Quackable 객체가 생성되도록 했습니다. 이를 통해 오리 객체의 생성이 중앙에서 관리되었습니다.
4. Flock 패턴(무리 패턴)과 반복자 패턴
모든 오리와 거위를 관리하는 것이 어려워졌습니다. 이를 해결하기 위해 Flock 패턴을 도입해 오리들을 무리 단위로 관리하게 되었습니다. 이 과정에서 반복자 패턴을 사용해 각 오리들의 quack()을 순차적으로 호출할 수 있게 했습니다.
5. 옵저버 패턴 도입
팩퀵학자들은 오리의 행동을 관찰하고 싶어했습니다. 옵저버 패턴을 적용해 Quackologist가 Quackable 객체의 옵저버로 등록되었고, 오리가 소리를 낼 때마다 옵저버에게 알릴 수 있었습니다.
결론
이러한 패턴들을 적용한 덕분에 복잡한 객체들 간의 관계를 효율적으로 관리하고, 소리 횟수 측정과 같은 추가적인 기능을 손쉽게 구현할 수 있었습니다. 옵저버 패턴, 데코레이터 패턴, 반복자 패턴 등 다양한 패턴이 잘 결합되어 문제를 해결했습니다.
3. MVC 패턴
1. 사용자와 뷰(View)의 상호작용
- 사용자는 뷰를 통해 모델의 상태를 볼 수 있습니다.
- 사용자가 뷰에서 특정 버튼을 누르면 그 정보가 컨트롤러에게 전달됩니다. 컨트롤러는 상황에 맞게 모델과 뷰에 적절한 작업을 요청합니다.
2. 컨트롤러가 모델에게 상태 변경 요청
- 컨트롤러는 사용자의 입력을 받아 그 의미를 해석한 후 모델에 상태 변경을 요청합니다.
- 예를 들어, 사용자가 버튼을 클릭하면 컨트롤러가 그 입력을 받아 모델에 처리 방법을 지시합니다.
3. 컨트롤러가 뷰(View)에게 요청
- 컨트롤러는 때로는 뷰에게도 변경을 요청할 수 있습니다.
- 예를 들어, 컨트롤러는 뷰의 특정 버튼이나 메뉴의 활성화/비활성화를 요청할 수 있습니다.
4. 모델이 뷰에게 상태 변경 알림
- 모델이 상태를 변경하면 뷰에게 그 사실을 알려줍니다.
- 예를 들어, 재생 목록에서 다음 곡으로 넘어가는 등의 상태 변화가 발생하면 모델은 뷰에 그 변화를 전달합니다.
5. 뷰가 모델에게 상태 요청
- 뷰는 모델의 상태를 직접 가져와 화면에 표시합니다.
- 예를 들어, 모델이 곡 목록을 업데이트하면 뷰는 그 정보를 모델에서 받아와 사용자에게 보여줍니다.
'개발서적 > 헤드퍼스트 디자인패턴' 카테고리의 다른 글
[개발서적] 헤드퍼스트 디자인 패턴 Ch11. 프록시 패턴 (0) | 2024.10.09 |
---|---|
[개발서적] 헤드퍼스트 디자인 패턴 Ch10. 상태 패턴 (0) | 2024.09.22 |
[개발서적] 헤드퍼스트 디자인 패턴 Ch9. 반복자 패턴과 컴포지트 패턴 (0) | 2024.09.04 |
[개발서적] 헤드퍼스트 디자인 패턴 Ch8. 템플릿 메소드 패턴 (0) | 2024.08.30 |
[개발서적] 헤드퍼스트 디자인 패턴 Ch7. 어댑터 패턴과 퍼사드 패턴 (1) | 2024.08.29 |