Шаг 111.
Объектно-ориентированная система FLAVOR. Обмен сообщениями

    На этом шаге мы рассмотрим реализацию обмена сообщениями.

    Объекты индивидуальны, и у каждого из них есть свое текущее состояние и свой набор соответствующих типу методов. Вызов методов обобщен и его единообразность обеспечивается специальным механизмом, который называется обменом сообщениями. Таким образом, метод объекта можно активизировать, послав соответствующее этому методу сообщение (Message).

    Посылка сообщения объекту осуществляется вызовом универсальной функции SEND, в которой объекту передается имя вызываемого метода и используемые им аргументы. Принимающий сообщение объект содержит механизм, который опознает сообщение, выбирает соответствующий ему метод, активизирует его и передает ему содержащиеся в сообщении параметры. Метод вычисляется так же, как обыкновенная функция, и в качестве ответа на сообщение возвращается его значение.

    Итак, сообщение посылается формой SEND:

   (SEND OBJ METH . VARS)

    OBJ - это объект, которому посылается сообщение METH с аргументами VARS. Получающий сообщение объект активизирует соответствующий ему метод и, вообще говоря, возвращает сообщение тому объекту, который послал сообщение.

    Например:

   ; Пусть вращаются планеты!
   (SEND МЕРКУРИЙ :ВРАЩАЙСЯ)
   (SEND ВЕНЕРА   :ВРАЩАЙСЯ)
   (SEND ЗЕМЛЯ    :ВРАЩАЙСЯ)
   (SEND МАРС     :ВРАЩАЙСЯ)
   ; Присваивание спутников Земле и Марсу
   (SEND ЗЕМЛЯ :SET-СПИСОК-СПУТНИКОВ (LIST ЛУНА))
   (SEND МАРС  :SET-СПИСОК-СПУТНИКОВ (LIST ФОБОС ДЕЙМОС))

    Если у объекта нет метода, соответствующего принятому сообщению, то сообщение теряется. Обычно причиной этого является ошибка или по крайней мере исключительная ситуация, к обработке которой нужно быть готовым.

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

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

    Объекты, методы и обмен сообщениями позволяют использовать новый принцип построения программы и вычислений в ней, более общий и гибкий, чем традиционная идея подпрограммы. Ход событий не управляется извне, а осуществляется в зависимости от поведения объектов (их реакции на сообщения).

    Объекты являются более независимыми единицами, чем традиционные подпрограммы. Они могут содержать сведения о собственном строении и функционировании, участвовать в управлении вычислением и взаимодействовать с другими объектами, посылая и принимая сообщения.


    Пример 1 [1, с.118-121]. Программа рассчитана на использование системы объектно-ориентированного программирования Flavor, содержащейся в файле FLAVORS.LSP (автор: Peter Ohler), поставляемом вместе с системой muLISP87.
   (DEFFLAVOR
      SHIP                           ; Имя флэйвера
      (NAME X Y X-SPEED Y-SPEED)     ; Переменные экземпляра
      ()                             ; Надклассы отсутствуют
      :SETTABLE-INSTANCE-VARIABLES   ; Режимы
      :GETTABLE-INSTANCE-VARIABLES
   )
   ; --------
   (DEFMETHOD
      (SHIP :SPEED)
      ()
      (+ (* X-SPEED X-SPEED)
         (* Y-SPEED Y-SPEED))
   )
   % ------------
   (DEFUN MAIN ()
      (SETQ SHIP1 (MAKE-INSTANCE 'SHIP))
      ; Объекту при создании дадим имя
      (SETQ SHIP78 (MAKE-INSTANCE 'SHIP :NAME 'Titanik))
      ; Объекту дается новое имя
      (PRINT (SEND SHIP78 :SET-NAME 'BORE))
      ; У объекта спрашивается имя
      (PRINT (SEND SHIP78 :NAME))
      ; Объекту присваивается X-SPEED
      (PRINT (SEND SHIP78 :SET-X-SPEED 3.0))
      ; Объекту присваивается Y-SPEED
      (PRINT (SEND SHIP78 :SET-Y-SPEED 4.0))
      ; У объекта спрашивается SPEED
      (PRINT (SEND SHIP78 :SPEED))
   )
   ; ----
   (MAIN)
Текст этой функции можно взять здесь.


    Пример 2. Простая оконная система, написанная в объектно-ориентированном стиле. Программа рассчитана на использование системы объектно-ориентированного программирования Flavor, содержащейся в файле FLAVORS.LSP (автор: Peter Ohler), поставляемом вместе с системой muLISP87.
   (SETQ *ALL-WINDOWS*)
   (DEFFLAVOR WINDOW
   ; Базовый флэйвер для окна
      (
         TOP              ; Вершина окна в рядах
         LEFT             ; Левый край окна в колонках
         HEIGHT           ; Высота в колонках
         WIDTH            ; Ширина в рядах
         (CURRENT-ROW 0)  ; Позиция курсора (ряд)
         (CURRENT-COL 0)  ; Позиция курсора (колонка)
         (EXPOSEDP  NIL)  ; Включено ли (видимо ли?) окно?
         (BORDER 1)       ; Ширина бордюра вокруг окна
         (BORDER-COLOR 7) ; Цвет бордюра
         (FOREGROUND 15)  ; Цвет переднего плана окна
         (BACKGROUND 0)   ; Цвет фона окна
      )
      ()                  ; Надклассы отсутствуют
      :SETTABLE-INSTANCE-VARIABLES
      :GETTABLE-INSTANCE-VARIABLES
   )
   ; ---------------------------------
   (DEFMETHOD (WINDOW :AFTER :INIT) ()
   ; После создания окна, метод добавляет его имя к
   ; списку окон
      (PUSH SELF *ALL-WINDOWS*)
   )
   ; ----------------------------
   (DEFMETHOD (WINDOW :EXPOSE) ()
   ; Метод позволяет высветить окно: он формирует окно с за-
   ; данными границами, устанавливает курсор в нужную  пози-
   ; цию, если окно уже высвечено; в противном случае очища-
   ; ет окно
      ( (SEND SELF :EXPOSEDP)
        (MAKE-WINDOW (+ TOP BORDER)
                     (+ LEFT BORDER)
                     (- HEIGHT (* 2 BORDER))
                     (- WIDTH  (* 2 BORDER))
        )
        (SET-CURSOR CURRENT-ROW CURRENT-COL)
      )
     (SEND SELF :REFRESH)
   )
   ; -----------------------------
   (DEFMETHOD (WINDOW :REFRESH) ()
   ; Метод "очищает" окно требуемым цветом и устанавливает
   ; выведенные на экран переменные в TRUE
      (BACKGROUND-COLOR BORDER-COLOR)
      (FOREGROUND-COLOR 0)
      (MAKE-WINDOW TOP LEFT HEIGHT WIDTH)
      (CLEAR-SCREEN)
      (BACKGROUND-COLOR BACKGROUND)
      (FOREGROUND-COLOR FOREGROUND)
      (MAKE-WINDOW (+ TOP BORDER)
                   (+ LEFT BORDER)
                   (- HEIGHT (* 2 BORDER))
                   (- WIDTH (* 2 BORDER))
      )
      (CLEAR-SCREEN)
      (SETQ CURRENT-ROW 0 CURRENT-COL 0)
      (SETQ EXPOSEDP 'T)
   )
   ; -------------------------------------------
   (DEFMETHOD (WINDOW :AFTER :REFRESH) (WINDOWS)
   ; После того, как окно очищено, метод проверяет все ос-
   ; тальные окна и если они перекрываются новым окном, то
   ; высвечивает их вновь
      (SETQ WINDOWS (REMOVE SELF *ALL-WINDOWS*))
      (LOOP
         ( (NULL WINDOWS) )
         ( ( (NO-OVERLAP TOP LEFT HEIGHT WIDTH (CAR WINDOWS))
               (POP WINDOWS)
           )
           (SEND (CAR WINDOWS) :SET-EXPOSEDP NIL)
           (POP WINDOWS)
         )
      )
   )
   ; -------------------------------------------------
   (DEFMETHOD (WINDOW :BEFORE :PRINT) (STR ROW COLUMN)
   ; Прежде, чем Вы что-либо выводите в окно, оно должно
   ; появиться
      (SEND SELF :EXPOSE)
   )
   ; -------------------------------------------------
   (DEFMETHOD (WINDOW :BEFORE :PRINC) (STR ROW COLUMN)
   ; Прежде, чем Вы что-либо выводите в окно, оно должно
   ; появиться
      (SEND SELF :EXPOSE)
   )
   ; -----------------------------------------
   (DEFMETHOD (WINDOW :PRINT) (STR ROW COLUMN)
   ; Печатает STR в заданных ROW и COLUMN окна
      (IF  (AND (INTEGERP ROW) (INTEGERP COLUMN))
         (SET-CURSOR ROW COLUMN))
      (PRINT STR)
      (SETQ CURRENT-ROW (ROW) CURRENT-COL (COLUMN))
   )
   ; -----------------------------------------
   (DEFMETHOD (WINDOW :PRINC) (STR ROW COLUMN)
   ; Печатает STR в заданных ROW и COLUMN окна
      (IF  (AND (INTEGERP ROW) (INTEGERP COLUMN))
         (SET-CURSOR ROW COLUMN))
      (PRINC STR)
      (SETQ CURRENT-ROW (ROW) CURRENT-COL (COLUMN))
   )
   ; -------------------------
   (DEFWHOPPER (WINDOW :PRINT)
                        (STR ROW COLUMN W1 W2 W3 W4 R C)
   ; Мы хотим вернуться в текущее окно после работы в
   ; другом окне, поэтому мы находим размеры текущего
   ; окна, а затем переустанавливаем их после выполнения
   ; метода :PRINT
      (SETQ R (MAKE-WINDOW) W1 (POP R) W2 (POP R)
            W3 (POP R) W4 (POP R))
      (SETQ R (ROW) C (COLUMN))
      (IF (NO-OVERLAP W1 W2 W3 W4 SELF)
         (CONTINUE-WHOPPER STR ROW COLUMN))
      (MAKE-WINDOW W1 W2 W3 W4)
      (SET-CURSOR R C)
   )
   ; -------------------------
   (DEFWHOPPER (WINDOW :PRINC)
                         (STR ROW COLUMN W1 W2 W3 W4 R C)
   ; Мы хотим вернуться в текущее окно после работы в
   ; другом окне, поэтому мы находим размеры текущего
   ; окна, а затем переустанавливаем их после выполнения
   ; метода :PRINC
      (SETQ R (MAKE-WINDOW) W1 (POP R) W2 (POP R)
            W3 (POP R) W4 (POP R))
      (SETQ R (ROW) C (COLUMN))
      (IF  (NO-OVERLAP W1 W2 W3 W4 SELF)
         (CONTINUE-WHOPPER STR ROW COLUMN))
      (MAKE-WINDOW W1 W2 W3 W4)
      (SET-CURSOR R C)
   )
   ; ---------------------------------------------------
   (DEFUN NO-OVERLAP (TOP1 LEFT1 HEIGHT1 WIDTH1 WINDOW2)
   ; Перекрывает ли окно прямоугольник со сторонами,
   ; образуемыми TOP1 LEFT1 HEIGHT1 WIDTH1
      ( (LAMBDA (TOP2 LEFT2 HEIGHT2 WIDTH2
                 BOTTOM1 BOTTOM2 RIGHT1 RIGHT2)
           (SETQ BOTTOM1 (+ TOP1 HEIGHT1)
                 BOTTOM2 (+ TOP2 HEIGHT2)
                 RIGHT1  (+ LEFT1 WIDTH1)
                 RIGHT2  (+ LEFT2 WIDTH2))
           ( (OR (<= TOP1 TOP2 BOTTOM1)
                 (<= TOP1 BOTTOM2 BOTTOM1)
                 (<= TOP2 BOTTOM1 BOTTOM2))
             ( (OR (<= LEFT1 LEFT2 RIGHT1)
                   (<= LEFT1 RIGHT2 RIGHT1)
                   (<= LEFT2 RIGHT1 RIGHT2))
               NIL))
           'T
        )
        (SEND WINDOW2 :TOP)
        (SEND WINDOW2 :LEFT)
        (SEND WINDOW2 :HEIGHT)
        (SEND WINDOW2 :WIDTH)
      )
   )
   ;----------------------------------------
   ; Изготовление экземпляра флэйвера WINDOW
   (SETQ SMALL (MAKE-INSTANCE 'WINDOW
                              :TOP 2
                              :LEFT 10
                              :HEIGHT 8
                              :WIDTH 60))
   ; ---------------------------------------
   ; Изготовление экземпляра флэйвера WINDOW
   (SETQ TINY (MAKE-INSTANCE 'WINDOW
                              :TOP 3
                              :LEFT 30
                              :HEIGHT 8
                              :WIDTH 20
                              :BORDER 2))
   ; ---------------------------------------
   ; Изготовление экземпляра флэйвера WINDOW
   (SETQ MAIN (MAKE-INSTANCE 'WINDOW
                             :TOP 12
                             :LEFT 0
                             :HEIGHT 12
                             :WIDTH 80))
   ; ------------------------------------------
   (SEND MAIN :EXPOSE)     ; Включить окно MAIN
   (WRITE-LINE
     "Послано сообщение (SEND SMALL :PRINC 'HELLO 2 2)")
   (SEND SMALL :PRINC 'HELLO 2 2)
   (WRITE-LINE
     "Послано сообщение (SEND TINY :PRINC 'HELLO 2 2) ")
   (SEND TINY :PRINC 'HELLO 2 2)
   (SEND MAIN  :REFRESH)     ; Очистили окно MAIN
   (SEND SMALL :REFRESH)     ; Очистили окно SMALL
   (SEND TINY  :REFRESH)     ; Очистили окно TINY
Текст этой библиотеки можно взять здесь.


    Замечание. Посылка сообщения соответствует вызову функционала FUNCALL. Если представить, что определение функции МЕТОД сохраняется в списке свойств объекта ОБЪЕКТ под именем МЕТОД, то его можно было бы вызвать функцией:
   (FUNCALL (GET ОБЪЕКТ МЕТОД) АРГУМЕНТЫ)

    Так становится явной связь между объектно-ориентированным мышлением и списком свойств. Список свойств символа - это в некотором смысле тоже объект. Его методами являются, например, функции GET, PUT, REMPROP.


   


(1) Хювенен Э., Сеппянен Й. Мир Лиспа. В 2-х т. Т.2: Методы и системы программирования. - М.: Мир, 1990. - 319 с.

    На следующем шаге мы поговорим о неследовании.




Предыдущий шаг Содержание Следующий шаг