반응형
복합 패턴이란?
- 문제를 해결하기 위해 개발할 때는 효율적인 코드를 작성하기 위해 디자인 패턴을 고민하고 적용합니다.
- 이때 하나의 디자인 패턴만을 사용하지 않고 여러 패턴을 복합적으로 사용하여 문제에 대한 해결을 시도 하는 것을 복합 패턴이라고 합니다.
복합 패턴 적용하기
- 전략 패턴에서 사용했던 오리 예제를 다시 한 번 사용하여 예제를 진행하겠습니다.
- 이전에는 오리 문제를 전략패턴만 사용해서 적용했지만 이번에는 새로운 문제를 예제 중간에 지속적으로 제안하고 문제 해결을 위한 디자인 패턴을 적용하도록 해보겠습니다.
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 RubberDuck implements Quackable {
public void quack() {
System.out.println("Squeak");
}
}
public class DuckCall implements Quackable {
public void quack() {
System.out.println("Kwak");
}
}
- 오리 소리를 내는 행동을 정의하여 구현한 오리들의 구현체입니다.
테스트
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("\nDuck Simulator");
simulate(mallardDuck);
simulate(redheadDuck);
simulate(duckCall);
simulate(rubberDuck);
}
void simulate(Quackable duck) {
duck.quack();
}
}
수행결과
public class Goose {
public void honk() {
System.out.println("Honk");
}
}
- 위 예제 상황에서 클라이언트가 위와 같은 Goose 객체를 추가하고 싶다고 추가 요구 사항을 제안 합니다.
- 그러나 Goose 객체는 honk() 메소드를 통해 소리를 내는 메소드를 수행하기 때문에 인터페이스가 다르는 문제점이 존재합니다.
- 이러한 문제를 해결하기 위해 어댑터 패턴을 적용하도록 하겠습니다.
public class GooseAdapter implements Quackable {
Goose goose;
public GooseAdapter(Goose goose) {
this.goose = goose;
}
public void quack() {
goose.honk();
}
public String toString() {
return "Goose pretending to be a Duck";
}
}
테스트
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();
//Goose를 GooseAdpate로 감싸서 다른 오리처럼 Quack메소드를 수행하도록 구현
Quackable gooseDuck = new GooseAdapter(new Goose());
System.out.println("\nDuck Simulator: With Goose Adapter");
simulate(mallardDuck);
simulate(redheadDuck);
simulate(duckCall);
simulate(rubberDuck);
simulate(gooseDuck);
}
void simulate(Quackable duck) {
duck.quack();
}
}
수행결과
- 위와 같은 상황에서 클라이언트는 꽥꽥 소리를 낸 횟수를 세주는 기능이 필요하다는 추가 사항을 요구합니다.
- 새로운 행동을 추가 하기 위해서는 데코레이터 패턴을 적용하여 횟수를 측정하는 기능을 추가하면 기존의 Duck 코드는 변경하지 않아도 됩니다.
- 그렇기 때문에 현재 예제에서 데코레이터 패턴을 추가하도록 하겠습니다.
public class QuackCounter implements Quackable {
Quackable duck;
static int numberOfQuacks;
public QuackCounter (Quackable duck) {
this.duck = duck;
}
public void quack() {
duck.quack();
numberOfQuacks++;
}
// Quack 소리가 몇 번 발생했는지를 반환한다.
public static int getQuacks() {
return numberOfQuacks;
}
public String toString() {
return duck.toString();
}
}
- QuackCounter가 데코레이터 패턴을 의미하는 클래스입니다.
- QuackCounter는 QuackAble 인터페이스를 정의하여 오리 객체를 생성할때 구현체를 생성자로 받아 메소드를 호출할때 quack() 메소드 수행과 회수를 측정하는 로직을 재정의합니다.
테스트
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("\nDuck Simulator: With Decorator");
simulate(mallardDuck);
simulate(redheadDuck);
simulate(duckCall);
simulate(rubberDuck);
simulate(gooseDuck);
System.out.println("The ducks quacked " +
QuackCounter.getQuacks() + " times");
}
void simulate(Quackable duck) {
duck.quack();
}
}
수행결과
- 위와 같은 상황에서 클라이언트가 오리 구현체를 생성자로 넘겨 QuackCounter를 사용하는 방식에서 생성자를 전달하는 절차를 빼먹고 구현하여 휴먼에러가 발생할 수 있는점을 문제삼았습니다.
- 오리 객체를 생성하는 작업을 한 곳에서 하면 이런 문제가 발생하지 않겠다고 판단하여 데코레이터 패턴을 적용하는 부분에 캡슐화하는 작업을 진행하기 위해 팩토리 패턴을 적용하기로 결정하였습니다.
public abstract class AbstractDuckFactory {
public abstract Quackable createMallardDuck();
public abstract Quackable createRedheadDuck();
public abstract Quackable createDuckCall();
public abstract Quackable createRubberDuck();
}
- 여러 종류의 오리를 생성하므로 추상 팩토리 패턴을 적용하도록 해보겠습니다.
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());
}
}
- AbstractDuckFactory를 상속하여 정의한 CountingDuckFactory 클래스입니다.
테스트
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("\nDuck Simulator: With Abstract Factory");
simulate(mallardDuck);
simulate(redheadDuck);
simulate(duckCall);
simulate(rubberDuck);
simulate(gooseDuck);
System.out.println("The ducks quacked " +
QuackCounter.getQuacks() +
" times");
}
void simulate(Quackable duck) {
duck.quack();
}
}
수행결과
- 위와 같은 상황에서 클라이언트가 simulate() 코드가 반복되는것에 최적화를 요구했습니다.
- 또한, 오리를 일괄적으로 관리할 수 있는 방법이 필요하다는 개발 추가 사항이 접수되었습니다.
- 이러한 문제를 해결하기 위해 컴포지트 패턴을 적용하도록 해보겠습니다.
public class Flock implements Quackable {
ArrayList<Quackable> quackers = new ArrayList<Quackable>();
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();
}
}
public String toString() {
return "Flock of Quackers";
}
}
- 컴포지트 패턴의 복합 객체와 잎 원소는 같은 인터페이스를 구현해야하기 때문에 Quackable 인터페이스를 구현하고 있습니다.
- Flcok에 속하는 Quackable 객체들은 ArrayList에 저장하고 있습니다.
- quack() 메소드를 살펴보면 객체의 Iteratort를 반환하여 재귀적으로 순환을 돌려서 호출하는것을 확인할 수 있습니다. 이러한 부분은 반복자 패턴을 적용하여 구현하였습니다.
테스트
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 redheadDuck = duckFactory.createRedheadDuck();
Quackable duckCall = duckFactory.createDuckCall();
Quackable rubberDuck = duckFactory.createRubberDuck();
Quackable gooseDuck = new GooseAdapter(new Goose());
System.out.println("\nDuck Simulator: With Composite - Flocks");
//Flock을 생성한 다음 Quackable을 Flock의 리스트에 포함시킵니다.
Flock flockOfDucks = new Flock();
flockOfDucks.add(redheadDuck);
flockOfDucks.add(duckCall);
flockOfDucks.add(rubberDuck);
flockOfDucks.add(gooseDuck);
//물에서만 뜨는 오리를 담는 Flock을 별도로 생성합니다.
Flock flockOfMallards = new Flock();
Quackable mallardOne = duckFactory.createMallardDuck();
Quackable mallardTwo = duckFactory.createMallardDuck();
Quackable mallardThree = duckFactory.createMallardDuck();
Quackable mallardFour = duckFactory.createMallardDuck();
flockOfMallards.add(mallardOne);
flockOfMallards.add(mallardTwo);
flockOfMallards.add(mallardThree);
flockOfMallards.add(mallardFour);
//처음에 생성한 Flock에 물 오리 Flock을 함시킵니다.
flockOfDucks.add(flockOfMallards);
//Flock + 물 오리 Flock이 동시적으로 호출됩니다.
System.out.println("\nDuck Simulator: Whole Flock Simulation");
simulate(flockOfDucks);
//물 오리 Flcok만 호출됩니다.
System.out.println("\nDuck Simulator: Mallard Flock Simulation");
simulate(flockOfMallards);
System.out.println("\nThe ducks quacked " +
QuackCounter.getQuacks() +
" times");
}
void simulate(Quackable duck) {
duck.quack();
}
}
수행결과
- 이런 상황에서 오리들의 개별 행동들을 관찰하고 싶다는 클라이언트의 추가 개발 요구 사항이 접수되었습니다.
- 이러한 요구사항을 충족시키기 위해 옵저버 패턴을 적용해보겠습니다.
public interface QuackObservable {
public void registerObserver(Observer observer);
public void notifyObservers();
}
- 관찰 대상이 되는것이 Observable 인터페이스입니다. Observable에는 옵저버를 등록하는 메소드와 옵저버에게 연락을 돌리는 메소드가 존재합니다.
- 옵저버를 제거하는 메소드는 별도로 필요시 구현하는 방향으로 예제를 진행하겠습니다.
public interface Quackable extends QuackObservable {
public void quack();
}
- 모든 Quackable은 관찰 대상이 되므로 Quackable에서 Observable 인터페이스를 상속받도록 구현하겠습니다.
public class Observable implements QuackObservable {
List<Observer> observers = new ArrayList<Observer>();
QuackObservable duck;
public Observable(QuackObservable duck) {
this.duck = duck;
}
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers() {
Iterator<Observer> iterator = observers.iterator();
while (iterator.hasNext()) {
Observer observer = iterator.next();
observer.update(duck);
}
}
public Iterator<Observer> getObservers() {
return observers.iterator();
}
}
- 관찰 대상이 되는 QuackObservalble에 필요한 모든 기능을 구현하는 Observable 객체입니다.
- Observable은 각 오리 객체에 존재하면서 옵저버에게 필요한 행동들을 수행합니다.
- Observable은 QuackObservable을 구현합니다. 나중에 QuackObservable 에서 정의한 메소드를 Observable에서 사용하기 때문입니다.
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();
}
public String toString() {
return "Mallard Duck";
}
}
- 위와 같이 관찰 대상에 Observable 객체를 추가합니다.
- MallardDuck 외에도 다른 오리 객체에도 Quackable을 정의를 구현하고 Observable 객체를 선언해야합니다.
public interface Observer {
public void update(QuackObservable duck);
}
- 관찰자인 Observer 인터페이스를 선언해줍니다.
- 관찰자인 Observer 인터페이스는 관찰 대상인 Observable 객체가 수행될 때 Observer의 update 메소드가 수행됩니다.
public class Quackologist implements Observer {
public void update(QuackObservable duck) {
System.out.println("Quackologist: " + duck + " just quacked.");
}
public String toString() {
return "Quackologist";
}
}
- Observer 인터페이스의 구현체인 Quacklogist 입니다.
테스트
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 redheadDuck = duckFactory.createRedheadDuck();
Quackable duckCall = duckFactory.createDuckCall();
Quackable rubberDuck = duckFactory.createRubberDuck();
Quackable gooseDuck = new GooseAdapter(new Goose());
Flock flockOfDucks = new Flock();
flockOfDucks.add(redheadDuck);
flockOfDucks.add(duckCall);
flockOfDucks.add(rubberDuck);
flockOfDucks.add(gooseDuck);
Flock flockOfMallards = new Flock();
Quackable mallardOne = duckFactory.createMallardDuck();
Quackable mallardTwo = duckFactory.createMallardDuck();
Quackable mallardThree = duckFactory.createMallardDuck();
Quackable mallardFour = duckFactory.createMallardDuck();
flockOfMallards.add(mallardOne);
flockOfMallards.add(mallardTwo);
flockOfMallards.add(mallardThree);
flockOfMallards.add(mallardFour);
flockOfDucks.add(flockOfMallards);
System.out.println("\nDuck Simulator: With Observer");
Quackologist quackologist = new Quackologist();
flockOfDucks.registerObserver(quackologist);
simulate(flockOfDucks);
System.out.println("\nThe ducks quacked " +
QuackCounter.getQuacks() +
" times");
}
void simulate(Quackable duck) {
duck.quack();
}
}
수행결과
예제 클래스 다이어그램
복합 패턴인 MVC 패턴 분석해보기
- MVC 패턴은 Model-View-Controller라는 의미입니다.
- 각 단계가 뷰 → 컨트롤러 → 모델 3단의 관계로 이루어져있습니다.
- 뷰 영역은 사용자가 보는 인터페이스 화면을 의미하며 사용자는 뷰에만 접족할 수 있습니다. 사용자가 뷰 영역에서 행동을 취할 경우 컨틀롤러 영역으로 입력이 전달됩니다.
- 컨트롤러에서는 입력 받은 내용에 대한 행동을 수행합니다. 컨트롤러에서 수행한 결과로 생긴 데이터와 상태 값들은 모델이라는 영역으로 데이터를 전달합니다.
- 변경된 정보를 가지고 있는 모델은 다시 한 번 뷰에게 새로운 정보를 전달해줍니다.
- 컨트롤러에서 뷰로 데이터를 변경해 달라고 요청하는 경우도 존재합니다. 예를 들어 컨트롤러를 통해 인터페이스의 버튼이나 메뉴를 비활성화할 수 있습니다.
- 이러한 MVC 패턴은 크게 전략 패턴, 컴포지트 패턴, 옵저버 패턴 3가지로 결합된 패턴입니다.
- 옵저버 패턴은 모델의 상태가 변할 때마다 뷰와, 1컨트롤러에게 연락하기 위해 사용됩니다.
- 전략 패턴은 뷰와 컨트롤러의 관계에서 사용됩니다. 컨트롤러는 전략 패턴의 행동에 해당하며 사용자의 요청에 맞게 행동이 변환되는 전략패턴의 특성 처럼 사용자의 요청에 따라 다른 컨트롤러가 동작합니다.
- 컴포지트 패턴은 뷰안에서 사용됩니다. 버튼, 윈도우 와 같은 다양한 요소를 컴포지트 패턴을 통해 관리하게 됩니다.
MVC 패턴 구현해보기
- 오디오 증감 프로그램을 MVC 패턴을 활용해서 구현해보겠습니다.
요구사항
- 위 그림처럼 >> 버튼을 클릭하면 1 BPM이 상승합니다.
- 반대로 << 버튼을 클릭하면 1 BPM이 감소합니다.
- Set 버튼을 누를 경우 설정한 수치만큼 BPM 값이 변경됩니다.
public interface BeatModelInterface {
//컨트롤러가 모델에게 사용자 입력을 전달할 때 사용하는 메소드
void initialize();
//컨트롤러가 모델에게 사용자 입력을 전달할 때 사용하는 메소드
void on();
//컨트롤러가 모델에게 사용자 입력을 전달할 때 사용하는 메소드
void off();
//컨트롤러가 모델에게 사용자 입력을 전달할 때 사용하는 메소드
void setBPM(int bpm); //BPM을 설정하는 메소드
//밑에 메소드 모두 뷰와 컨트롤러가 상태를 알아내거나 옵저버로 등록할 때사용하는 메소드
int getBPM(); //현재 BPM을 반환 비트 생성기가 꺼저 있을경우 0을 반환
void registerObserver(BeatObserver o);
void removeObserver(BeatObserver o);
void registerObserver(BPMObserver o);
void removeObserver(BPMObserver o);
}
- BeatModelnterface는 컨트롤러에서 비트를 조절하거나 뷰와 컨트롤러에서 모델의 상태를 알아낼 때 사용할 수 있도록 외부에 공개된 인터페이스입니다.
public class BeatModel implements BeatModelInterface, Runnable {
List<BeatObserver> beatObservers = new ArrayList<BeatObserver>();
List<BPMObserver> bpmObservers = new ArrayList<BPMObserver>();
int bpm = 90;
Thread thread;
boolean stop = false; //비트 재생 여부 관련 flag
Clip clip; //비트용으로 재생하는 오디오 Clip
public void initialize() {
try {
File resource = new File("clap.wav");
clip = (Clip) AudioSystem.getLine(new Line.Info(Clip.class));
clip.open(AudioSystem.getAudioInputStream(resource));
}
catch(Exception ex) {
System.out.println("Error: Can't load clip");
System.out.println(ex);
}
}
public void on() {
bpm = 90;
//notifyBPMObservers();
thread = new Thread(this);
stop = false;
thread.start();
}
public void off() {
stopBeat();
stop = true;
}
public void run() {
while (!stop) {
playBeat();
notifyBeatObservers();
try {
Thread.sleep(60000/getBPM());
} catch (Exception e) {}
}
}
public void setBPM(int bpm) {
this.bpm = bpm;
notifyBPMObservers();
}
public int getBPM() {
return bpm;
}
public void registerObserver(BeatObserver o) {
beatObservers.add(o);
}
public void notifyBeatObservers() {
for(int i = 0; i < beatObservers.size(); i++) {
BeatObserver observer = (BeatObserver)beatObservers.get(i);
observer.updateBeat();
}
}
public void registerObserver(BPMObserver o) {
bpmObservers.add(o);
}
public void notifyBPMObservers() {
for(int i = 0; i < bpmObservers.size(); i++) {
BPMObserver observer = (BPMObserver)bpmObservers.get(i);
observer.updateBPM();
}
}
public void removeObserver(BeatObserver o) {
int i = beatObservers.indexOf(o);
if (i >= 0) {
beatObservers.remove(i);
}
}
public void removeObserver(BPMObserver o) {
int i = bpmObservers.indexOf(o);
if (i >= 0) {
bpmObservers.remove(i);
}
}
public void playBeat() {
clip.setFramePosition(0);
clip.start();
}
public void stopBeat() {
clip.setFramePosition(0);
clip.stop();
}
}
- BeatModelInterface의 구현체인 BeatModel입니다.
public class DJView implements ActionListener, BeatObserver, BPMObserver {
BeatModelInterface model;
ControllerInterface controller;
JFrame viewFrame;
JPanel viewPanel;
BeatBar beatBar;
JLabel bpmOutputLabel;
JFrame controlFrame;
JPanel controlPanel;
JLabel bpmLabel;
JTextField bpmTextField;
JButton setBPMButton;
JButton increaseBPMButton;
JButton decreaseBPMButton;
JMenuBar menuBar;
JMenu menu;
JMenuItem startMenuItem;
JMenuItem stopMenuItem;
public DJView(ControllerInterface controller, BeatModelInterface model) {
this.controller = controller;
this.model = model;
model.registerObserver((BeatObserver)this);
model.registerObserver((BPMObserver)this);
}
public void createView() {
// Create all Swing components here
viewPanel = new JPanel(new GridLayout(1, 2));
viewFrame = new JFrame("View");
viewFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
viewFrame.setSize(new Dimension(100, 80));
bpmOutputLabel = new JLabel("offline", SwingConstants.CENTER);
beatBar = new BeatBar();
beatBar.setValue(0);
JPanel bpmPanel = new JPanel(new GridLayout(2, 1));
bpmPanel.add(beatBar);
bpmPanel.add(bpmOutputLabel);
viewPanel.add(bpmPanel);
viewFrame.getContentPane().add(viewPanel, BorderLayout.CENTER);
viewFrame.pack();
viewFrame.setVisible(true);
}
//스윙 구성 요소
public void createControls() {
// Create all Swing components here
JFrame.setDefaultLookAndFeelDecorated(true);
controlFrame = new JFrame("Control");
controlFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
controlFrame.setSize(new Dimension(100, 80));
controlPanel = new JPanel(new GridLayout(1, 2));
menuBar = new JMenuBar();
menu = new JMenu("DJ Control");
startMenuItem = new JMenuItem("Start");
menu.add(startMenuItem);
startMenuItem.addActionListener((event) -> controller.start());
// was....
/*
startMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
controller.start();
}
});
*/
stopMenuItem = new JMenuItem("Stop");
menu.add(stopMenuItem);
stopMenuItem.addActionListener((event) -> controller.stop());
// was...
/*
stopMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
controller.stop();
}
});
*/
JMenuItem exit = new JMenuItem("Quit");
exit.addActionListener((event) -> System.exit(0));
// was...
/*
exit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.exit(0);
}
});
*/
menu.add(exit);
menuBar.add(menu);
controlFrame.setJMenuBar(menuBar);
bpmTextField = new JTextField(2);
bpmLabel = new JLabel("Enter BPM:", SwingConstants.RIGHT);
setBPMButton = new JButton("Set");
setBPMButton.setSize(new Dimension(10,40));
increaseBPMButton = new JButton(">>");
decreaseBPMButton = new JButton("<<");
setBPMButton.addActionListener(this);
increaseBPMButton.addActionListener(this);
decreaseBPMButton.addActionListener(this);
JPanel buttonPanel = new JPanel(new GridLayout(1, 2));
buttonPanel.add(decreaseBPMButton);
buttonPanel.add(increaseBPMButton);
JPanel enterPanel = new JPanel(new GridLayout(1, 2));
enterPanel.add(bpmLabel);
enterPanel.add(bpmTextField);
JPanel insideControlPanel = new JPanel(new GridLayout(3, 1));
insideControlPanel.add(enterPanel);
insideControlPanel.add(setBPMButton);
insideControlPanel.add(buttonPanel);
controlPanel.add(insideControlPanel);
bpmLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
bpmOutputLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
controlFrame.getRootPane().setDefaultButton(setBPMButton);
controlFrame.getContentPane().add(controlPanel, BorderLayout.CENTER);
controlFrame.pack();
controlFrame.setVisible(true);
}
public void enableStopMenuItem() {
stopMenuItem.setEnabled(true);
}
public void disableStopMenuItem() {
stopMenuItem.setEnabled(false);
}
public void enableStartMenuItem() {
startMenuItem.setEnabled(true);
}
public void disableStartMenuItem() {
startMenuItem.setEnabled(false);
}
public void actionPerformed(ActionEvent event) {
if (event.getSource() == setBPMButton) {
int bpm = 90;
String bpmText = bpmTextField.getText();
if (bpmText == null || bpmText.contentEquals("")) {
bpm = 90;
} else {
bpm = Integer.parseInt(bpmTextField.getText());
}
controller.setBPM(bpm);
} else if (event.getSource() == increaseBPMButton) {
controller.increaseBPM();
} else if (event.getSource() == decreaseBPMButton) {
controller.decreaseBPM();
}
}
public void updateBPM() {
if (model != null) {
int bpm = model.getBPM();
if (bpm == 0) {
if (bpmOutputLabel != null) {
bpmOutputLabel.setText("offline");
}
} else {
if (bpmOutputLabel != null) {
bpmOutputLabel.setText("Current BPM: " + model.getBPM());
}
}
}
}
public void updateBeat() {
if (beatBar != null) {
beatBar.setValue(100);
}
}
}
- 요구사항의 뷰를 담당하는 구현입니다.
public interface ControllerInterface {
void start();
void stop();
void increaseBPM();
void decreaseBPM();
void setBPM(int bpm);
}
- 컨트롤러 인터페이스입니다.
public class BeatController implements ControllerInterface {
BeatModelInterface model;
DJView view;
//컨트롤러 생성시 모델 인자가 전달되며 생성자에서 뷰를 생성한다.
public BeatController(BeatModelInterface model) {
this.model = model;
view = new DJView(this, model);
view.createView();
view.createControls();
view.disableStopMenuItem();
view.enableStartMenuItem();
model.initialize();
}
public void start() {
model.on();
view.disableStartMenuItem();
view.enableStopMenuItem();
}
public void stop() {
model.off();
view.disableStopMenuItem();
view.enableStartMenuItem();
}
public void increaseBPM() {
int bpm = model.getBPM();
model.setBPM(bpm + 1);
}
public void decreaseBPM() {
int bpm = model.getBPM();
model.setBPM(bpm - 1);
}
public void setBPM(int bpm) {
model.setBPM(bpm);
}
}
- BeatController는 컨트롤러 인터페이스를 정의한 구현체입니다
테스트
public class DJTestDrive {
public static void main (String[] args) {
BeatModelInterface model = new BeatModel();
ControllerInterface controller = new BeatController(model);
}
}
수행결과
- BPM set과 BPM 증감이 이뤄지는것이 정상적으로 작동한다.
정리
- 복합 패턴은 2개 이상의 패턴을 결합한 패턴을 의미합니다.
- MVC 패턴은 옵저버, 전략, 컴포지트 패턴으로 이루어진 복합 패턴입니다.
- 모델은 옵저버 패턴을 사용해서 의존성을 없애면서 옵저버들에게 자신의 상태가 변경되었음을 알리수 있습니다.
- 컨트롤러는 뷰의 전략 객체입니다. 뷰는 사용자의 요청에 맞게 적합한 컨트롤러를 수행합니다.
- 뷰는 컴포지트 패턴을 사용해 사용자 인터페이스를 구현합니다. 패널, 프레임, 버튼과 같은 중첩 요소들이 구성 요소로 이루어집니다.
- 새로운 모델을 기존 뷰와 컨트롤러에 연결할 경우에는 어댑터 패턴을 활용하면 됩니다.
반응형
'디자인 패턴' 카테고리의 다른 글
CHAPTER 13.실전 디자인 패턴 (0) | 2023.05.06 |
---|---|
CHAPTER 11.프록시 패턴 (0) | 2023.05.06 |
CHAPTER 10.상태 패턴 (0) | 2023.05.06 |
CHAPTER 09.반복자 패턴과 컴포지트 패턴 (0) | 2023.05.06 |
CHAPTER 08.템플릿 메소드 패턴 (0) | 2023.05.06 |