想象一下基类pgj-model
,其上定义了许多方法,但没有插槽。现在考虑:
(defclass cat (pgj-model) ())
(let ((cat (make-instance 'cat)))
(ensure-backend cat)
(insert cat (obj "name" "Joey" "coat" "tabby")))
如果我们想要自定义新类cat
,我们可以专门化这样的方法:
(defmethod insert ((model cat) (object hash-table))
;; Do something interesting
)
这一切都非常好。但pgj-model
和cat
都没有任何插槽,它们的状态较少。这是设计的,因为我只对它们感兴趣,因为lisp类型可以专注于方法。所以在你想要调用这样的方法的任何地方创建类cat
的实例似乎很烦人/令人困惑。
一个想法是:
(defparameter *cat* (make-instance 'cat)) ; There can be only one...
...
(insert *cat* (obj "name" "Joey" "coat" "tabby"))
另一个是在我的所有通用函数上专门化一个额外的方法,如:
(defmethod insert ((model symbol) object)
(insert (make-instance model) object))
(insert 'cat (obj "name" "Joey" "coat" "tabby"))
看起来没问题,但1)可能会混淆用户,2)使用样板文件膨胀通用函数,3)为每个方法调用增加一些开销。
其他建议?
答案 0 :(得分:3)
您可以使用eql
特化器来分配符号标识而不是类:
(defgeneric insert (thing container))
(defmethod insert ((thing (eql 'cat)) (container hash-table))
...)
答案 1 :(得分:1)
一种可能的方法,但我不建议,因为这不是一个好习惯,就是创建自己的单例元类,这样你就可以第一次专注allocate-instance
到call-next-method
并缓存新的实例每隔一段时间返回一次。
(defclass singleton-class (standard-class)
((instance :initform nil :accessor singleton-class-instance)))
(defmethod allocate-instance ((class singleton-class) &rest initargs)
(declare (ignore initargs))
(with-slots (instance) class
(or instance
(setf instance (call-next-method)))))
(defclass pgj-model ()
()
(:metaclass singleton-class))
(defclass cat (pgj-model)
()
(:metaclass singleton-class))
请注意,您需要为每个类声明元类,它不会被继承。
您也可以以类似的方式专门化make-instance
,因此它不会在第一次之后遵循通常的初始化过程。
我没有处理同步,因为你的对象是无状态的,但是如果你需要可靠地保留一个身份,你可能需要解决它。
最后,由于颠覆了allocate-instance
的目的以及make-instance
的目的,这绝对不是一种好的做法。根据规范,Lisp实现可以编译以下函数:
(defun test-singleton-class (class)
(eq (make-instance class)
(make-instance class)))
通过优化它,好像它是这样定义的:
(defun test-singleton-class (class)
;; possibly inlined make-instance calls
nil)
这是该代码所不期望的。
所以,我的实际建议是继续使用全局变量,或者更确切地说是全局读者函数,例如:一个不可设置的(cat)
。