Notes from Lecture 4 – Design patterns II
The two design patterns we discuss in lecture 3 belong to the so-called behavioral group. Such patterns implement communication protocols between a collection of objects, e.g., the visitor objects and the shape objects in the visitor pattern.
Besides behavioral patters other groups of patterns focus on how to create new objects or collections of new objects that meet certain constraints (creational patterns); how to manage objects in a multi-threading world (concurrency patterns); and how to organize objects in structures that realize relations between objects (structural patterns).
1 The proxy pattern
The proxy pattern is a very powerful structural pattern that solves the problem of modifying the behavior of an object without changing the implementation of the class of the object nor the way other code interacts with the object.
(define player<%> (interface () register place play))
(define player% (class* object% (player<%>) (super-new) (init-field name) (define my-workers '()) (define/public (register) name) (define/public (place color) (set! my-workers (list (string-append color 1) (string-append color 2))) '((0 0) (0 4))) (define/public (play board) (list (first my-workers) '("N","N")))))
The proxy pattern offers an alternative that is scalable, composable and requires no changes to the player% class and minimum changes to the code that uses the player% object. The trick is quite simple: we define another class that implements the player<%> interface, has a field that stores an object of a class that implements player<%>, and methods that after some processing invoke the corresponding methods of the stored object and process the result of these invocations before returning themselves.
(define player-contract-proxy% (class* object% (player<%>) (super-new) (init-field the-real-player) (define/public (register) (let ([name (send the-real-player register)]) (if (string? name) name "something is wrong with the real player"))) (define/public (place color) (if (string? color) (send the-real-player place color) "something is wrong with the referee")) (define/public (play board) (send the-real-player board))))
> (define christos (new player% [name 'christos])) > (define christos-with-contract (new player-contract-proxy% [the-real-player christos])) > (send christos-with-contract register) "something is wrong with the real player"
> (send christos-with-contract place 'blue) "something is wrong with the referee"
(define player-logging-proxy% (class* object% (player<%>) (super-new) (init-field the-real-player) (define/public (register) (displayln "register called") (send the-real-player register)) (define/public (place color) (displayln "place called") (send the-real-player player color)) (define/public (play board) (displayln "play called") (send the-real-player board))))
> (define christos-with-contract+logging (new player-logging-proxy% [the-real-player christos-with-contract])) > (send christos-with-contract+logging register) register called
"something is wrong with the real player"
> (define christos-with-contract+logging (new player-logging-proxy% [the-real-player christos])) > (send christos-with-contract+logging register) register called
'christos
2 The power of proxies: remote objects
The diagram in figure 2 is a so-called sequence diagram where all communication between objects happens with method invocations. The proxy pattern though is so powerful that it can be useful in situations that this fundamental assumption does not hold.
For example, consider the case of a remote player for Santorini. The remote player is by nature a network client to the Santorini server/admin. Thus the admin needs to communicate with it with network messages over TCP/IP or some other protocol rather than method calls. This seems to imply that to support remote players we have to change completely the architecture of the game to recognize if a player is local or not and pick the right mode of communication (methods VS network messages).