Replacing Command - MordernProgramming/FunctionalProgramming GitHub Wiki

Replacing Command

To turn a method invocation into an object and execute it in a central location

Commnad 패턴은 여러 요소가 맞물려서 작동한다. ("a few moving parts")

  • command : 인터페이스
  • command구현체들 : command를 구현한 다양한 command들
  • receiver : Command가 뭔가를 실행하는 대상
  • invoker : Command를 실행한다. client의 요청과 receiver의 메소드 호출을 분리한다. 실행을 한곳에서 제어할 수 있는 포인트.
  • client : Command를 만들어서 사용한다.

####Cash Register it only handles whole dollars, and it contains a total amount of cash. Cash can only be added to the register. 그냥 가계부.

###Java

public interface Command {
	public void execute();
}
public class Purchase implements Command {
	private CashRegister cashRegister;  // receiver
	private Integer amount;

	public Purchase(CashRegister cashRegister, Integer amount) {
		this.cashRegister = cashRegister;
		this.amount = amount;
	}

	@Override
	public void execute() {
		cashRegister.addCash(amount);
	}

}
public class CashRegister {     // receiver
	private Integer total;

	public CashRegister(Integer startingTotal) {
		total = startingTotal;
	}
	public void addCash(Integer toAdd) {
		total += toAdd;
	}
	public Integer getTotal() {
		return total;
	}
	public void reset() {
		total = 0;
	}
}
public class PurchaseInvoker {
	private List<Command> executedPurchases = new ArrayList<Command>();
	public void executePurchase(Command purchase) {
		purchase.execute();
		executedPurchases.add(purchase);
	}
	public List<Command> getPurchaseHistory() {
		return executedPurchases;
	}
}

// purchase를 뺀 버전...
public class CommandInvoker {
	private List<Command> executed = new ArrayList<Command>();
	public void execute(Command command) {
		command.execute();
		executed.add(command);
	}
	public List<Command> getHistory() {
		return executed;
	}
}
public class RegisterClient {
	private static CashRegister cashRegister = new CashRegister(0);
	private static PurchaseInvoker purchaseInvoker = new PurchaseInvoker();

	public static void main(String[] args) {
		Command purchase1 = new Purchase(cashRegister, 100);
		Command purchase2 = new Purchase(cashRegister, 50);

		// Invoke commands, check register total.
		purchaseInvoker.executePurchase(purchase1);
		purchaseInvoker.executePurchase(purchase2);
		System.out.println("After purchases: " + cashRegister.getTotal());

		// Replay purchases
		cashRegister.reset();
		System.out.println("Register reset to 0");
		List<Command> purchases = purchaseInvoker.getPurchaseHistory();
		for (Command purchase : purchases) {
			purchase.execute();
		}
		System.out.println("After replay: " + cashRegister.getTotal());
	}
}

###Scala

  • straightforward
  • No need for a Command, Purchase, or separate invoker class
  • higher-order function을 사용.
class CashRegister(var total: Int) {    // receiver
    def addCash(toAdd: Int) {
        total += toAdd
    }
}

코드가 짧다. (생성자, getTotal, reset이 없다.)

def makePurchase(register: CashRegister, amount: Int) = {
    () => {
        println("Purchase in amount: " + amount)
        register.addCash(amount)
    }
}

Command 인터페이스와 구현체인 Purchase대신 함수를 리턴하는 hihger-order 함수로 구현함. 그리고 closure. (인수로 받은 register와 amount를 유지함.)

var purchases: Vector[() => Unit] = Vector()
def executePurchase(purchase: () => Unit) = {
    purchases = purchases :+ purchase
    purchase()
}

invoker 대신 구현. () => Unit 형태의 closure를 받아서 purchases에 하나씩 저장하고, closure를 실행함.

scala> val register = new CashRegister(0)
scala> val purchaseOne = makePurchase(register, 100)
scala> val purchaseTwo = makePurchase(register, 50)
scala> executePurchase(purchaseOne)
Purchase in amount: 100
scala> executePurchase(purchaseTwo)
Purchase in amount: 50
scala> register.total
res2: Int = 150
scala> register.total = 0
scala> for(purchase <- purchases){ purchase.apply() }
Purchase in amount: 100
Purchase in amount: 50
scala> register.total
res4: Int = 150

client를 대신함.

###Clojure

  • Scala랑 비슷함.
  • 다른점은 OO가 아니라서 class를 못만든다는 것. (CashRegister)
  • 대신 atom이란 걸 쓴다.
(defn make-cash-register []
    (let [register (atom 0)]
        (set-validator! register (fn [new-total] (>= new-total 0)))
        register))
        
(defn add-cash [register to-add]
    (swap! register + to-add))

(defn reset [register]
    (swap! register (fn [oldval] 0)))
=> (def register (make-cash-register))
=> (add-cash register 100)
100

register가 atom이고, 0으로 시작함. add-cash할 때마다 새로운(fresh) atom을 리턴해준다. set-validator는 머지?? swap에 느낌표는 머지??

(defn make-purchase [register amount]
    (fn []
        (println (str "Purchase in amount: " amount))
        (add-cash register amount)))

make-purchase도 function을 리턴하는 higher-order function. 그리고 closure. (register와 amount를 유지함) 골뱅이는 머지??

=> (def register (make-cash-register))
=> @register
0
=> (def purchase-1 (make-purchase register 100))
=> (def purchase-2 (make-purchase register 50))
=> (purchase-1)
Purchase in amount: 100
100
=> (purchase-2)
Purchase in amount: 50
150
=> (reset register)
0
=> (doseq [purchase @purchases] (purchase))
Purchase in amount: 100
Purchase in amount: 50
nil
=> @register
150

객체없이 closure로 했음. 다형성을 어떻게 구현할까 걱정되겠지만 ad hoc hierarchy, multimethod, protocol로 지원이 더 잘된다. 걱정마삼.

###Discussion

별말없음. Command 패턴은 command 인터페이스 뿐만 아니라 invoker/receiver/client가 포함된 개념이다. 일부 이를 단순 "인터페이스"로 혼동하고 GOF가 정의한 "패턴"으로 사용하지 않는 사람도 흔하다. 단순 인터페이스는 3-1장으로도 대체가 된다. 3-3장에서는 모두 FP로 바꾸었다.

END.

⚠️ **GitHub.com Fallback** ⚠️