cl-clos-patterns
CLOS設計パターンを適用。クラス設計・メソッド実装時に使用
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for applying Common Lisp Object System (CLOS) design patterns. It assists developers in architecting robust class hierarchies, managing complex inheritance, and implementing sophisticated method combinations. By leveraging standardized patterns like Mixins, Protocols, and Visitors, it ensures that Lisp-based applications are scalable, maintainable, and follow best practices for object-oriented programming in Common Lisp.
Use Cases
- Architecting complex system hierarchies: Using multiple inheritance and C3 linearization to design flexible and reusable class structures.
- Implementing advanced method logic: Utilizing :before, :after, and :around method combinations for seamless logging, validation, and execution wrapping.
- Standardizing object initialization: Customizing initialize-instance and factory functions to ensure data integrity and proper object state upon creation.
- Applying specialized design patterns: Implementing Lisp-specific versions of Singleton, Visitor, and Mixin patterns to solve recurring architectural challenges.
| name | cl-clos-patterns |
|---|---|
| description | CLOS設計パターンを適用。クラス設計・メソッド実装時に使用 |
| allowed-tools | Read, Grep, Glob |
CLOS Design Patterns
クラス定義
基本構造
(defclass user ()
((id
:initarg :id
:reader user-id
:type integer
:documentation "Unique identifier")
(name
:initarg :name
:accessor user-name
:type string
:documentation "Display name")
(email
:initarg :email
:accessor user-email
:type string)
(created-at
:initform (get-universal-time)
:reader user-created-at
:type integer)
(active-p
:initarg :active-p
:initform t
:accessor user-active-p
:type boolean))
(:documentation "Represents a user account."))
スロットオプション
| オプション | 用途 |
|---|---|
:initarg |
コンストラクタ引数 |
:initform |
デフォルト値 |
:reader |
読み取り専用アクセサ |
:writer |
書き込み専用アクセサ |
:accessor |
読み書きアクセサ |
:type |
型宣言(最適化ヒント) |
:allocation |
:instance または :class |
:documentation |
ドキュメント |
アクセサの使い分け
;; :reader - 変更不可のID
(id :initarg :id :reader user-id)
;; :accessor - 変更可能な属性
(name :initarg :name :accessor user-name)
;; :writer のみ - 稀なケース
(password-hash :writer (setf user-password-hash))
継承
単一継承
(defclass person ()
((name :initarg :name :accessor person-name)))
(defclass employee (person)
((employee-id :initarg :employee-id :reader employee-id)
(department :initarg :department :accessor employee-department)))
多重継承
(defclass named-mixin ()
((name :initarg :name :accessor object-name)))
(defclass timestamped-mixin ()
((created-at :initform (get-universal-time) :reader created-at)
(updated-at :initform (get-universal-time) :accessor updated-at)))
(defclass document (named-mixin timestamped-mixin)
((content :initarg :content :accessor document-content)))
クラス優先順位 (CPL)
;; C3線形化でメソッド解決順序を決定
(defclass a () ())
(defclass b (a) ())
(defclass c (a) ())
(defclass d (b c) ()) ; CPL: d -> b -> c -> a -> standard-object -> t
メソッド定義
基本メソッド
(defgeneric process (object)
(:documentation "Process the given object."))
(defmethod process ((obj user))
(format t "Processing user: ~A~%" (user-name obj)))
(defmethod process ((obj document))
(format t "Processing document: ~A~%" (object-name obj)))
メソッド組み合わせ
:before / :after
;; メインメソッドの前後に実行
(defmethod process :before ((obj user))
(log:info "Starting process for user ~A" (user-id obj)))
(defmethod process :after ((obj user))
(log:info "Finished process for user ~A" (user-id obj)))
:around
;; メインメソッドをラップ
(defmethod process :around ((obj user))
(let ((start (get-internal-real-time)))
(prog1 (call-next-method) ; メインメソッドを呼び出し
(log:debug "Process took ~Dms"
(- (get-internal-real-time) start)))))
実行順序
:around (外側)
:before (CPL順)
primary (最も特化)
:after (CPL逆順)
:around (内側に戻る)
特化子 (Specializer)
クラス特化
(defmethod draw ((shape circle))
...)
EQL特化
(defmethod handle-event ((event (eql :click)))
(format t "Click event~%"))
(defmethod handle-event ((event (eql :keypress)))
(format t "Keypress event~%"))
初期化プロトコル
initialize-instance
(defmethod initialize-instance :after ((user user) &key)
;; IDが未指定なら生成
(unless (slot-boundp user 'id)
(setf (slot-value user 'id) (generate-id)))
;; バリデーション
(unless (valid-email-p (user-email user))
(error "Invalid email: ~A" (user-email user))))
make-instance のカスタマイズ
;; ファクトリ関数を提供
(defun make-user (name email &key (active-p t))
"Create a new user with validation."
(make-instance 'user
:name name
:email email
:active-p active-p))
reinitialize-instance
(defmethod reinitialize-instance :after ((user user) &key)
(setf (updated-at user) (get-universal-time)))
;; 使用
(reinitialize-instance user :name "New Name")
設計パターン
パターン1: プロトコル (Interface)
;; 抽象プロトコル
(defgeneric serialize (object stream)
(:documentation "Serialize object to stream."))
(defgeneric deserialize (class stream)
(:documentation "Deserialize object from stream."))
;; 実装
(defmethod serialize ((user user) stream)
(format stream "~A:~A:~A"
(user-id user)
(user-name user)
(user-email user)))
パターン2: Mixin
(defclass validatable-mixin ()
())
(defgeneric validate (object)
(:method-combination progn))
(defmethod validate progn ((obj validatable-mixin))
;; 基本バリデーション
t)
(defclass user (validatable-mixin)
(...))
(defmethod validate progn ((user user))
(assert (valid-email-p (user-email user))))
パターン3: ビジター
(defgeneric visit (visitor object)
(:documentation "Visit object with visitor."))
(defclass print-visitor () ())
(defmethod visit ((v print-visitor) (u user))
(format t "User: ~A~%" (user-name u)))
(defmethod visit ((v print-visitor) (d document))
(format t "Document: ~A~%" (object-name d)))
パターン4: シングルトン
(defclass configuration ()
((instance :allocation :class :initform nil)))
(defun get-configuration ()
(or (slot-value (find-class 'configuration) 'instance)
(setf (slot-value (find-class 'configuration) 'instance)
(make-instance 'configuration))))
ベストプラクティス
1. defgeneric を明示
;; Good - 明示的なジェネリック定義
(defgeneric process (object)
(:documentation "Process the object."))
(defmethod process ((obj user))
...)
;; Bad - 暗黙的なジェネリック
(defmethod process ((obj user))
...)
2. 適切なアクセサ選択
;; 変更不可 -> :reader
;; 変更可能 -> :accessor
;; 内部使用 -> アクセサなし
3. スロットの直接アクセスを避ける
;; Good - アクセサ経由
(user-name user)
;; Bad - 直接アクセス(テストやデバッグ以外)
(slot-value user 'name)