• Home
  • About
    • 밤에 쓴 코드 photo

      밤에 쓴 코드

      부엉이의 개발 노트

    • Learn More
    • Facebook
    • Instagram
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Design Pattern 5

09 Jul 2019

Reading time ~4 minutes

커맨드패턴


배달앱을 만들어보려고 하는데 문제가 있다.

지금 새로 앱을 만들 예정이라서 아직 스토어를 등록한 사장님이 한 분도 안계시기 때문이다.

새로운 사장님들은 지속적으로 스토어를 등록하실 건데 말이다.

또 스토어별로 주문옵션이 되게 상이할 것 같다는 느낌이 들었다.

예측할 수 없는 변화에 대해서 어떻게 최대한 유연하게 반응할 수 있는 앱을 만들 수 있을까?

음 일단 변화에 대해 유연한 구조를 설계하기위해서는 필요한 것들이 무엇일까?

일단 서로 서로를 최소한으로 알거나 몰라도 되는 경우에는 아예 분리시켜버리는 게 좋을 수 있다.

배달앱은 스토어를 모르게 하고 하나의 배달주문이 발생하면 그 주문에 있는 스토어를 자세히는 모르되

주문에 적혀있는 스토어로 연결만 해주면 될 것같다.

배달주문은 다양한 형태로 존재할 것이다.

근데 배달주문을 어떻게 일반화하여서 표현할 수 있을까?

배달은 일종의 요청이다. 요청은 요청을 받을 상대가 있어야한다.

요청은 커맨드 객체 라는 말로 표현할 것이고, 요청을 받을 상대는 리시버 객체 로 표현할 것이다.

public interface Command {
    void execute();
}

주문은 주문을 했을 때 주문을 받은 스토어가 해야할 일이 있을 것이다.

하지만 주문을 받는 스토어의 종류는 다양할 것이라는 것과 , 또 주문을 받은 스토어가 해야할 일이 너무 다양하기 때문에 그 부분을 정해놓고 앱을 만드는 게 확장면에서는 좋지 않을 것 같다는 생각이 들었다.

강조된 부분은 앞으로 변할 수 있는 부분이라는 생각이 들었다. 이 부분을 캡슐화하여 숨기고, 그 캡슐화된 하나의 방법만을 사용하게 된다면, 내부가 바뀌었다고 외부가 알아야할 일은 없을 것 같다.

execute() 라는 하나의 메소드로 주문(요청,호출) 자체를 캡슐화할 것이다.

오..

말하던 도중 스토어등록을 원하시는 사장님이 생겼다.

public class 서브웨이 {
    class 샌드위치 {
        String 메뉴이름;
        public 샌드위치(String 메뉴이름){
            this.메뉴이름 = 메뉴이름;
        }
        public String toString(){
            return this.메뉴이름+"🥙";
        }
    }

    public void 샌드위치주문(String 메뉴이름) {
        샌드위치 주문메뉴 = new 샌드위치(메뉴이름);
        System.out.println(주문메뉴+"배달하였습니다.");
    }   
}

샌드위치를 판매하는 가게가 등록 되었다.

사장님이 주문을 기다리시니 어서 앱에서 주문을 받을 수 있게 해야겠다.

일단 주문을 캡슐화한 Command 를 구현한 하나의 주문 객체를 만들 것이다.

public class 샌드위치주문 implements Command {

    서브웨이 서브웨이;
    String 메뉴이름;

    public 샌드위치주문(서브웨이 서브웨이, String 메뉴이름) {
        this.서브웨이 = 서브웨이;
        this.메뉴이름 = 메뉴이름;
    }

    @Override
    public void execute() {
        서브웨이.샌드위치주문(메뉴이름);
    }
}
샌드위치 가게라는 걸 추상화하면 좋겠지만, 이번 주제에서는 요청,호출 을 추상화하는 걸 중점적으로 할 것이기에 

예제가 복잡해지는 걸 방지하고자 이런 식으로 구현을 했다.

그럼 이제 배달앱은 저 주문을 받아서 사장님이 영업하시는 스토어로 메시지를 무사히 전달하면 될 것같다.

이제 내가 만든 배달앱을 공개하겠다.

public class 배달부_엉이 {

    Command 주문;

    public void set주문(Command 주문) {
        this.주문 = 주문;
    }

    public void 주문요청(){
        주문.execute();
    }
}

ㅎ ㅡㅎ 내가 만들어낸 배달부_엉이 라는 배달앱이다.

주문이 들어오면 주문에 써있는 행동을 그대로 수행해주는 시시한 앱이다.

하지만 주문 조건만 맞는 다면, 어떤 주문이라도 잘 해결할 수있는 훌륭한 앱이다.

  • 주문조건 : 인터페이스 구현 을 이야기한다.

주문을 한번 해보자.

public class Main {
    public static void main(String [] args) {
        // 배달앱을 앱스토어에서 받았다!
        배달부_엉이 배달앱 =  new 배달부_엉이();
        // 메뉴를 고르고 주문항목을 선택했다.
        Command 주문 = new 샌드위치주문(new 서브웨이(), "치킨로티세리");
        // 배달앱에 장바구니에 주문을 넣어두고
        배달앱.set주문(주문);
        // 결제와 동시에 주문을 했다.
        배달앱.주문요청();
    }
}

스크린샷 2019-07-09 오후 5 49 17

만족스러운 주문이었다.

두개의 다른 메뉴를 주문한다면???

