{"id":16086929,"url":"https://github.com/guillep/avatar","last_synced_at":"2025-03-18T06:30:43.202Z","repository":{"id":46915524,"uuid":"82040883","full_name":"guillep/avatar","owner":"guillep","description":"Proxy library for the Pharo language","archived":false,"fork":false,"pushed_at":"2024-12-19T15:24:36.000Z","size":52,"stargazers_count":2,"open_issues_count":10,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-28T07:49:29.710Z","etag":null,"topics":["delegation-proxies","forwarding-proxy","message-dispatcher","metaprogramming","pharo","proxy-captures-messages","proxy-library"],"latest_commit_sha":null,"homepage":null,"language":"Smalltalk","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/guillep.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-02-15T09:05:12.000Z","updated_at":"2024-12-20T18:02:43.000Z","dependencies_parsed_at":"2023-01-17T20:16:30.986Z","dependency_job_id":null,"html_url":"https://github.com/guillep/avatar","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guillep%2Favatar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guillep%2Favatar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guillep%2Favatar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guillep%2Favatar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/guillep","download_url":"https://codeload.github.com/guillep/avatar/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243906411,"owners_count":20367020,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["delegation-proxies","forwarding-proxy","message-dispatcher","metaprogramming","pharo","proxy-captures-messages","proxy-library"],"created_at":"2024-10-09T13:26:13.553Z","updated_at":"2025-03-18T06:30:42.837Z","avatar_url":"https://github.com/guillep.png","language":"Smalltalk","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Avatar\n\nAvatar 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.\n\n## Proxies and Handlers\n\nA 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`.\n\nIn summary, a proxy captures messages sent to a target and domes some special handling in a handler.\n\nAvatar 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.\n\n### First Example\n\nLet'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:\n\n```smalltalk\nObject subclass: #MyLoggingHandler\n\tinstanceVariableNames: ''\n\tclassVariableNames: ''\n\tpackage: 'Avatar-Tests'\n```\n\nAnd put in it a `handleMessage: aMessage toTarget: anObject` method:\n\n```smalltalk\nMyLoggingHandler \u003e\u003e handleMessage: aMessage toTarget: anObject\n    aMessage logCr.\n    aMessage sendTo: anObject.\n```\n\nIn such a manner, our handler will log the message into the `Transcript`, and then resend the message to the object.\n\nWe can create a proxy using this handler as follows:\n\n```smalltalk\nproxy := AvForwardingProxy target: MyObject new handler: MyLoggingHandler new.\nproxy yourself.\nproxy = true.\n```\n\nAnd see that the output of our Transcript will say:\n\n```\nyourself\n= true\n```\n\nWe used in this example a forwarding proxy. More about them follows in next sections.\n\n### 1 . Forwarding Proxies\n\nA 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`:\n\n```smalltalk\nRectangle\u003e\u003earea\n  ^  self height * self width\n```\n\nIf 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.\n\n```smalltalk\nproxy := AvForwardingProxy target: Rectangle new handler: MyLoggingHandler new.\nproxy area.\n```\n\nThis will output in the `Transcript`:\n\n```\narea\n```\n\n### 2 . Delegation Proxies\n\nA 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`:\n\n```smalltalk\nRectangle\u003e\u003earea\n  ^  self height * self width\n```\n\nIf 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`.\n\n```smalltalk\nproxy := AvDelegationProxy target: Rectangle new handler: MyLoggingHandler new.\nproxy area.\n```\n\nThis will output in the `Transcript`:\n\n```\narea\nheight\ninstVarAt: 1\nwidth\ninstVarAt: 2\n```\n\nNotice 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.\n\n### Handlers\n\nHandlers 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:\n\n- *instead* of sending the message to the target\n\nBy 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:\n\n```smalltalk\nMyLoggingHandler \u003e\u003e handleMessage: aMessage toTarget: anObject\n    aMessage logCr.\n```\n\n- *before* or *after* the message is sent to the target\n\nTo 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.\n\n```smalltalk\nMyLoggingHandler \u003e\u003e handleMessage: aMessage toTarget: anObject\n    | result |\n    ('message: ', aMessage asString) logCr.\n    result := aMessage sendTo: anObject.\n    ('result: ', result asString) logCr.\n    ^ result\n```\n\n\n## Implementation Details\n\nAvatar uses `doesNotUnderstand:` for reasons of simplicity and performance. This implementation has a main drawback: `doesNotUnderstand:` cannot be captured by the library.\n\nAlternatively, 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.\n\n## Inspirations and Acknowledgements\n\nAvatar 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.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguillep%2Favatar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fguillep%2Favatar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguillep%2Favatar/lists"}