Replacing Command - MordernProgramming/FunctionalProgramming GitHub Wiki
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.