Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/guillep/avatar
Proxy library for the Pharo language
https://github.com/guillep/avatar
delegation-proxies forwarding-proxy message-dispatcher metaprogramming pharo proxy-captures-messages proxy-library
Last synced: about 2 months ago
JSON representation
Proxy library for the Pharo language
- Host: GitHub
- URL: https://github.com/guillep/avatar
- Owner: guillep
- License: mit
- Created: 2017-02-15T09:05:12.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2022-09-05T12:25:23.000Z (over 2 years ago)
- Last Synced: 2024-10-10T13:25:58.137Z (2 months ago)
- Topics: delegation-proxies, forwarding-proxy, message-dispatcher, metaprogramming, pharo, proxy-captures-messages, proxy-library
- Language: Smalltalk
- Size: 48.8 KB
- Stars: 1
- Watchers: 4
- Forks: 5
- Open Issues: 10
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Avatar
Avatar is a proxy library for the Pharo language. The main focus of this library is to be of simple usage and providing clear examples for final users.
## Proxies and Handlers
A proxy is an object that takes the place of a `target` object. This allows a proxy to capture all interactions with the target and do some particular intercession. For example, a proxy could log every message received, or stop in a breakpoint when a particular combination of messages happens. The intercession done by a proxy is specified by a `handler`.
In summary, a proxy captures messages sent to a target and domes some special handling in a handler.
Avatar provides pre-made forwarding and delegation proxy implementations that work for classes and objects. In addition, it provides a nice way to describe handlers that are independent of the proxy implementation.
### First Example
Let's imagine we want to create a proxy logging all the interactions with a particular object. This means that we require to create a proxy with a logging handler. An avatar handle is any object that understands the message: `handleMessage: aMessage toTarget: anObject`. To implement our logging handler we need to define a new handler class:
```smalltalk
Object subclass: #MyLoggingHandler
instanceVariableNames: ''
classVariableNames: ''
package: 'Avatar-Tests'
```And put in it a `handleMessage: aMessage toTarget: anObject` method:
```smalltalk
MyLoggingHandler >> handleMessage: aMessage toTarget: anObject
aMessage logCr.
aMessage sendTo: anObject.
```In such a manner, our handler will log the message into the `Transcript`, and then resend the message to the object.
We can create a proxy using this handler as follows:
```smalltalk
proxy := AvForwardingProxy target: MyObject new handler: MyLoggingHandler new.
proxy yourself.
proxy = true.
```And see that the output of our Transcript will say:
```
yourself
= true
```We used in this example a forwarding proxy. More about them follows in next sections.
### 1 . Forwarding Proxies
A forwarding proxy is a non-reentrant proxy. This means that messages dispatched from the forwarding proxy do not pass any more through the forwarding proxy. Let's consider for example the following method in class `Rectangle`:
```smalltalk
Rectangle>>area
^ self height * self width
```If we create a forwarding proxy of a rectangle and we call area on it, the forwarding proxy will catch **only** the area message. Height and width messages are dispatched directly to the rectangle and the proxy is not used anymore from that point on.
```smalltalk
proxy := AvForwardingProxy target: Rectangle new handler: MyLoggingHandler new.
proxy area.
```This will output in the `Transcript`:
```
area
```### 2 . Delegation Proxies
A delegation proxy is a reentrant proxy. This means that messages dispatched from the delegation proxy will pass through the delegation proxy too. Let's consider for example the following method in class `Rectangle`:
```smalltalk
Rectangle>>area
^ self height * self width
```If we create a delegation proxy of a rectangle and we call area on it, the delegation proxy will catch the area message and all other messages sent to self such as `height` and `width`.
```smalltalk
proxy := AvDelegationProxy target: Rectangle new handler: MyLoggingHandler new.
proxy area.
```This will output in the `Transcript`:
```
area
height
instVarAt: 1
width
instVarAt: 2
```Notice that the delegation proxy captured messages named `instVarAt:`. Delegation proxies capture all interactions with the object, even read and write accesses to instance variables in the object. In case we would like to ignore these messages, we could do it by changing the behaviour of our handler. We discuss further about it in the handlers section.
### Handlers
Handlers are meta-objects that define what to do when a proxy receives a message. As we saw before a handler can be any object, as soon as it implements de defined `handleMessage:toTarget:` interface. Using different patterns, a handler can then capture messages in different ways:
- *instead* of sending the message to the target
By default, a handler will execute its defined action *instead* of sending the message. For example, a handler could log the received messages but never resend the message to the original object:
```smalltalk
MyLoggingHandler >> handleMessage: aMessage toTarget: anObject
aMessage logCr.
```- *before* or *after* the message is sent to the target
To not replace the send to the original object (because for example we would like to keep its behaviour), we need to explicitly resend the message to the target using the `sendTo:` message of the received message. Using this, we could do something *before* the message continues, something *after* it continues.
```smalltalk
MyLoggingHandler >> handleMessage: aMessage toTarget: anObject
| result |
('message: ', aMessage asString) logCr.
result := aMessage sendTo: anObject.
('result: ', result asString) logCr.
^ result
```## Implementation Details
Avatar uses `doesNotUnderstand:` for reasons of simplicity and performance. This implementation has a main drawback: `doesNotUnderstand:` cannot be captured by the library.
Alternatively, we could have implemented it with `cannotInterpret:`. This would have required a bit more complexity in the implementation, but the main point is that cannotInterpret: is not optimized in the VM JIT, thus it provokes a considerable loss of performance.
## Inspirations and Acknowledgements
Avatar is based in previous research from M.M.Peck, C. Teruel, their first implementation of Ghost and the continuation of this work by D. Kudriashov.