Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2008-11-17

CLとデザインパターン - Command

| 08:02 | CLとデザインパターン - Command - わだばLisperになる を含むブックマーク はてなブックマーク - CLとデザインパターン - Command - わだばLisperになる

今回はCommandパターンです。

このパターンもまたNorvig氏とGreg Sullivan氏のGOF Design Patterns in a Dynamic OO Languageによればファースト・クラスの関数がある言語ならばクラスをつかわなくてもできるでしょうとのこと。

確かに関数とデータを何らかのデータに一緒に閉じ込めて、後で取り出したりして実行というのは簡単にできます。

また、テンプレート的なところは、内部で動作する関数を引数に取れる関数を定義してやれば良かったりするんでしょう。

とはいえ、それでは面白くもないので、クラスでやりくりで書いてみました。

今回は、22.Commandパターン | TECHSCORE(テックスコア)の食塩の飽和濃度を調べる手順をCLOSで真似てみました。

水に塩を加えて飽和濃度を調べる実験と、塩に水を加えて調べる実験との2種類を作成。

それぞれの方式がクラスで表現されていて、類似の実験方法を後で簡単に追加できるのが、ポイントのようです。

作ってみた感想ですが、引数に関数を渡せる言語では、継承のメリットが全面に押し出されたような状況でもなければ、関数を渡した方が見通しが良さそうな気もしました。

;; 実験セット
(defclass beaker () 
  ((water :initarg :water :initform 0 :accessor beaker.water)
   (salt :initarg :salt :initform 0 :accessor beaker.salt)))

(defun make-beaker (water salt)
  (make-instance 'beaker :water water :salt salt))

;; 食塩を加える
(defgeneric add-salt (beaker salt)
  (:method ((beaker beaker) (salt number))
    (incf (beaker.salt beaker) salt)))

;; 水を加える
(defgeneric add-water (beaker water)
  (:method ((beaker beaker) (water number))
    (incf (beaker.water beaker) water)))

;; 濃度
(defgeneric concentration (beaker)
  (:method ((beaker beaker))
    (with-accessors ((salt beaker.salt)
                     (water beaker.water)) beaker
      (float (* 100 (/ salt (+ water salt)))))))

;; 記録
(defgeneric note (beaker)
  (:method ((beaker beaker))
    (with-accessors ((water beaker.water)
                     (salt beaker.salt)) beaker
      (format 'T 
              "水:~Ag~%食塩:~Ag~%濃度:~A%"
              water
              salt
              (float (* 100 (/ salt (+ water salt))))))))

;; コマンド
(defclass command () 
  ((beaker :initform (make-beaker 0 0)
           :initarg :beaker
           :accessor beaker)))

(defgeneric execute (command)
  (:method ((command command))))

;; 食塩を加える方向での実験
(defclass add-salt-command (command) ())

(defun make-add-salt-command ()
  (make-instance 'add-salt-command))

(defmethod execute ((command add-salt-command))
  (with-accessors ((b beaker)) command
    (loop :while (> 26.4 (concentration b)) ;20℃での飽和濃度 26.4%
          :do (add-salt b 1))
    (format 'T "~&--- 食塩を1gずつ加える実験 ---~%")
    (note b)))

;; 水を加える方向での実験
(defclass add-water-command (command) ())

(defun make-add-water-command ()
  (make-instance 'add-water-command))

(defmethod execute ((command add-water-command))
  (with-accessors ((b beaker)) command
    (loop :while (< 24.6 (concentration b)) 
          :do (add-water b 10))
    (format 'T "~&--- 水を10gずつ加える実験 ---~%")
    (note b)))

;; 実験をする生徒
(defclass student () ())

(defmethod experiment ((s student))
  (let ((add-salt (make-add-salt-command))
        (add-water (make-add-water-command)))
    ;; ビーカーをセットする
    (setf (beaker add-salt) (make-beaker 100 0))
    (setf (beaker add-water) (make-beaker 0 10))
    ;; 飽和食塩水を作る実験
    (execute add-salt)
    (execute add-water)))

;; 実験
(experiment (make-instance 'student))

;>>>
; --- 食塩を1gずつ加える実験 ---
; 水:100g
; 食塩:36g
; 濃度:26.470589%
; --- 水を10gずつ加える実験 ---
; 水:40g
; 食塩:10g
; 濃度:20.0%