Как уменьшить дублирование кода, используя комбинацию методов, но сохраняя возможность раннего возврата

У меня есть набор классов, которые представляют сообщение, которое нужно обработать. Но есть только ограниченное количество открытых мест для хендлеров. Поэтому любая «отправка» обработчика, обрабатывающего объект сообщения, должна сначала проверить, есть ли свободное место.

Если есть -> отправка.

Если нет -> не отправлять и вернуть соответствующее сообщение

Поскольку эта часть кода будет одинаковой в любом методе отправки, я решил, что лучше всего использовать средство комбинирования методов, чтобы обеспечить это, но я не могу понять, как это сделать.

В моей текущей кодовой базе я пытался использовать метод :before, но, по-видимому, вы не можете использовать return в таком контексте:

(defclass message () ((msg :initarg :msg :reader msg)))

(defclass message-ext (message) 
    ((univ-time :initarg :univ-time :reader univ-time)))

(defparameter *open-handler* nil)

(defgeneric handle (message)
  (:documentation "handle the given message appropriately"))

(defmethod handle :before ((message message))
  (when (> (length *open-handler*) 1)
    (return :full)))

(defmethod handle ((message message))
  (push (FORMAT nil "dispatched handler") *open-handler*))

(defmethod handle ((message-ext message-ext))
  (push (FORMAT nil "dispatched ext handler") *open-handler*))

(handle (make-instance 'message :msg "allemeineentchen"))

(handle (make-instance 'message-ext 
                       :msg "rowrowrowyourboat" 
                       :univ-time (get-universal-time)))

(handle (make-instance 'message-ext 
                       :msg "gentlydownthestreet" 
                       :univ-time (get-universal-time)))

Execution of a form compiled with errors.
Form:
  (RETURN-FROM NIL FULL)
Compile-time error:
  return for unknown block: NIL
   [Condition of type SB-INT:COMPILED-PROGRAM-ERROR]

Restarts:
 0: [RETRY] Retry SLIME interactive evaluation request.
 1: [*ABORT] Return to SLIME's top level.
 2: [TERMINATE-THREAD] Terminate this thread (#<THREAD "worker" RUNNING {100594F743}>)

Backtrace:
  0: ((SB-PCL::FAST-METHOD HANDLE :BEFORE (MESSAGE)) #<unavailable argument> #<unavailable argument> #<unavailable argument>)
  1: ((SB-PCL::EMF HANDLE) #<unavailable argument> #<unavailable argument> #<MESSAGE-EXT {1005961733}>)
  2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (HANDLE (MAKE-INSTANCE 'MESSAGE-EXT :MSG "gentlydownthestreet" :UNIV-TIME (GET-UNIVERSAL-TIME))) #<NULL-LEXENV>)
  3: (EVAL (HANDLE (MAKE-INSTANCE 'MESSAGE-EXT :MSG "gentlydownthestreet" :UNIV-TIME (GET-UNIVERSAL-TIME))))
  4: ((LAMBDA () :IN SWANK:INTERACTIVE-EVAL))

Является ли этот подход разумным, и если да, то как я могу сделать это в рабочем режиме? (Я уже пробовал return-from с тем же результатом)


person Sim    schedule 22.08.2013    source источник


Ответы (2)


Я думаю, вам следует использовать квалификатор метода :around вместо этого:

(defmethod handle :around ((message message))
  (if (cddr *open-handler*)
      :full
      (call-next-method)))

Однако более «сюсюкающий» подход — использовать систему условий CL. , например, что-то вроде этого:

(define-condition too-many-messages (...) (...) ...)
(defun add-message (message)
  (when (cddr *open-handler*)
    (signal 'too-many-messages))
  (push message *open-handler*))
(defmethod handle ((message message))
  (add-message (FORMAT nil "dispatched handler")))

Вам придется обработать условие (используя, например, handler-bind ) в дополнение к проверке возвращаемых значений вашей функции handle.

PS. Вызов length в списке, чтобы проверить, достаточно ли он длинный, - не очень хорошая идея, хотя в вашем случае, когда список гарантированно будет коротким, это может быть скорее проблемой стиля.

ППС. Не очень хорошая идея использовать слово handle в качестве имени вашей функции, потому что в CL есть функции, которые его содержат (например, handler-case). Это усложнит поиск в вашем коде, а также запутает людей, читающих ваш код.

person sds    schedule 22.08.2013

Вы не можете вызвать RETURN для возврата из такой функции.

Вам нужно будет использовать RETURN-FROM с именем функции. Но здесь он будет возвращаться из метода, а не из общей функции.

У @sds есть ответ. Другим может быть сигнал об определенном пользователем условии и обработка его где-то еще. В старом коде использовались catch и throw.

Более сложной задачей будет комбинация методов, определяемых пользователем.

person Rainer Joswig    schedule 22.08.2013