우는 아이도 보면 울음을 그친다는 마법의.... 리모컨!
이 떠오르는 이번 디자인 패턴!! 은 바로바로바로!!!!
커맨드 패턴 입니다.!
커맨드 패턴이란 ?
요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 매서드 이름, 매개변수 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수 있게 하는 패턴이다.
출처 : 위키백과
쉽게 다시 설명을 하자면!
조명을 컨트롤 할 수 있는 리모컨이 있다고 생각해보자.
아래 그림에서 빨간 동그라미가 쳐져있는 버튼을 누르면 조명이 켜지게 된다.
이때 리모컨에 대한 구현을 어떻게 하면 좋을까 ?
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();
}
}
각 버튼에 대한 설명과 동작 영상!
감사합니당.!
질문과.. 틀린 것이 있다면..
말씀해주세용,,
'SELF STUDY > Design Pattern' 카테고리의 다른 글
[디자인패턴] 퍼사드 패턴 | Facade Pattern | 안드로이드 예제 (0) | 2021.12.27 |
---|---|
[Design Pattern] 어댑터 패턴 | Adapter Pattern (0) | 2021.12.20 |
[디자인패턴] 데코레이터 패턴 | Decorator pattern (0) | 2021.09.01 |
[디자인 패턴] Observer pattern | 옵저버 패턴 (0) | 2021.08.18 |
[디자인 패턴] 싱글톤 패턴 (0) | 2021.07.14 |