Command Pattern
메소드 호출을 캡슐화(encapsulation) 한다.
- 여러 기능을 실행할 수 있도록 재사용성이 높은 클래스를 설계하는 패턴
- 서비스를 호출할 때 융통성을 높이려고 사용
- 매개 변수를 사용하여 여러가지 다른 요구사항을 처리
문제점)
GUI toolkit
GUI 컴포넌트(버튼, 텍스트필드) 들이 Client 가 설정한 임의의 작업을 수행하도록 하려면 어떻게 해야할까
Universal remote controller
각 버튼이 임의의 가전제품을 제어하게 하려면 어떻게 해야 할까
Unknown consumer electronic devices
객체간 상호작용
- Receiver : 서비스 제공자
- Invoker : Client의 일부가 될 수 있다.
invoker 와 receiver 사이에
Direct Couping이 발생
문제가 있는 간단한 코드
class ButtonListener implements ActionListener {
void actionPerformed(ActionEvent e) {
String button = e.getActionCommand();
switch (button) {
case "버튼1" : 거실등.On(); break;
case "버튼2" : 거실등.Off(); break;
case "버튼3" : 차고문.up(); break;
case "버튼4" : 차고문.down(); break; // 새로운 서비스 추가시 이 부분이 변경
case "버튼5" : 차고문.stop(); break; // -> OCP 에 위배됨
default : ...
} // type checking 필요
}
}
해결)
- DIP
- 변경되는 부분을 추상화
- 메소드 호출 부분을 추상화
- "명령 객체"
차고문.down();
차고문.stop();
(명령 객체) 명령에 대한 호출을 캡슐화
Command 객체 사용으로 invoker & receiver 디커플링
if (requestType == TypeA) commandA.execute();
else if (requestType == TypeB) commandB.execute();
else if (requestType == TypeC) commandC.execute();
.execute()
public interface Command {
public void execute();
}
class LightOffBtn extends JButton implements Command {
public void execute() { 거실등.Off(); }
}
// 버튼을 Command 클래스를 상속받아 구현하면 문제 발생
button인 동시에 command: user interface와 business logic이 분리되지 않음
해결2)
명령 객체를 분리
Command
public interface Command {
public void execute();
}
class LightOnCmd implements Command {
private 거실등 거실등; // Receiver를 구성
public void execute() {
거실등.On(); // 위임
}
}
class LightOffCmd implements Command {
private 거실등 거실등; // Receiver를 구성
public void execute() {
거실등.Off();
}
}
CommandHolder
public interface CommandHolder {
public void registerCommand(Command cmd);
public Command getCommand();
}
// 명령 객체를 가지는 GUI 컴포넌트
class BtnHoldingCmd extends JButton implements CommandHolder {
private Command 명령;
public void registerCommand(Command cmd) {
this.명령 = 명령;
}
//✓장치 변경에 영향 받지 않음. (동적 장치 변경 가능)
//✓business logic과 분리됨
public Command getCommand() {
return cmd;
}
}
Invoker
class ButtonListener implements ActionListener {
void actionPerformed(ActionEvent event) { // event (=버튼)
CommandHolder 버튼 = (CommandHolder)event.getSource();
Command 명령 = 버튼.getCommand();
명령.execute();
}
} // 새로운 기능 추가 될때 , 변경에 대해 저항력 가진다.
Receiver
public class 거실등
{
public void turnOn() {
System.out.println("The light is on");
}
public void turnOff() {
System.out.println("The light is off");
}
}
참가자
Command, Invoker, Receiver
Receiver : 요청과 연관된 작업을 실제로 수행하는 객체
Command : 메소드 호출을 추상화
ConcreteCommand : 메소드 호출을 캡슐화 ( 특정수신자.특정메소드() )
Invoker : 여러 타입의 command를 실행
- DIP 적용 설계 : Invoker는 Command의 interface or abstract 에 의존
예제
간단한 스위치 예시 코드)
Receiver 수신자
// Receiver
public class Light
{
public void turnOn(){
System.out.println("the light is on");
}
public void turnOff(){
System.out.println("the light is off");
}
}
Command
public interface Command
{
void execute();
}
public class OnCmd implements Command
{
private Light light; // 구성
public OnCmd(Light light) { // 주입
this.light = light;
}
@Override
public void execute() {
light.turnOn(); // 위임
}
}
Invoker 클라이언트 요청 실행
public class Switch
{
private List<Command> history = new ArrayList<>(); // (옵션) 어떤 명령이 있었는지
public void storeAndFire(Command cmd){
history.add(cmd);
cmd.execute(); // 위임
}
public void store(Command cmd) {
history.add(cmd);
}
public void fire(Command cmd) {
cmd.execute();
}
}
public class SwitchTest {
public static void main(String[] args) {
Light lamp = new Light(); // 리시버
Command switchOnCmd = new OnCmd(lamp); // 구성
Switch sw = new Switch(); // InVoker 클라이언트 요청
String input = new Scanner(System.in).next();
switch(input)
{
case "ON": sw.storeAndFire(switchOnCmd); // 명령 객체 구성
// OFF
default:
System.out.println("The end");
}
}
}
실행 결과
Universal Remote Controller
가상 주식 거래 상황에 커맨트 패턴 적용해보기
- client는 주식 매수 주문서 (concrete command: BuyStockOrder), 주식 매도 주문서 (concrete command SellStockOrder) 생성
- 주문서를 대행사(invoker: Agent)에게 전송
- 대행사는 받은 주문들을 주식거래시스템(receiver: StockTrade)에 전달
- 단 대행사는 주식거래시스템이 개장할 때까지 자신의 queue에 주문서를 저장
invoker
// invoker
public class Agent
{
private Queue<Order> ordersQueue;
public Agent() {
ordersQueue = new LinkedList<>();
}
// 주문서 저장
public void placeOrder(Order order){
ordersQueue.add(order);
}
public void sell(){
ordersQueue.poll().execute();
}
public int orderCount(){
return ordersQueue.size();
}
}
Stock
public abstract class Stock
{
int price;
int numOfShares; // 주식수
public Stock(int price, int numOfShares) {
this.price = price;
this.numOfShares = numOfShares;
}
//주식을 샀다.
public void bought(int num){
numOfShares -= num;
}
// 주식을 팔았다.
public void sold(int num){
numOfShares += num;
}
public int getPrice() {
return price;
}
public int getNumOfShares() {
return numOfShares;
}
public abstract void description();
}
// 여러 종목들
public class Samsung extends Stock
{
public static final String CODE = "005930";
public Samsung( int price, int numOfShares) {
super(price,numOfShares);
}
@Override
public void description() {
System.out.println("삼성전자 설명");
}
}
//
// 하이닉스, ...
receiver
// 주식 거래 시스템
public class StockTrade
{
Map<String, Stock> stocks = new HashMap<>();
public StockTrade() {
// 종목 셋팅
stocks.put(Samsung.CODE,new Samsung(70000,10000000));
}
public void buy(String code, int num){
stocks.get(code).bought(num);
}
public void sell(String code, int num){
stocks.get(code).sold(num);
}
public void displayShares(String code){
System.out.println("주식수: "+stocks.get(code).getNumOfShares());
}
}
Command
// command
public interface Order
{
public void execute();
}
public class BuyStockOrder implements Order
{
private StockTrade stockTrade;
private String code="";
private int num=0;
public BuyStockOrder(StockTrade stockTrade) {
this.stockTrade = stockTrade;
}
public BuyStockOrder(StockTrade stockTrade, String code, int num) {
this.stockTrade = stockTrade;
this.code = code;
this.num = num;
}
public void setOrder(String code, int num) {
this.code = code;
this.num = num;
}
@Override
public void execute() {
if(!(code.equals("")&&num==0)) stockTrade.buy(code,num);
else System.out.println("코드와 갯수 확인해주세요");
setOrder("",0);
}
}
public class SellStockCommand implements Order
{
// receiver
private StockTrade stockTrade;
private String code="";
private int num=0;
public SellStockCommand(StockTrade stockTrade) {
this.stockTrade = stockTrade;
}
public SellStockCommand(StockTrade stockTrade, String code, int num) {
this.stockTrade = stockTrade;
this.code = code;
this.num = num;
}
public void setOrder(String code, int num) {
this.code = code;
this.num = num;
}
@Override
public void execute() {
if(!(code.equals("")&&num==0)) stockTrade.sell(code,num);
else System.out.println("코드와 갯수 확인해주세요");
setOrder("",0);
}
}
메인
public class Client {
public static void main(String[] args) {
StockTrade stockTrade = new StockTrade();
Agent agent = new Agent();
agent.placeOrder(new BuyStockOrder(stockTrade));
agent.placeOrder(new BuyStockOrder(stockTrade,Samsung.CODE,100));
agent.placeOrder(new SellStockCommand(stockTrade,Samsung.CODE,5));
System.out.println(agent.orderCount());
//
agent.sell();
stockTrade.displayShares(Samsung.CODE);
agent.sell();
stockTrade.displayShares(Samsung.CODE);
agent.sell();
stockTrade.displayShares(Samsung.CODE);
//
}
}
댓글