Design Pattern : 행동 패턴(Behavioral Patterns) 2

김하영·2021년 2월 14일
1
post-thumbnail

6. 전략 패턴 (Strategy Pattern)

  • 전략 패턴이란?

행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴으로 같은 문제를 해결하는 여러 알고리즘이 클래스별로 캡슐화되어 있고 이들이 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 알고리즘으로 해결할 수 있게 하는 디자인 패턴이다.

즉, 전략을 쉽게 바꿀 수 있도록 해주는 디자인 패턴이다.

  • 전략이란?

어떤 목적을 달성하기 위해 일을 수행하는 방식, 비즈니스 규칙, 문제를 해결하는 알고리즘 등

특히 게임 프로그래밍에서 게임 캐릭터가 자신이 처한 상황에 따라 공격이나 행동하는 방식을 바꾸고 싶을 때 스트래티지 패턴은 매우 유용하다.

  • 전략 패턴 장점
  1. 시스템의 구조 및 Context Class를 변경하지 않고 요청에 맞는 로직을 추가 및 수정할 수 있다.

  2. 같은 인터페이스 양식을 가진 알고리즘을 별도로 캡슐화하여 코드의 가독성이 높아지며, 생산성이 높아진다.

  3. 요청에 맞는 로직을 실시간으로 변경할 수 있다.

  • 전략 패턴 구조

  1. Strategy
    인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시한다.

  2. ConcreteStrategy
    스트래티지 패턴에서 명시한 알고리즘을 실제로 구현한 클래스이다.

  3. 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();
    }
}

7. 템플릿 메서드 패턴 (Template method pattern)

  • 템플릿 메서드 패턴이란?

상위 클래스에서 처리의 흐름을 제어하며, 하위클래스에서 처리의 내용을 구체화하는 디자인 패턴이다.

공통되는 사항은 상위 추상 클래스에서 구현하며, 각 객체마다 다른 부분은 하위 클래스에서 구현한다.
상속을 통한 확장 개발 방법으로 코드의 중복을 줄이고, 리팩토링(Refactoring)에 유리하여 가장 많이 사용되는 패턴 중 하나이다.

  • 템플릿 메서드 패턴 구조

  1. Abstract Class : 추상 클래스로 templateMethod를 정의한다.
  2. Concrete Class : 부모 클래스에서 abstract로 정의된 templateMethod를 구현한다.
  • 예제 코드

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);
    }
}

8. 방문자 패턴 (visitor Pattern)

  • 방문자 패턴이란?

실제 로직을 가지고 있는 객체(Visitor)가 로직을 적용할 객체(Element)를 방문하면서 실행하는 패턴이다.

즉, 로직과 구조를 분리하는 패턴이라고 볼 수 있다.
로직과 구조가 분리되면 구조를 수정하지 않고도 새로운 동작을 기존 객체 구조에 추가할 수 있다.

비슷한 종류의 객체들을 가진 그룹에서 작업을 수행해야 할 때 주로 사용되는 패턴이다.

  • 방문자 패턴 구조

  1. Visitor
    명령을 수행하기 위해 필요한 메소드를 정의하는 인터페이스이다.

  2. ConcreteVisitor
    명령을 수행하는 메소드를 구현한다.

  3. Element
    Visit를 사용할 수 있는지 확인하는 accept 메소드를 정의하는 인터페이스이다.

  4. 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;
    }
}

9. 중재자 패턴 (Mediator Pattern)

  • 중재자 패턴이란?

객체들 간의 상호작용 행위를 정리하여 모은 중재자 객체를 따로 두어 관리하는 디자인 패턴이다.
모든 클래스간의 복잡한 로직(상호작용)을 캡슐화하여 하나의 클래스에 위임하여 처리하는 패턴이다.

즉, M:N의 관계에서 M:1의 관계로 복잡도를 떨어뜨려 유지 보수 및 재사용의 확장성에 유리한 패턴이다. 커뮤니케이션을 하고자 하는 객체가 있을 때 서로가 커뮤니케이션 하기 복잡한 경우 이를 해결해주고 서로 간 쉽게 해주며 커플링을 약화시켜주는 패턴이다.

객체들간 커뮤니케이션 정의가 되었지만, 복잡할 때(종합이 필요할 때)사용한다.

  • 중재자 패턴이 사용되는 경우
  1. 객체들간의 관계가 매우 복잡하여 객체의 재사용에 부담이 갈 경우
  • 중재자 패턴 장점
  1. 객체들 간 수정을 하지않고 관계를 수정할 수 있다.

  2. 객체들간의 관계의 복잡도, 의존성 및 결합도를 감소시킨다.

  • 중재자 패턴 구조

  1. Mediator
    Colleague 객체간의 상호작용을 위한 인터페이스를 정의한다.

  2. ConcreteMediator
    Mediator의 인터페이스를 구현하여 객체간의 상호작용을 조정한다.

  3. Colleague
    다른 Colleague와의 상호작용을 위한 인터페이스를 정의한다.

  4. 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();
    }
}

10. 상태 패턴 (State Pattern)

  • 상태 패턴이란?

상태 디자인 패턴은 객체 내부의 상태에 따라 동작을 변경해야할 때 사용하는 디자인 패턴이다.

즉, 객체의 특정 상태를 클래스로 선언하고 클래스에서는 해당 상태에서 할 수 있는 행위들을 메서드로 정의한다.
그리고 이러한 각 상태 클래스들을 인터페이스로 캡슐화 하여 클라이언트에서 인터페이스를 호출한다.

  • 상태 패턴 장점
  1. 하나의 객체에 대한 여러 동작을 구현해야할 때 상태 객체만 수정하므로 동작의 추가, 삭제 및 수정이 간단하다.

  2. State 패턴을 사용하면 객체의 상태에 따른 조건문(if/else, switch)이 줄어들어 코드가 간결해지고 가독성이 올라간다.

  • 상태 패턴 구조

  1. Context
    객체의 상태를 정의하는 데 사용되는 메소드를 정의하는 인터페이스이다.

  2. State
    상태에 따른 동작을 정의하는 인터페이스이다.

  3. 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();
    }
}

11. 기념품 패턴 (Memento Pattern)

  • 기념품 패턴이란?

기념품 패턴은 객체의 상태 정보를 가지는 클래스를 따로 생성하여 객체의 상태를 저장하거나 이전 상태로 복원할 수 있게 해주는 패턴이다. 기념품 패턴은 바둑, 오목, 체스 등의 보드게임 등에서 '무르기' 기능을 구현할 때 사용되기도 한다.

단, 이전 상태의 객체를 저장하기 위한 Originator가 클 경우 많은 메모리가 필요하다.

  • 기념품 패턴 구조

  1. Originator
    객체의 상태를 저장한다.
    Memento 객체를 생성하며 후에 Memento를 사용하여 실행 취소(undo)를 할 수 있다.

  2. Memento
    Originator의 상태를 유지하는 객체이다.(POJO)

  3. 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));
    }
}

profile
Back-end Developer

0개의 댓글