Skip to content

커맨드 패턴 (Command pattern)

VG edited this page Dec 30, 2020 · 14 revisions

커맨드 패턴 (Command pattern)

명령 자체를 객체의 형태로 캡슐화하는 패턴

Unity를 알고있다면?
Unity의 Event Trigger를 생각하면 된다.

다이어그램

그림출처

예제

만능 버튼C#

형광등 불을 키는 버튼을 만들것이다.

간단하다. 해당 다이어그램대로
버튼을 형광등과 곧바로 연결하면 될 것이다.

Invoker 클래스

public class LightOnButton
{
    private Light light;

    //켤 불을 등록한다.
    public void setLight(Light light) 
    {
        this.light= light;
    }
    // 버튼이 눌리면 현재 등록된 light의 LightOn 메서드를 호출한다.
    public void pressed()
    {
        light.LightOn();
    }
}

Receiver 클래스

public class Light
{
    public void LightOn()
    {
        Console.WriteLine("형광등을 킴");
    }
    public void LightOff()
    {
        Console.WriteLine("형광등을 끔");
    }
}

메인 클래스

class Program
{
    static void Main(string[] args)
    {
        Light light = new Light();

        LightOnButton buttonLigtOn = new LightOnButton();

        buttonLigtOn.setLight(light); // 형광등 등록
        buttonLigtOn.pressed(); // 형광등을 킴

    }
}

해당 코드는 불을 켜는데 문제는 없을 것이다.
하지만, 다시 불을 끄고 싶다면? TV, 선풍기 등 새로운 제품을 추가하게 된다면?

  1. 버튼은 누른다라는 한 가지 기능밖에 존재하지 않는다.
  2. 그렇다면, 제품마다 On버튼, Off버튼을 따로 만들어야 한다.
  3. 누른다라는 한 가지의 동일한 기능을 가진 버튼들이 무수히 많아 진다.

∴결국, 제품 제어를 원할 때마다, 매번 다른 버튼들을 조작해야 한다. 매우 비 효율 적이지 않을 수 없다.[1]

눈치 챘는지 모르겠지만, 에초에 위의 코드는 🛑DIP위반이다.
상위수준의 개체인 버튼이 불을 켜기 위해, 하위수준의 개체인 형광등에게 의존하고 있기 때문이다.

[1]이것이 DIP원칙을 지켜야 하는 이유이다.[↻]

그렇다면 어떻게 고쳐야 할까?

그림출처

해당 문제는 커맨드 패턴을 적용시키면, 간단히 해결된다.

Command 인터페이스

public interface Command
{
    public void execute();
}

Receiver 클래스

public class Light
{
    public void LightOn()
    {
        Console.WriteLine("형광등을 킴");
    }
    public void LightOff()
    {
        Console.WriteLine("형광등을 끔");
    }
}
public class TV
{
    public void TVOn()
    {
        Console.WriteLine("TV를 킴");
    }
    public void TVOff()
    {
        Console.WriteLine("TV를 끔");
    }
}

Concreate Command 클래스

// 불을 켜는 LightOnCommand 클래스
public class LightOnCommand : Command
{
    private Light light;
    public LightOnCommand(Light light)
    {
        this.light = light;
    }
    // Command 인터페이스의 execute 메서드
    public void execute()
    {
        light.LightOn();
    }
}
// 불을 끄는 LightOffCommand 클래스
public class LightOffCommand : Command
{
    private Light light;
    public LightOffCommand(Light light)
    {
        this.light = light;
    }
    // Command 인터페이스의 execute 메서드
    public void execute()
    {
        light.LightOff();
    }
}
// TV 켜는 TVOnCommand 클래스
public class TVOnCommand : Command
{
    private TV tv;
    public TVOnCommand(TV tv)
    {
        this.tv = tv;
    }
    // Command 인터페이스의 execute 메서드
    public void execute()
    {
        tv.TVOn();
    }
}
// TV 끄는 TVOffCommand 클래스
public class TVOffCommand : Command
{
    private TV tv;
    public TVOffCommand(TV tv)
    {
        this.tv = tv;
    }
    // Command 인터페이스의 execute 메서드
    public void execute()
    {
        tv.TVOff();
    }
}

Invoker 클래스

public class Button
{
    private Command[] theCommand;

    // 버튼의 명령을 theCommand변수에 등록한다.
    public void setCommand(Command[] newCommand) 
    {
        this.theCommand = newCommand;
    }
    // 버튼이 눌리면 현재 등록된 Command의 execute 메서드를 호출한다.
    public void pressed()
    {
        for (int i = 0; i < theCommand.Length; i++)
            theCommand[i].execute();
    }
}

메인 클래스

class Program
{
    static void Main(string[] args)
    {
        Light light = new Light();
        Command lightOnCommand = new LightOnCommand(light);
        Command lightOffCommand = new LightOffCommand(light);

        TV tv = new TV();
        Command tvOnCommand = new TVOnCommand(tv);
        Command tvOffCommand = new TVOffCommand(tv);

        Button button = new Button();

        Command[] commands = new Command[] { lightOnCommand };
        button.setCommand(commands); // 커맨드목록 등록
        button.pressed(); // 형광등을 킴


        commands = new Command[] { tvOnCommand };
        button.setCommand(commands); // 커맨드목록 등록
        button.pressed(); // TV를 킴


        commands = new Command[] { lightOffCommand, tvOffCommand };
        button.setCommand(commands); // 커맨드목록 등록
        button.pressed(); // TV와 형광등을 동시에 끔

    }
}

이제, 하나의 버튼으로 여러가지 명령을 수행할 수 있게 되었다.
또한, 버튼의 명령들을 배열변수로 등록함으로서, 한번에 여러 명령을 동시에 수행할 수 도 있다.

만약, 버튼 클래스에서 theCommand변수의 getter를 구현하면, 다른 버튼에게 명령들을 넘기는 것 또한 가능할 것이다.

public class Switch
{
    private Command[] onCommand;
    private Command[] offCommand;

    // 스위치의 명령을 onCommand와 offCommand 변수에 등록한다.
    public void setCommand(Command[] onCommand, Command[] offCommand)
    {
        this.onCommand = onCommand;
        this.offCommand = offCommand;
    }
    // 스위치를 켜면 현재 등록된 onCommand들의 execute 메서드를 호출한다.
    public void switchOn()
    {
        for (int i = 0; i < onCommand.Length; i++)
            onCommand[i].execute();
    }
    // 스위치를 끄면 현재 등록된 offCommand들의 execute 메서드를 호출한다.
    public void switchOff()
    {
        for (int i = 0; i < offCommand.Length; i++)
            offCommand[i].execute();
    }
}

게다가, 커맨드를 사용하면 다른 기능제품들도 손쉽게 만들어 낼 수 있다.

Clone this wiki locally