SELF STUDY/Design Pattern

[디자인패턴] 커맨드 패턴 | Command Pattern | 안드로이드 예제

호이호이호잇 2021. 9. 29. 01:01
728x90
반응형

우는 아이도 보면 울음을 그친다는 마법의.... 리모컨! 

이 떠오르는 이번 디자인 패턴!! 은 바로바로바로!!!!

입니다.!


커맨드 패턴이란 ?

요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 매서드 이름, 매개변수 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수 있게 하는 패턴이다.

출처 : 위키백과


쉽게 다시 설명을 하자면!

조명을 컨트롤 할 수 있는 리모컨이 있다고 생각해보자.

아래 그림에서 빨간 동그라미가 쳐져있는 버튼을 누르면 조명이 켜지게 된다.

이때 리모컨에 대한 구현을 어떻게 하면 좋을까 ?

 

 

Q. 각 버튼에 대해 기능을 지정해 주면 되지 않을까?

1번, 2번, 3번 ... 버튼들에 대해 각각의 기능을 정의해주면 되지 않을까 생각해본다.

if(clickedButtonId == 1){
	lightOn();
}

이렇게 했을 때, lightOn 커맨드에 변경이 생기거나 button1에 대한 기능을 바꾸고 싶을 때 코드 전체에 대한 수정이 필요하게 된다. 즉 간단한 기능 수정을 하려고 했는데, 대 공사가 되어 버릴 것 이다.

 

이러한 불필요한 수정을 최대한 피하기 위해서는 결합도를 낮추어야 한다.

 

커맨드간 결합도를 낮추기 위해 커맨드 패턴을 사용하면 된다.

버튼은 버튼이 눌렸음 만을 알려주고, 그를 받은 곳에서 기능을 처리하는 것 이다.

 

패턴에 대한 전체적인 구조이다.

 

내가 짠 예제와 함께 정리를 해보겠다.

 

일단 내가 짠 예제는 A,B,C,D,E 버튼이 있고 이 버튼 들이 각각 아래와 같은 역할을 한다.

각각의 기능을 커맨드 패턴을 적용시켜 동작하도록 구현해 보았다.

위 패턴 구조에 맞춰서 내 소스를 설명하고, 그 뒤 전체적인 소스와 동작 확인 영상을 확인하면 좋을 것 같다.!

 

- ConcreteCommand

  : Command의 틀.

   위의 구조와 달리 나는 undo를 stack으로 구현해주었다..!

interface Command {
    public void execute();
}

 

- Command

 :  실제 사용하는 커맨드.

class LightOffCommand implements Command {
    Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }
}

class TVOnCommand implements Command {
    TV tv;

    public TVOnCommand(TV tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.on();
    }
}

class TVOffCommand implements Command {
    TV tv;

    public TVOffCommand(TV tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.off();
    }
}

 

- Invoker

: 커맨드를 지정해줌

class SimpleRemoteControl {
    Command slot;

    public SimpleRemoteControl() {}
    public void setCommand(Command command) {
        this.slot = command;
    }
    public void buttonWasPressed() {
        slot.execute();
    }
}

 

- Receiver

 : 커맨드를 실행해주는 부분.

  나의 경우에는 command를 지정해주는 부분이 함께 있다.!

  다 따로하는 것이 시간이 더 걸릴 듯 하여, Command 변수 하나를 사용하도록 하였다.!

@Override
public void onClick(View v) {
    switch(v.getId()) {
        case R.id.buttonA:
            nowCommand = new LightOnCommand(new Light(this));
            simpleRemoteControl.setCommand(nowCommand);
            simpleRemoteControl.buttonWasPressed();
            usedCommandList.add(nowCommand);
            break;
        case R.id.buttonB:
            nowCommand = new LightOffCommand(new Light(this));
            simpleRemoteControl.setCommand(nowCommand);
            simpleRemoteControl.buttonWasPressed();
            usedCommandList.add(nowCommand);
            break;
        case R.id.buttonC:
            nowCommand = new TVOnCommand(new TV(this));
            simpleRemoteControl.setCommand(nowCommand);
            simpleRemoteControl.buttonWasPressed();
            usedCommandList.add(nowCommand);
            break;
        case R.id.buttonD:
            nowCommand = new TVOffCommand(new TV(this));
            simpleRemoteControl.setCommand(nowCommand);
            simpleRemoteControl.buttonWasPressed();
            usedCommandList.add(nowCommand);
            break;
        case R.id.buttonUndo:
            try{
                nowCommand = usedCommandList.pop();
                if(nowCommand != null){
                    simpleRemoteControl.setCommand(nowCommand);
                    simpleRemoteControl.buttonWasPressed();
                }
            }catch(EmptyStackException e){
                Log.d("CommandTest", "No more undo command");
            }
            break;
    }
}