스크린샷 2019-07-09 오후 5 50 56

두 메뉴 모두 따듯하게 배달되는 걸 볼 수있다.

아직 배달부_엉이앱(커맨드패턴)의 강력함을 다 보여주지 않았다.

바로 새로운 스토어가 계속적으로 생겨도 , 배달앱은 문제없이 잘 될 것이라는 거다.

축하할 일이 생겼다.👏🎉🎊

배달부_엉이앱에 애플스토어⌚️💻📱(new Receiver) 가 입점하고 싶다는 문의가 들어왔다.

우리는 이제 이런 제품을 살 수있다.

class 아이폰 {
        String 색상;
        int 시리즈;
        int 용량;

        public 아이폰(String 색상, int 시리즈, int 용량) {
            this.색상 = 색상;
            this.시리즈 = 시리즈;
            this.용량 = 용량;
        }

        public String toString() {
            return "아이폰"+시리즈+"📱"+색상+"("+용량+")";
        }
    }
class 애플워치 {
        String 소재;
        int 시리즈;
        int 용량;

        public 애플워치(String 소재, int 시리즈, int 용량) {
            this.소재 = 소재;
            this.시리즈 = 시리즈;
            this.용량 = 용량;
        }

        public String toString() {
            return "애플워치"+시리즈+"⌚️"+소재+"("+용량+")";
        }
    }
 class 맥북 {
        String 색상;
        String 라인;
        int 용량;
        boolean 키보드마우스추가;

        public 맥북(String 색상, String 라인, int 용량,boolean 키보드마우스추가) {
            this.색상 = 색상;
            this.라인 = 라인;
            this.용량 = 용량;
            this.키보드마우스추가 = 키보드마우스추가;
        }

        public String toString() {
            String 키보드마우스 = 키보드마우스추가 ? "🖱⌨️" : "";
            return "맥북"+라인+"💻"+색상+"("+용량+")" + 키보드마우스;
        }
    }

그리고 이번에 입점한 애플스토어이다.

public class 애플스토어 {
 
    public 아이폰 아이폰주문(String 색상, int 시리즈, int 용량) {
        return new 아이폰(색상,시리즈,용량);
    }

    public 맥북 맥북주문(String 색상, String 라인, int 용량,boolean 키보드마우스추가여부) {
        return new 맥북(색상,라인,용량,키보드마우스추가여부);
    }

    public 애플워치 애플워치주문(String  소재, int 시리즈, int 용량) {
        return new 애플워치(소재,시리즈,용량);
    }


}

맥북이 없는 부엉이는 신난 눈치이다.

맥북을 어디한번 주문해볼까?

그럼 주문객체(커맨드객체)만 만들면 되겠다.

public class 아이폰주문 implements Command {
    애플스토어 주문상점;
    String 색상;
    int 시리즈;
    int 용량;
    주문자 주문자;

    public 아이폰주문(애플스토어 주문상점, String 색상, int 시리즈, int 용량, 주문자 주문자) {
        this.주문상점 = 주문상점;
        this.색상 = 색상;
        this.시리즈 = 시리즈;
        this.용량 = 용량;
        this.주문자 = 주문자;
    }

    @Override
    public void execute() {
      주문자.받다(주문상점.아이폰주문(색상,시리즈,용량));
    }
}
public class 맥북주문 implements Command {
    애플스토어 주문상점;
    String 색상;
    String 라인;
    int 용량;
    boolean 키보드마우스추가;
    주문자 주문자;

    public 맥북주문(애플스토어 주문상점, String 색상, String 라인, int 용량, boolean 키보드마우스추가, 주문자 주문자) {
        this.주문상점 = 주문상점;
        this.색상 = 색상;
        this.라인 = 라인;
        this.용량 = 용량;
        this.키보드마우스추가 = 키보드마우스추가;
        this.주문자 = 주문자;
    }

    @Override
    public void execute() {
        주문자.받다(주문상점.맥북주문(색상,라인,용량,키보드마우스추가));
    }
}

복잡하기때문에 이번에는 애플워치는 별로 구미가 당기지 않기때문에 다른 두개에 대해서만 구현해보겠다.

이번에는 주문자의 주소를 입력해서 주문한 주소로 제품이 도착할 수 있게 앱을 업그레이드 해보았다.

이렇게 다양한 주문이 앞으로도 계속 생길 수 있지만 , 배달앱은 계속 유지가 가능할 것이다.

해당하는 주문(커맨드객체)만 계속 만들어 낸다면 말이다!!

💳💶💷💴💸💵주문신청~~~~

 public static void main(String [] args) {
		    배달부_엉이 배달앱 =  new 배달부_엉이();
        애플스토어 코드스쿼드점 = new 애플스토어();

        주문자 부엉이 = new 주문자("🦉");
        Command 아이폰주문 = new 아이폰주문(코드스쿼드점,"블랙",11,64,부엉이);

        배달앱.set주문(아이폰주문);
        배달앱.주문요청();

        주문자 블루 = new 주문자("BLU");
        Command 맥북주문 = new 맥북주문(코드스쿼드점,"블랙","Pro",512,true,블루);

        배달앱.set주문(맥북주문);
        배달앱.주문요청();

    }

주문완료!

스크린샷 2019-07-09 오후 6 44 04

UML

스크린샷 2019-07-09 오후 7 10 41 스크린샷 2019-07-09 오후 7 13 26



Share Tweet +1