2017년 1월 22일 일요일

[clojure][design pattern] 상태패턴 , 비지터 패턴


clojure design pattern


출처::http://clojure.or.kr/docs/clojure-and-gof-design-patterns.html#command

3. 상태패턴

유료/무료 사용자 차이, 잔액 계산, 유료 전환

JAVA 
1.두개의상태를 enum으로 만든다.
public enum UserState {
  SUBSCRIPTION(Integer.MAX_VALUE), NO_SUBSCRIPTION(10);

  private int newsLimit; 
 
  UserState(int newsLimit) { this.newsLimit = newsLimit; }
  public int getNewsLimit() { return newsLimit; }
}
2. 유저를 만든다.
public class User {
  private int money = 0;
  private UserState state = UserState.NO_SUBSCRIPTION;
  private final static int SUBSCRIPTION_COST = 30;

  public List newsFeed() { // enum안에 들어있는 리미트 값으로 보여준다.
    return DB.getNews(state.getNewsLimit());
  }

  public void pay(int money) { //돈을 넣어서 잔액을 지불하고 임계점을 넘으면 유료회원이 된다.
    this.money += money;
    if (state == UserState.NO_SUBSCRIPTION
        && this.money >= SUBSCRIPTION_COST) {
      // buy subscription
      state = UserState.SUBSCRIPTION;
      this.money -= SUBSCRIPTION_COST;
    }
  }
}
CLOJURE multimethod
 
(defmulti news-feed :user-state)

(defmethod news-feed :subscription [user]
  (db/news-feed))

(defmethod news-feed :no-subscription [user]
  (take 10 (db/news-feed))) 
** defmulti는 무엇을 플래그로 멀티메소드를 만들 것인가. (:user-state으로 분기한다. 

(def user (atom {:name "Jackie Brown"
                 :balance 0
                 :user-state :no-subscription}))

(def ^:const SUBSCRIPTION_COST 30)

(defn pay [user amount]
  (swap! user update-in [:balance] + amount)
  (when (and (>= (:balance @user) SUBSCRIPTION_COST)
             (= :no-subscription (:user-state @user)))
    (swap! user assoc :user-state :subscription)
    (swap! user update-in [:balance] - SUBSCRIPTION_COST)))

(news-feed @user) ;; top 10
(pay user 10)
(news-feed @user) ;; top 10
(pay user 25)
(news-feed @user) ;; all news

 4. 방문자 패턴 (Visitor pattern);
public abstract class Item{ abstract void accept(Visitor v); }

class Message extends Item { void accept(Visitor v) { v.visit(this);} }
class Activity extends Item  { void accept(Visitor v) { v.visit(this);} }
// 비지터
public interface visitor {
  void visit(Activity a);
  void visit(Message m);
}

class PDFVisitor implements Visitor {
  public void visit(Activity a) { System.out.println("PDFExporter.export(a) - Activity");
  public void visit(Message m) { System.out.println("PDFExporter.export(a) - Message");
}
class ExcelVisitor implements Visitor {
  public void visit(Activity a) { System.out.println("ExcelExporter.export(a) - Activity");
  public void visit(Message m) { System.out.println("ExcelExporter.export(a) - Message");
}
main {
  Item i = new Message();
  Visitor v = new PDFVisitor();
  i.accept(v);
}

더블디스패치패턴이 생겨난 이유는(추측컨데) 인자 타입체크를 하지 않기 때문이다. 예를들어
(비지터 패턴을 사용하지 않는 경우 가정 출처링크 참고)
Item i = new Message();
i.accept()를 실행하면 그것이 Activity인지 Message인지 알 필요가 없다. 왜냐하면 이놈은 타입체크가 이루어지기 때문이다. 하지만 아래 놈은 다르다.
Format f = new PDF(); //여기서 Format은 PDF,EXCEL,GIF를 모두 다루는 놈이라 가정하자.
i.accept(f)
될거라 생각하지 말자.
Format f는 인자로 들어와서 타입체크가 되지 않았다. (인자는 타입체크를 하지 않는다는 것이 중요하다.) 그래서 더블디스패치로 이중 타입체크를 하는 것이다. 하는 방법은 아주 단순하다.
i.apply(f); 
// 만약 이런 메소드를 실행하려 하는데 i는 타입체크가 되었고 f는 인자이기 때문에 타입체크가 안되있다면 이 메소드를 바로 실행하는 것이 아니다.
f.apply(i); // 이렇게 바꿔서 f도 타입체크를 하도록 하는 것이다. 그러면 두번 타입체크가 되는 것이다.
어쨋든 Visitor패턴으로 잘 만들었다.

clojure

(defmulti export
  (fn [item format] [(:type item) format]))
{:type :message :content "Say what again!"}
{:type :message, :content "Say what again!"}
{:type :activity :content "Quoting Ezekiel 25:17"}
{:type :activity, :content "Quoting Ezekiel 25:17"}

(defmethod export [:activity :pdf] [item format]
 (println "exporter/activity->pdf" item))

(defmethod export [:activity :xml] [item format]
  (println "exporter/activity->xml" item))
(defmethod export [:message :pdf] [item format]
  (println "exporter/message->pdf" item))
(defmethod export [:message :xml] [item format]
  (println "exporter/message->xml" item))

(defmethod export :default [item format]
  (throw (IllegalArgumentException. "not supported")))
===============================================
(export {:type :message } :xml)
exporter/message->xml {:type :message}
nil
user=> (export {:type :message } :pdf)
exporter/message->pdf {:type :message}
===============================================
(하지만 :pdf:xml 사이에는 아무런 상하 관계(hierarchy)가 존재하지 않네요. 단순히 키워드일 뿐이잖아요?)
hierarchy만들기
(derive ::pdf ::format)
(derive ::xml ::format)
(isa? ::xml ::format)
(isa? ::pdf ::format)

(defmethod export [:message ::pdf] [item format] (println ":message ::pdf" item))
(defmethod export [:message ::format] [item format] (println ":message ::FORMAT!!!!" item))

(export {:type :message :content "SAYHIHIHIHI"} ::pdf)
:message ::pdf {:type :message, :content SAYHIHIHIHI}
nil
user=> (export {:type :message :content "???????????"} ::xml)
:message ::FORMAT!!!! {:type :message, :content ???????????}
nil

::xml을 구현하지도 않았는데 만들어졌다. FORMAT으로 거슬러 올라가서 일이 실행된것!!

** 더블디스페치를 지원하면 비지터패턴은 의미가 없다.




댓글 없음:

댓글 쓰기