我想编写具有多个用户界面后端(例如文本和图形)的代码,因此它们很容易切换。我的方法是使用 CLOS:
(defgeneric draw-user-interface (argument ui)
(:documentation "Present the user interface")
(:method (argument (ui (eql :tui)))
(format t "Textual user interface! (~A)" argument))
(:method (argument (ui (eql :gui)))
(format t "Graphical user interface! (~A)" argument)))
这种方法乍一看似乎没问题,但它有一些缺点。为了简化调用,我定义了参数ui-type将在每个函数调用中使用,以简化切换后端,但是在使用高阶函数时会出现问题:
(defparameter *ui-type* :tui
"Preferred user interface type")
(draw-user-interface 3 *ui-type*)
;;; I can't use the following due to the `ui' argument:
;(mapcar #'draw-user-interface '(1 2 3))
;;; Instead I have to write this
(mapcar #'(lambda (arg)
(draw-user-interface arg *ui-type*))
'(1 2 3))
;; or this
(mapcar #'draw-user-interface
'(1 2 3)
(make-list 3 :initial-element *ui-type*))
;; The another approach would be defining a function
(defun draw-user-interface* (argument)
(draw-user-interface argument *ui-type*))
;; and calling mapcar
(mapcar #'draw-user-interface* '(1 2 3))
如果采用这种方法,我们可以将通用函数命名为 %draw-user-interface,而将包装函数命名为 draw-user-interface。
这是有效的方法还是有更直接的方法?问题是为相同的功能提供不同的后端,不一定是用户界面。
另一个用例可能是这样一种情况,当我有许多相同算法的实现(针对速度、内存消耗等进行了优化)并且我想以一种干净的方式切换它们,保留接口和参数类型。