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-10-02

CLとデザインパターン - Template Method

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

どういう訳かLISP系言語とデザインパターンについて以前から興味があります。

しかし、どうもデザインパターンの解説が馴れないと難解というか、コードの例もUML図もさっぱり分からないことが多いので、さっぱり理解できません。

といっても、このままでは、何も分からないままなので、とりあえず理解できそうなところから手を付けて行くことにしました。

基本的に手引とするのは、Norvig氏のDesign Patterns in Dynamic LanguagesとGreg Sullivan氏のGOF Design Patterns in a Dynamic OO Languageの文献と、矢沢久雄氏のITproでの連載-no titleです。

今回は、一番簡単っぽい、Template Methodを考えてみることにしました。

Template Method

Template Methodとは、具体的な処理をサブクラスにまかせるパターンとのこと。雛型のクラスを作成し、そこで抽象的な処理の流れを記述して、具体的な処理の記述は、サブクラスで行なうというパターンのようです。

それで、上述のNorvig氏の資料によれば、LISPの場合、関数がファースト・クラスなので取り立ててパターンとして意識されることもない、というような説明があります。

とりあえず、どういうことなのか良く分からないので

の内容を参考に適当にコードを書いてみました。

高階関数版
(defun template-function (&optional (op1 #'values) (op2 #'print))
  (lambda (string)
    (funcall op2 (funcall op1 string))))

(defun op1 (string)
  (string-upcase string))

(defun op1-1 (string)
  (string-capitalize string))

(defun op2 (string)
  (print string))

(let ((abstract (template-function))
      (concrete (template-function #'op1 #'op2))
      (concrete1 (template-function #'op1-1 #'op2)))
  (funcall abstract "foo bar baz")
  (funcall concrete "foo bar baz")
  (funcall concrete1 "foo bar baz"))

;>>> "foo bar baz" 
;>>> "FOO BAR BAZ" 
;>>> "Foo Bar Baz" 

関数をとって、手順を合成した関数を返すというtemplate-functionを作成してみています。

この場合、template-functionは全体の流れだけを保持しているような感じです。

次にクラスを使って書いてみました。

クラス版
(progn
  (defclass abstract () () )
  
  (defmethod op1 ((class abstract) (string string))
    string)
  (defmethod op2 ((class abstract) (string string))
    string)
  (defmethod template-method ((class abstract) (string string))
    (op2 class (op1 class string))))
  
(progn
  (defclass concrete (abstract) () )

  (defmethod op1 ((class concrete) (string string))
    (string-upcase string))
  (defmethod op2 ((class concrete) (string string))
    (format t "~A~%" string)))

(progn
  (defclass concrete2 (abstract) () )

  (defmethod op1 ((class concrete2) (string string))
    (string-capitalize string))
  (defmethod op2 ((class concrete2) (string string))
    (princ string)
    (terpri)))

(template-method (make-instance 'concrete) "foo bar baz")
;>>> FOO BAR BAZ

(template-method (make-instance 'concrete2) "foo bar baz")
;>>> Foo Bar Baz

(template-method (make-instance 'abstract) "foo bar baz")
;=> "foo bar baz"

何となく抽象的な雛型を作成しておいて、その穴を埋めるという感じは理解できました。

Template-Methodの場合、情報の隠蔽性も重要なところみたいなのですが、CLOSの場合、隠蔽することに力点を置いてないのと、総称関数なので隠すのはちょっと難しいですね(;´Д`)

隠すとしたら、パッケージの機能でメインの関数だけをexportする等の方法になるんでしょうか。