결국 ButtonA가 눌렸을 때, Button A에 지정된 기능(light on)을 바로 실행하는 것이 아닌

ButtonA -> buttonWasPressed -> execute -> lighton 이 되도록 캡슐화가 된 것 이다.!

이렇게 되면 lighton()에 대한 수정이 필요할 경우, 그 부분만 수정이 가능해 더욱 더 편리한 구조가 된다.!

 

전체적인 소스이다.

package com.example.designpattern;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.util.EmptyStackException;
import java.util.Stack;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

        SimpleRemoteControl simpleRemoteControl;
        Command nowCommand;

        Stack<Command> usedCommandList;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            init();

            simpleRemoteControl = new SimpleRemoteControl();
        }

        private void init() {
            Button buttonA = findViewById(R.id.buttonA);
            buttonA.setOnClickListener(this);

            Button buttonB = findViewById(R.id.buttonB);
            buttonB.setOnClickListener(this);

            Button buttonC = findViewById(R.id.buttonC);
            buttonC.setOnClickListener(this);

            Button buttonD = findViewById(R.id.buttonD);
            buttonD.setOnClickListener(this);

            Button buttonUndo = findViewById(R.id.buttonUndo);
            buttonUndo.setOnClickListener(this);

            usedCommandList = new Stack<Command>();
        }

        @Override
        public void onClick(View v) {
            switch(v.getId()) {
                case R.id.buttonA:
                    nowCommand = new LightOnCommand(new Light(this));
                    simpleRemoteControl.setCommand(nowCommand);
                    simpleRemoteControl.buttonWasPressed();
                    usedCommandList.add(nowCommand);
                    break;
                case R.id.buttonB:
                    nowCommand = new LightOffCommand(new Light(this));
                    simpleRemoteControl.setCommand(nowCommand);
                    simpleRemoteControl.buttonWasPressed();
                    usedCommandList.add(nowCommand);
                    break;
                case R.id.buttonC:
                    nowCommand = new TVOnCommand(new TV(this));
                    simpleRemoteControl.setCommand(nowCommand);
                    simpleRemoteControl.buttonWasPressed();
                    usedCommandList.add(nowCommand);
                    break;
                case R.id.buttonD:
                    nowCommand = new TVOffCommand(new TV(this));
                    simpleRemoteControl.setCommand(nowCommand);
                    simpleRemoteControl.buttonWasPressed();
                    usedCommandList.add(nowCommand);
                    break;
                case R.id.buttonUndo:
                    try{
                        nowCommand = usedCommandList.pop();
                        if(nowCommand != null){
                            simpleRemoteControl.setCommand(nowCommand);
                            simpleRemoteControl.buttonWasPressed();
                        }
                    }catch(EmptyStackException e){
                        Log.d("CommandTest", "No more undo command");
                    }
                    break;
            }
        }
    }

    interface Command {
        public void execute();
    }

    class Light {
        Context mContext;

        public Light(Context context){
            mContext = context;
        }

        public void on(){
            Log.d("CommandTest", "Light ON");
        }

        public void off(){
            Log.d("CommandTest", "Light OFF");
        }
    }

    class TV {
        Context mContext;

        public TV(Context context){
            mContext = context;
        }

        public void on(){
            Log.d("CommandTest", "TV ON");
        }

        public void off(){
            Log.d("CommandTest", "TV OFF");
        }
    }

    class LightOnCommand implements Command {
        Light light;

        public LightOnCommand(Light light) {
            this.light = light;
        }

        @Override
        public void execute() {
            light.on();
        }
    }

    class LightOffCommand implements Command {
        Light light;

        public LightOffCommand(Light light) {
            this.light = light;
        }

        @Override
        public void execute() {
            light.off();
        }
    }

    class TVOnCommand implements Command {
        TV tv;

        public TVOnCommand(TV tv) {
            this.tv = tv;
        }

        @Override
        public void execute() {
            tv.on();
        }
    }

    class TVOffCommand implements Command {
        TV tv;

        public TVOffCommand(TV tv) {
            this.tv = tv;
        }

        @Override
        public void execute() {
            tv.off();
        }
    }

    class SimpleRemoteControl {
        Command slot;

        public SimpleRemoteControl() {}
        public void setCommand(Command command) {
            this.slot = command;
        }
        public void buttonWasPressed() {
            slot.execute();
        }
    }

각 버튼에 대한 설명과 동작 영상!

 

감사합니당.!

 

질문과.. 틀린 것이 있다면..

말씀해주세용,,

728x90
반응형