Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
OTPattern/VirtualRefactoring
Intent
Adjust the structure of a base class to the needs of a bound role.
Motivation
If a base class does not have exactly that structure that would be most suitable for a role to be bound to this base, a developer might want to change that structure to facilitate developing the role against a more suitable interface. However, other clients may depend on the current structure of the base class, hence actual Refactoring may not be feasible.
In this situation the developer may want to provide more than one view of the given base class: the original structure untouched and a view as if the Refactoring had been performed.
Structure
This pattern obviously requires a role bound to a base. The pattern adds an indirection for implementing the desired view. It does so by a set of callout bindings with parameter mappings.
Implementation
For illustration consider the Refactoring "Encapsulate Collection". In plain Java this Refactoring is used change the public interface of a class that contains a collection-typed field in these ways: make the collection-typed field private (if it isn't already) and do not provide direct getter/setter methods for the field add specific access methods that expose only selected methods of the collection.
E.g.,
package savings; public class PiggyBank { private Collection<Coin> coins; public Collection<Coin> getCoins() { return coins; } // DANGEROUS: client could remove coins from the returned collection }
would be refactored to
package savings; public class PiggyBank { private Collection<Coin> coins; public boolean add(Coin coin) { return coins.add(coin); } public int getCoinCount() { return coins.size(); } }
Now a piggy bank cannot be robbed.
If one cannot perform the Refactoring in its classical way, one may still devise a role with these bindings:
import base savings.PiggyBank; // can only use PiggyBank in bindings public team class ChildsWorld { protected class SafePiggyBank playedBy PiggyBank { /* provide method "add" by a callout binding: */ public boolean add(Coin coin) -> get Collection<Coin> coins with { result <- base.coins.add(coin) } /* provide method "getCoinCount" by a callout binding: */ public int getCoinCount() -> get Collection<Coin> coins with { result <- base.coins.size() } } }
As a result, all the code in methods of ChildsWorld and its roles will only see PiggyBank by the interface of SafePiggyBank, which is the same interface as the refactored regular class above. Even methods within SafePiggyBank cannot bypass this restricted interface. Only the expressions in the parameter mappings have free access to features of the base class, i.e., these parameter mappings live within the no man's land of the gate way between role and base.
- Details
- The key feature used in this pattern are parameter mappings (OTJLD §3.2).
- If the "refactoring" affects a field, it is common to use callout to field (OTJLD §3.5).
Related Patterns
If a callout that's part of a Virtual Refactoring returns an object of another base/role pair (i.e, it applies lifting on its result), we have an instance of the VirtualAssociation pattern, as well.
This pattern is particularly useful in conjunction with the Connector pattern, where abstract methods of an inherited role are mapped to some features of a base class. Here the abstract methods exactly prescribe the expected structure which can be easily provide by applying a Virtual Refactoring.