Lesson 10.8 Callbacks and Interacting Objects

advertisement
Callbacks and Interacting Objects
CS 5010 Program Design Paradigms
"Bootcamp"
Lesson 10.8
© Mitchell Wand, 2012-2014
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
1
The agreement between publisher and
subscriber
• The publisher and subscriber must agree on a
protocol for exchanging messages.
• The protocol consists of:
– A publisher-side method for an object to
subscribe to the messages
– A subscriber-side method that the publisher can
call to deliver the messages
– An agreement on what messages mean and how
they are represented. Information and its
Representation as Data
(again!!)
2
Doing pub-sub without relying on a
common method name
• You might have several different classes of subscribers,
who want to do different things with a published
message.
• Maybe you don't know the name of the subscriber's
receiver method
• Solution: instead of registering an object, register a
function to be called.
– f : X -> Void
published
where X is the kind of value being
• To publish a value, call each of the registered functions
– It's a callback!
• These functions are called delegates or closures.
3
No more update-wall-pos method
(define SBall<%>
(interface (SWidget<%>)
;;
;;
wall's
;;
; Int -> Void
; EFFECT: updates the ball's cached value of the
position
update-wall-pos
))
4
The Wall keeps a list of callback
functions
(define Wall%
(class* object% (SWall<%>)
....
;; the list of registered balls
;; ListOf(Ball<%>)
(field [balls empty])
;; the list of registered
;; callbacks
;; ListOf(Int -> Void)
(field [callbacks empty])
;; (Int -> X) -> Int
;; EFFECT: registers the given
;;
callback
;; RETURNS: the current position
;;
of the wall
(define/public (register c)
(begin
(set! callbacks
(cons c callbacks))
pos))
(define/public (after-drag mx my)
(if selected?
(begin
(set! pos (- mx saved-mx))
(for-each
(lambda (callback)
(send b update-wall-pos pos)
(callback pos))
callbacks))
this))
The wall keeps a list of callback
functions instead of a list of
Balls. When the wall moves, it
calls each registered function
instead of sending a message
to each registered ball.
5
Publishing through a delegate
ball1
wall1
(register f1)
callbacks =
(list f1)
after-drag
f1
wall-pos = 250
(lambda (n) (set!
ball1
n))
(publish 250)
(f1 250)
w = wall1
wall-pos =
250
6
Whose wall-pos?
• When we write
(lambda (n) (set! wall-pos n))
we are referring to the wall-pos field in this
object.
• The next slide shows a similar diagram
illustrating what happens when there are
two balls in the world.
• Each ball has its own delegate, which refers
to its own wall-pos field.
7
f1
250
(lambda (n) (set!
n))
wall1
ball1 ball2
(register f1)
ball1
(register f2)
w = wall1
wall-pos = 250
subscribers =
(list f2 f1)
after-drag
f2
250
(lambda (n) (set!
wall-pos = 250
n))
(publish 250)
ball2
w = wall1
wall-pos = 250
(f2 250) 250
(f1 250) 250
Many balls, many delegates
8
Extending pub-sub
• Now that each ball knows about the wall, the
ball could send the wall other kinds of
messages.
9
Example: 10-8-communicating objects
• In this version, we'll allow the balls to interact
with the wall directly.
• When a ball is selected, the key event "a"
attracts the wall. It makes the wall move 50%
closer to the ball.
• Similarly "r" repels the wall and moves the
wall 50% farther away.
• Note this relies on the ball handling the
keystrokes.
10
Protocol for this communication
• The ball will have an update-wall-pos method
(as in 10-6-push-model-fixed).
• The wall will have a move-to method.
• The ball will call the move-to method with the
x-position the wall should move to.
• The ball will use its cached version of wall-pos
to calculate the desired new position for the
wall.
11
move-to
(define SWall<%>
(interface (SWidget<%>)
; SBall<%> -> Int
; GIVEN: An SBall<%>
; EFFECT: registers the ball
; to receive position updates
; from this wall.
; RETURNS: the x-position of the
; wall
register
; Int -> Void
; EFFECT: moves the wall to the given
; position. Notifies all the
; registered balls about the change.
move-to
(define Wall%
(class* object% (SWall<%>)
; the x position of the wall
(init-field [pos INITIAL-WALL-POSITION])
...
; move-to : Integer -> Void
; EFFECT: moves the wall to the specified
; position, and reports the new position
; to all registered balls
(define/public (move-to n)
(set! pos n)
(for-each
(lambda (b)
(send b update-wall-pos pos))
balls))
))
In the interface
In the class
definition.
The for-each is
repeated code, and
should probably be
moved to a help
function
12
... and in Ball%
;; KeyEvent -> Void
(define/public (after-key-event kev)
(if selected?
(cond
[(key=? kev "a") (attract-wall)]
[(key=? kev "r") (repel-wall)])
this))
(define (attract-wall)
(send w move-to (- wall-pos (/ (- wall-pos x) 2))))
(define (repel-wall)
(send w move-to (+ wall-pos (/ (- wall-pos x) 2))))
13
Many other protocols could
accomplish the same thing
• Ball could send the wall the distance to move
(either positive or negative), and the wall
could move that distance.
• Or the wall could have two methods, attract
and repel, and the ball could send (/ (- wallpos x) 2) to one or the other of the methods.
14
Yet another protocol (part 1)
Introduce a data type of messages, say
something like:
A MoveMessage is one of
-- (make-move-left NonNegInt)
-- (make-move-right NonNegInt)
Interp: the NonNegInt is the distance to
move
15
Yet another protocol (part 2)
• Then the receiver method in the wall will
decode the message and move to the right
position.
• This protocol generalizes: you could send the
wall messages in an arbitrary complicated
way.
• For example:
16
Wall choreography
;; A WallDance is a ListOfMoveMessage
;; WallDance -> Void
(define/public (interpret-dance msg)
(cond
[(empty? msg) this]
[else (begin (interpret-move (first msg))
(interpret-dance (rest msg)))]))
Now the ball can give the wall a whole sequence of instructions in a single message.
WallDance is a programming language!
17
Extending pub-sub
• What if we wanted to deal with multiple
messages?
18
Design #1: Separate subscription lists
• Each kind of message would have its own
subscription list and its own method name
• Good choice if different groups of methods
want to see different sets of messages.
19
Design #2: Single subscription list
• Better if most classes want to see most of the
same messages.
• All subscribers now see all the messages
• The object can simply ignore the messages it’s
not interested in.
20
Variations on Design #2
• Could have different receiver methods for
different messages:
– This is what we did in Widget<%>, with after-tick,
after-key-event, etc.
– add-stateful-object was the equivalent of register.
• Or could have a single receiver method, but
complex messages
– sometimes called a "message bus"
– this is how IP works: each device on the bus just
listens for the messages directed to it.
– this generalizes to large message sets
21
Summary: Reasons to use publishsubscribe
• Metaphor:
– "you" are an information-supplier
– You have many people that depend on your
information
• Your information changes rarely, so most of
your dependents' questions are redundant
• You don't know who needs your information
22
Module Summary
• Objects may need to know each other's identity:
– either to pull information from that object
– or to push information to that object
• Publish-subscribe enables you to send information to
objects you don't know about
– objects register with you ("subscribe")
– you send them messages ("publish") when your
information changes
– must agree on protocol for transmission
• eg: (method-name <data>)
• eg: call a registered closure with some data
– it's up to receiver to decide what to do with the data.
23
Next Steps
• Study the relevant files in the Examples folder:
– 10-6-push-model-fixed.rkt
– 10-7-callbacks.rkt
– 10-8-interacting-objects.rkt
• If you have questions about this lesson, ask
them on the Discussion Board
• Do Problem Set #10.
24
Download