행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴으로 같은 문제를 해결하는 여러 알고리즘이 클래스별로 캡슐화되어 있고 이들이 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 알고리즘으로 해결할 수 있게 하는 디자인 패턴이다.
즉, 전략을 쉽게 바꿀 수 있도록 해주는 디자인 패턴이다.
어떤 목적을 달성하기 위해 일을 수행하는 방식, 비즈니스 규칙, 문제를 해결하는 알고리즘 등
특히 게임 프로그래밍에서 게임 캐릭터가 자신이 처한 상황에 따라 공격이나 행동하는 방식을 바꾸고 싶을 때 스트래티지 패턴은 매우 유용하다.
시스템의 구조 및 Context Class를 변경하지 않고 요청에 맞는 로직을 추가 및 수정할 수 있다.
같은 인터페이스 양식을 가진 알고리즘을 별도로 캡슐화하여 코드의 가독성이 높아지며, 생산성이 높아진다.
요청에 맞는 로직을 실시간으로 변경할 수 있다.
Strategy
인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시한다.
ConcreteStrategy
스트래티지 패턴에서 명시한 알고리즘을 실제로 구현한 클래스이다.
Context
스트래티지 패턴을 이용하는 역할을 수행한다.
필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 setter 메서드(‘집약 관계’)를 제공한다.
KickBehavior
public interface KickBehavior {
public void kick();
}
JumpBehavior
public interface JumpBehavior {
public void jump();
}
LightningKick : ConcreteStrategy
public class LightningKick implements KickBehavior{
@Override
public void kick() {
System.out.println("Lightning Kick");
}
}
TornadoKick : ConcreteStrategy
public class TornadoKick implements KickBehavior{
@Override
public void kick() {
System.out.println("Tornado Kick");
}
}
LongJump : ConcreteStrategy
public class LongJump implements JumpBehavior{
@Override
public void jump() {
System.out.println("Long Jump");
}
}
ShortJump : ConcreteStrategy
public class ShortJump implements JumpBehavior{
@Override
public void jump() {
System.out.println("Short Jump");
}
}
Fighter : Context
public abstract class Fighter {
KickBehavior kickBehavior;
JumpBehavior jumpBehavior;
public Fighter(KickBehavior kickBehavior, JumpBehavior jumpBehavior) {
this.kickBehavior = kickBehavior;
this.jumpBehavior = jumpBehavior;
}
public void punch() {
System.out.println("Default Punch");
}
public void kick() {
kickBehavior.kick();
}
public void jump() {
jumpBehavior.jump();
}
public void roll() {
System.out.println("Default Roll");
}
public void setKickBehavior(KickBehavior kickBehavior) {
this.kickBehavior = kickBehavior;
}
public void setJumpBeahvior(JumpBehavior jumpBehavior) {
this.jumpBehavior = jumpBehavior;
}
public abstract void display();
}
Kim
public class Kim extends Fighter{
public Kim(KickBehavior kickBehavior, JumpBehavior jumpBehavior) {
super(kickBehavior, jumpBehavior);
}
@Override
public void display() {
System.out.println("KIM");
}
}
Main Class
public class FighterMain {
public static void main(String[] args) {
JumpBehavior shortJump = new ShortJump();
JumpBehavior LongJump = new LongJump();
KickBehavior tornadoKick = new TornadoKick();
Fighter kim = new Kim(tornadoKick, shortJump);
kim.display();
kim.punch();
kim.kick();
kim.jump();
kim.setJumpBeahvior(LongJump);
kim.jump();
}
}
상위 클래스에서 처리의 흐름을 제어하며, 하위클래스에서 처리의 내용을 구체화하는 디자인 패턴이다.
공통되는 사항은 상위 추상 클래스에서 구현하며, 각 객체마다 다른 부분은 하위 클래스에서 구현한다.
상속을 통한 확장 개발 방법으로 코드의 중복을 줄이고, 리팩토링(Refactoring)에 유리하여 가장 많이 사용되는 패턴 중 하나이다.
OrderProcessTemplate : Abstract Class
public abstract class OrderProcessTemplate {
public abstract void doSelect();
public abstract void doPayment();
public abstract void doDelivery();
public final void giftWrap() {
try {
System.out.println("Gift wrap successful");
} catch (Exception e) {
System.out.println("Gift wrap unsuccessful");
}
}
public final void processOrder(boolean isGift) {
doSelect();
doPayment();
if (isGift) giftWrap();
doDelivery();
}
}
StoreOrder : Concrete Class
public class StoreOrder extends OrderProcessTemplate {
@Override
public void doSelect() {
System.out.println("Do Select : Customer chooses the item from shelf.");
}
@Override
public void doPayment() {
System.out.println("Do Payment : Pays at counter through cash/POS");
}
@Override
public void doDelivery() {
System.out.println("Do Delivery : Item deliverd to in delivery counter.");
}
}
NetOrder : Concrete Class
public class NetOrder extends OrderProcessTemplate {
@Override
public void doSelect() {
System.out.println("Do Select : Item added to online shopping cart");
System.out.println("Do Select : Get gift wrap preference");
System.out.println("Do Select : Get delivery address.");
}
@Override
public void doPayment() {
System.out.println
("Do Payment : Online Payment through Netbanking, card or Paytm");
}
@Override
public void doDelivery() {
System.out.println
("Do Delivery : Ship the item through post to delivery address");
}
}
Main Class
public class OrderMain {
public static void main(String[] args) {
OrderProcessTemplate netOrder = new NetOrder();
netOrder.processOrder(true);
System.out.println();
OrderProcessTemplate storeOrder = new StoreOrder();
storeOrder.processOrder(true);
}
}
실제 로직을 가지고 있는 객체(Visitor)가 로직을 적용할 객체(Element)를 방문하면서 실행하는 패턴이다.
즉, 로직과 구조를 분리하는 패턴이라고 볼 수 있다.
로직과 구조가 분리되면 구조를 수정하지 않고도 새로운 동작을 기존 객체 구조에 추가할 수 있다.
비슷한 종류의 객체들을 가진 그룹에서 작업을 수행해야 할 때 주로 사용되는 패턴이다.
Visitor
명령을 수행하기 위해 필요한 메소드를 정의하는 인터페이스이다.
ConcreteVisitor
명령을 수행하는 메소드를 구현한다.
Element
Visit를 사용할 수 있는지 확인하는 accept 메소드를 정의하는 인터페이스이다.
ConcreteElement
Visitable에서 정의된 accept 메소드를 구현하며 Visitor객체는 이 객체를 통하여 명령이 전달된다.
ItemElement : Element
public interface ItemElement {
public int accept(ShoppingCartVisitor visitor);
}
ShoppingCartVisitor : Visitor
public interface ShoppingCartVisitor {
int visit(Book book);
int visit(Fruit fruit);
}
Book : ConcreteElement
public class Book implements ItemElement{
private int price;
private String isbnNumber;
public Book(int cost, String isbn) {
this.price = cost;
this.isbnNumber = isbn;
}
public int getPrice() {
return price;
}
public String getIsbnNumber() {
return isbnNumber;
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
Fruit : ConcreteElement
public class Fruit implements ItemElement{
private int pricePerKg;
private int weight;
private String name;
public Fruit(int priceKg, int weight, String name) {
this.pricePerKg = priceKg;
this.weight = weight;
this.name = name;
}
public String getName() {
return name;
}
public int getPricePerKg() {
return pricePerKg;
}
public int getWeight() {
return weight;
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
ShoppingCartVisitorImpl : ConcreteVisitor
public class ShoppingCartVisitorImpl implements ShoppingCartVisitor{
@Override
public int visit(Book book) {
int cost = 0;
if(book.getPrice() > 50) cost = book.getPrice()-5;
else cost = book.getPrice();
System.out.println("Book ISBN : " + book.getIsbnNumber() + " cost = " + cost);
return cost;
}
@Override
public int visit(Fruit fruit) {
int cost = fruit.getPricePerKg() * fruit.getWeight();
System.out.println(fruit.getName() + " cost = " + cost);
return cost;
}
}
Main Class
public class ShoppingCartMain {
public static void main(String[] args) {
ItemElement[] items = new ItemElement[]{new Book(20, "What is Justice?"), new Book(100, "DesignPattern"),
new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")};
int total = calculatePrice(items);
System.out.println("Total Cost = " + total);
}
private static int calculatePrice(ItemElement[] items) {
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int sum = 0;
for (ItemElement item : items) {
sum = sum + item.accept(visitor);
}
return sum;
}
}
객체들 간의 상호작용 행위를 정리하여 모은 중재자 객체를 따로 두어 관리하는 디자인 패턴이다.
모든 클래스간의 복잡한 로직(상호작용)을 캡슐화하여 하나의 클래스에 위임하여 처리하는 패턴이다.
즉, M:N의 관계에서 M:1의 관계로 복잡도를 떨어뜨려 유지 보수 및 재사용의 확장성에 유리한 패턴이다. 커뮤니케이션을 하고자 하는 객체가 있을 때 서로가 커뮤니케이션 하기 복잡한 경우 이를 해결해주고 서로 간 쉽게 해주며 커플링을 약화시켜주는 패턴이다.
객체들간 커뮤니케이션 정의가 되었지만, 복잡할 때(종합이 필요할 때)사용한다.
객체들 간 수정을 하지않고 관계를 수정할 수 있다.
객체들간의 관계의 복잡도, 의존성 및 결합도를 감소시킨다.
Mediator
Colleague 객체간의 상호작용을 위한 인터페이스를 정의한다.
ConcreteMediator
Mediator의 인터페이스를 구현하여 객체간의 상호작용을 조정한다.
Colleague
다른 Colleague와의 상호작용을 위한 인터페이스를 정의한다.
ConcreteColleague
Colleague의 인터페이스를 구현하며 Mediator를 통해 다른 Colleague와 상호작용한다.
Command : Mediator
public interface Command {
void land();
}
Flight : ConcreteMediator
public class Flight implements Command {
private IATCMediator atcMediator;
public Flight(IATCMediator atcMediator) {
this.atcMediator = atcMediator;
}
@Override
public void land() {
if (atcMediator.isLandingOK()) {
System.out.println("Successfylly Landed.");
atcMediator.setLandingStatus(true);
} else {
System.out.println("Waiting for Landing.");
}
}
public void getReady() {
System.out.println("Ready for landing");
}
}
Runway : ConcreteMediator
public class Runway implements Command{
private IATCMediator atcMediator;
public Runway(IATCMediator atcMediator) {
this.atcMediator = atcMediator;
atcMediator.setLandingStatus(true);
}
@Override
public void land() {
System.out.println("Lading permission granted.");
atcMediator.setLandingStatus(true);
}
}
IATCMediator : Colleague
public interface IATCMediator {
public void registerRunway(Runway runway);
public void registerFilght(Flight flight);
public boolean isLandingOK();
public void setLandingStatus(boolean status);
}
ATCMediator : ConcreteColleague
public class ATCMediator implements IATCMediator {
private Flight flight;
private Runway runway;
public boolean land;
@Override
public void registerRunway(Runway runway) {
this.runway = runway;
}
@Override
public void registerFilght(Flight flight) {
this.flight = flight;
}
@Override
public boolean isLandingOK() {
return land;
}
@Override
public void setLandingStatus(boolean status) {
land = status;
}
}
Main Class
public class IATCMediatorMain {
public static void main(String[] args) {
IATCMediator atcMediator = new ATCMediator();
Flight cptJack = new Flight(atcMediator);
Runway runnerWay = new Runway(atcMediator);
atcMediator.registerFilght(cptJack);
atcMediator.registerRunway(runnerWay);
cptJack.getReady();
runnerWay.land();
cptJack.land();
}
}
상태 디자인 패턴은 객체 내부의 상태에 따라 동작을 변경해야할 때 사용하는 디자인 패턴이다.
즉, 객체의 특정 상태를 클래스로 선언하고 클래스에서는 해당 상태에서 할 수 있는 행위들을 메서드로 정의한다.
그리고 이러한 각 상태 클래스들을 인터페이스로 캡슐화 하여 클라이언트에서 인터페이스를 호출한다.
하나의 객체에 대한 여러 동작을 구현해야할 때 상태 객체만 수정하므로 동작의 추가, 삭제 및 수정이 간단하다.
State 패턴을 사용하면 객체의 상태에 따른 조건문(if/else, switch)이 줄어들어 코드가 간결해지고 가독성이 올라간다.
Context
객체의 상태를 정의하는 데 사용되는 메소드를 정의하는 인터페이스이다.
State
상태에 따른 동작을 정의하는 인터페이스이다.
ConcreteState
State에서 정의된 메소드를 구현하는 클래스이다.
MobileAlertState : State
public interface MobileAlertState {
public void alert(AlertStateContext ctx);
}
AlertStateContext : Context
public class AlertStateContext {
private MobileAlertState currentState;
public AlertStateContext() {
currentState = new Vibration();
}
public void setState(MobileAlertState state) {
currentState = state;
}
public void alert() {
currentState.alert(this);
}
}
Vibration : ConcreteState
public class Vibration implements MobileAlertState {
@Override
public void alert(AlertStateContext ctx) {
System.out.println("Vibration");
}
}
Slient : ConcreteState
public class Slient implements MobileAlertState {
@Override
public void alert(AlertStateContext ctx) {
System.out.println("Silent");
}
}
Main Class
public class AlertMain {
public static void main(String[] args) {
AlertStateContext stateContext = new AlertStateContext();
stateContext.alert();
stateContext.setState(new Slient());
stateContext.alert();
}
}
기념품 패턴은 객체의 상태 정보를 가지는 클래스를 따로 생성하여 객체의 상태를 저장하거나 이전 상태로 복원할 수 있게 해주는 패턴이다. 기념품 패턴은 바둑, 오목, 체스 등의 보드게임 등에서 '무르기' 기능을 구현할 때 사용되기도 한다.
단, 이전 상태의 객체를 저장하기 위한 Originator가 클 경우 많은 메모리가 필요하다.
Originator
객체의 상태를 저장한다.
Memento 객체를 생성하며 후에 Memento를 사용하여 실행 취소(undo)를 할 수 있다.
Memento
Originator의 상태를 유지하는 객체이다.(POJO)
Caretaker
마치 게임의 세이브포인트처럼 복수의 Memento의 상태를 유지해주는 객체이다.
Life : Originator / Memento
public class Life {
private String time;
public void setTime(String time) {
System.out.println("Setting Time : " + time);
this.time = time;
}
public Memento saveToMemento() {
System.out.println("Saving time to Memento");
return new Memento(time);
}
public void restoreFromMemento(Memento memento) {
time = memento.getSavedTime();
System.out.println("Time restored from Memento : " + time);
}
public static class Memento {
private final String time;
public Memento(String timeToSave) {
this.time = timeToSave;
}
public String getSavedTime() {
return time;
}
}
}
Main Class : Caretaker
public class LifeMain {
public static void main(String[] args) {
List<Life.Memento> savedTimes = new ArrayList<Life.Memento>();
Life life = new Life();
life.setTime("1000 B.C.");
savedTimes.add(life.saveToMemento());
life.setTime("1000 A.D.");
savedTimes.add(life.saveToMemento());
life.setTime("2000 A.D.");
savedTimes.add(life.saveToMemento());
life.setTime("4000 A.D.");
savedTimes.add(life.saveToMemento());
life.restoreFromMemento(savedTimes.get(0));
}
}