// 구체 전략들
class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("날개로 납니다!");
}
}
class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("저는 못 날아요.");
}
}
// Context 클래스
class Duck {
private FlyBehavior flyBehavior;
public Duck(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void performFly() {
flyBehavior.fly();
}
public void setFlyBehavior(FlyBehavior fb) {
this.flyBehavior = fb;
}
}
// 사용 예
public class Main {
public static void main(String[] args) {
Duck mallard = new Duck(new FlyWithWings());
mallard.performFly(); // 날개로 납니다!
// 전략 변경 (런타임)
mallard.setFlyBehavior(new FlyNoWay());
mallard.performFly(); // 저는 못 날아요.
}
}
if-else 문으로 상태마다 다른 행동을 지정하는 대신, 상태를 클래스로 나누고 객체에게 행동을 위임하는 구조
ex)
🔁 객체의 상태에 따라 동작이 자주 바뀔 때
예: 게임 캐릭터의 상태(걷기/뛰기/점프)
🧱 조건문(if-else, switch)가 상태별로 복잡할 때
각 상태를 클래스로 분리해서 간단하게 유지
🧠 상태 전환이 자주 발생하는 시스템
예: 문, 커피머신, TCP 상태, 플레이어 등
// 상태 인터페이스
interface State {
void handle(Context context);
}
// 상태 구현
class OpenState implements State {
public void handle(Context context) {
System.out.println("문이 열려 있습니다. 이제 닫습니다.");
context.setState(new ClosedState());
}
}
class ClosedState implements State {
public void handle(Context context) {
System.out.println("문이 닫혀 있습니다. 이제 잠급니다.");
context.setState(new LockedState());
}
}
class LockedState implements State {
public void handle(Context context) {
System.out.println("문이 잠겨 있습니다. 이제 엽니다.");
context.setState(new OpenState());
}
}
// Context 클래스
class Context {
private State state;
public Context(State initialState) {
this.state = initialState;
}
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}
public class Main {
public static void main(String[] args) {
Context door = new Context(new OpenState());
door.request(); // 문이 열려 있습니다. 이제 닫습니다.
door.request(); // 문이 닫혀 있습니다. 이제 잠급니다.
door.request(); // 문이 잠겨 있습니다. 이제 엽니다.
}
}
요청(Request)을 캡슐화해서 호출자(Invoker)와 실행자(Receiver)를 분리하는 구조
요청을 객체로 캡슐화해서, 요청을 보낸 쪽과 실제 처리하는 쪽을 분리하고,
나중에 다시 실행하거나, 큐에 저장하거나, 취소할 수 있게 만드는 패턴
//1. 명령(Command) 인터페이스
interface Command {
void execute();
}
//2. 수신자(Receiver) – 실제 동작을 하는 클래스
class Light {
public void turnOn() {
System.out.println("💡 불을 켰습니다.");
}
public void turnOff() {
System.out.println("💡 불을 껐습니다.");
}
}
//3. 구체 명령(ConcreteCommand)
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.turnOn(); // 실제 동작을 Receiver에 위임
}
}
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.turnOff();
}
}
//4. 호출자(Invoker)
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
public class Main {
public static void main(String[] args) {
Light light = new Light();
Command lightOn = new LightOnCommand(light);
Command lightOff = new LightOffCommand(light);
RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton(); // 💡 불을 켰습니다.
remote.setCommand(lightOff);
remote.pressButton(); // 💡 불을 껐습니다.
}
}
요청을 순차적으로 전달하면서 처리할 수 있는 객체가 처리하게 만드는 행동(Behavioral) 패턴
즉, 누가 처리할지 모르지만 책임을 가진 객체들이 줄지어 대기하고
처리할 수 있는 객체가 나타날 때까지 요청을 넘겨주는 방식
ex)
🔁 요청 처리자가 여러 명 중에 하나일 수 있을 때
예: 이벤트 처리, 로깅, 필터 체인
🔀 요청 처리 순서를 유연하게 바꾸고 싶을 때
순서를 바꿔도 코드 수정이 최소화됨
🧱 클라이언트가 요청을 누가 처리하는지 몰라도 될 때
결합도를 낮출 수 있음
// 요청 처리 인터페이스
abstract class Handler {
protected Handler next;
public Handler setNext(Handler next) {
this.next = next;
return next;
}
public abstract void handle(String request);
}
// 구체 핸들러 A
class AuthHandler extends Handler {
public void handle(String request) {
if (request.equals("AUTH")) {
System.out.println("AuthHandler 처리 완료");
} else if (next != null) {
next.handle(request);
}
}
}
// 구체 핸들러 B
class LogHandler extends Handler {
public void handle(String request) {
if (request.equals("LOG")) {
System.out.println("LogHandler 처리 완료");
} else if (next != null) {
next.handle(request);
}
}
}
// 클라이언트
public class Main {
public static void main(String[] args) {
Handler handlerChain = new AuthHandler();
handlerChain.setNext(new LogHandler());
handlerChain.handle("LOG"); // → LogHandler 처리 완료
handlerChain.handle("AUTH"); // → AuthHandler 처리 완료
handlerChain.handle("ETC"); // → 아무도 처리 못 함 (silent fail)
}
}
//Observer 인터페이스: 관찰자를 나타내는 인터페이스
interface Observer {
public abstract void update(NumberGenerator generator);
//update를 호출하는 것은 숫자를 생성하는 NumberGenerator
//NumberGenerator가 내용이 갱신되었어요. 표시하는 쪽도 갱신해주세요라고 Observer에 전달하기 위한 메서드
}
//관찰하는 Observer를 저장하는 필드
public abstract class NumberGenerator {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
//중요! ★Observer에 통지한다.
public void notifyObserver{
for(Observer o: observers) {
o.update(this);
}
}
public abstract int getNumber();
public abstract void execute();
}
public class RandomNumberGenerator {
private Random random = new Random(); //난수 생성기
private int number; //현재 수
@Override
public int getNumber() {
return number;
}
//중요! 수를 생성한다. Observer 전체에 알림
@Override
public void execute() {
for(int i=0; i<20; i++){
number = random.nextInt(50);
notifyObservers();
}
}
}
public class DigitObserver implements Observer{
@Override
public void update(NumberGenerator generator) {
System.out.println("DigitObserver:" + generator.getNumber());
try {
Thread.sleep(100);
} catch (InterruptedException e){
}
}
}
public class GraphObserver implements Observer {
@Override
public void update(NumberGenerator generator) {
System.out.print("GraphObserver:");
int count = generator.getNumber();
for (int i=0; i<count; i++) {
System.outprint("*");
}
System.out.println("");
try{
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
public class Main{
public static void main(String[] args) {
NumberGenerator generator = new RandomNumberGenerator();
Observer observer1 = new DigitObserver();
Observer observer2 = new GraphObserver();
generator.addObserver(observer1);
generator.addObserver(observer2);
generator.execute();
}
}
Subject 역에는 여러 Observer가 등록되어 있다. notifyObservers의 메소드에서는 먼저 등록한 Observer의 update 메소드가 호출된다.
Subject 상태변화 -> Observer로 통지 -> Observer가 Subject의 메소드 호출 ->그에 따라 Subject상태 변화 -> Observer호출
MVC에서 Model과 View의 관계는 Observer 패턴의 Subject Observer 관계에 대응된다. Model은 표시 형식에 의존하지 않는 내부모델을 조작하는 부분. 그리고 View는 Model을 어떻게 보여 줄지 관리하는 부분. 일반적으로 하나의 model에 여러 View가 대응된다.
→ 내부 구조를 노출하지 않고 save()/restore() 만으로 이전 상태를 복원할 수 있음.
객체의 상태를 저장하고, 필요할 때 복원할 수 있게 해주는 행동 패턴
객체의 내부 상태를 캡슐화해서 저장
외부에서는 객체 내부를 직접 접근하지 않고 상태를 복원
캡슐화 유지가 핵심! (객체의 내부 상태를 외부에서 보지 않고 저장/복원)
// Memento: 저장할 상태 캡슐화
class Memento{
private final String state;
public Memento(String stateToSave){
this.state = stateToSave;
}
public String getState(){
return state;
}
}
// Originator: 상태를 가진 객체
class TextEditor{
private String content;
public void write(String text){
this.content = text;
}
public String read(){
return content;
}
public Memento save(){
return new Memento(content);
}
public void restore(Memento memento){
this.content = memento.getState();
}
}
// Caretaker: Memento를 저장하고 관리
class History{
private Memento backup;
public void saveBackup(Memento m){
this.backup = m;
}
public Memento getBackup(){
return backup;
}
}
// 사용 예
public class Main {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
History history = new History();
editor.write("초안입니다");
history.saveBackup(editor.save()); // 상태 저장
editor.write("실수로 바꿨음"); // 상태 변경
System.out.println(editor.read()); // → 실수로 바꿨음
editor.restore(history.getBackup()); // 복원
System.out.println(editor.read()); // → 초안입니다
}
}