Я хочу написать код с несколькими бэкендами пользовательского интерфейса (например, текстовым и графическим), чтобы их было легко переключать. Мой подход использует 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.
Это правильный подход или есть что-то более прямолинейное? Вопрос заключается в предоставлении разных бэкэндов для одной и той же функциональности, не обязательно пользовательского интерфейса.
Другим вариантом использования может быть ситуация, когда у меня есть много реализаций одного и того же алгоритма (оптимизированных по скорости, потреблению памяти и т. д.), и я хочу переключать их чистым способом, сохраняя интерфейс и типы аргументов.