Замикання еквівалентні об'єктам, що реалізують метод run (), і навпаки, об'єкти можуть бути імітовані закриттями.
Перевага закриттів полягає в тому, що їх можна легко використовувати в будь-якому місці, де ви очікуєте функцію: також функції вищого порядку, прості зворотні дзвінки (або стратегія). Вам не потрібно визначати інтерфейс / клас для створення спеціальних закриттів.
Перевагою об'єктів є можливість більш складних взаємодій: декілька методів та / або різних інтерфейсів.
Тож використання закриття чи предметів - це переважно питання стилю. Ось приклад речей, які закриття робить легко, але незручно реалізувати з об’єктами:
(let ((seen))
(defun register-name (name)
(pushnew name seen :test #'string=))
(defun all-names ()
(copy-seq seen))
(defun reset-name-registry ()
(setf seen nil)))
В основному, ви інкапсулюєте прихований стан, доступ до якого здійснюється лише через глобальне закриття: вам не потрібно посилатися ні на який об’єкт, використовуйте лише протокол, визначений трьома функціями.
- Я поширюю відповідь, щоб вирішити цей коментар від supercat *.
Я довіряю першому коментарю supercat про те, що в деяких мовах можна точно контролювати термін експлуатації об'єктів, тоді як те ж саме не стосується закриттів. Що стосується мов, що збираються зі сміттям, то термін експлуатації об'єктів, як правило, не обмежений, і таким чином можливо створити закриття, яке можна було б викликати в динамічному контексті, де воно не повинно викликатись (читання із закриття після потоку закритий, наприклад).
Однак запобігти такому зловживанню досить просто, захопивши керуючу змінну, яка буде охороняти виконання закриття. Точніше, ось що я маю на увазі (у Common Lisp):
(defun guarded (function)
(let ((active t))
(values (lambda (&rest args)
(when active
(apply function args)))
(lambda ()
(setf active nil)))))
Тут ми беремо позначення функції function
і повертаємо два закриття, обидва вони захоплюють локальну змінну з назвою active
:
- перший делегується
function
, лише коли active
це правда
- другий встановлюється
action
на nil
ака false
.
Замість цього (when active ...)
, звичайно, можливо мати (assert active)
вираз, який може кинути виняток у випадку, якщо закриття викликається, коли його не повинно бути. Крім того, майте на увазі, що небезпечний код вже може викинути виняток сам по собі при поганому використанні, тому вам рідко потрібна така обгортка.
Ось як би ви його використовували:
(use-package :metabang-bind) ;; for bind
(defun example (obj1 obj2)
(bind (((:values f f-deactivator)(guarded (lambda () (do-stuff obj1))))
((:values g g-deactivator)(guarded (lambda () (do-thing obj2)))))
;; ensure the closure are inactive when we exit
(unwind-protect
;; pass closures to other functions
(progn
(do-work f)
(do-work g))
;; cleanup code: deactivate closures
(funcall f-deactivator)
(funcall g-deactivator))))
Зауважте, що дезактиваційні закриття можуть бути надані і іншим функціям; тут локальні active
змінні не поділяються між f
та g
; також, крім того active
, f
лише посилається obj1
і g
тільки на нього obj2
.
Інший момент, про який згадує supercat, полягає в тому, що закриття може призвести до витоку пам’яті, але, на жаль, це стосується майже всього в місцях, де збираються сміття. Якщо вони доступні, це можна вирішити слабкими покажчиками (саме закриття може зберігатися в пам'яті, але не перешкоджає збору сміття інших ресурсів).