delegate (noun)
A person sent or authorized to represent others, in particular an elected representative sent to a conference.
A popular and widely applied practice for creating flexible and reusable object oriented code is delegation.
Delegation involves two objects handling a request: a receiving object(delegator) delegates operations to its delegate.
An example of this is the Strategy Pattern.
In order for the delegate pattern to be successful, the delegate must be able to have access to the same context as the delegator.
While the delegator context can be passed to the delegate in the form of raw parameters (delegateOperation(delegateRequiredParam1,delegateRequiredParam2,delegateRequiredParam3)
) this approach restricts the amount of responsibility that can be delegated in a nice, scalable way.
To remove such restriction, the delegate should receive a reference to the delegator object and have the possibility of accessing a wider interface than the one of a foreign caller.
Example
class Player extends EventEmitter {
protected xPos: number;
protected yPos: number;
constructor(private movementStrategy: PlayerMovementStrategy) {
super();
}
protected startAnimation(animationId: string) {
// Code to start an animation
}
protected setMovementStrategy(movementStrategy: PlayerMovementStrategy){
this.movementStrategy = movementStrategy;
}
move(direction: Direction) {
this.movementStrategy.move(this, direction);
}
}
class WalkingMovementStrategy implements PlayerMovementStrategy {
move(player: Player, direction: Direction) {
player.startAnimation('someWalkingAnimation');
switch (direction) {
case Direction.UP:
player.setMovementStrategy(flyingStrategy);
break;
case Direction.Right:
player.xPos = player.xPost + 1;
// Rest of cases
}
}
}
class FlyingMovementStrategy implements PlayerMovementStrategy {
move(player: Player, direction: Direction) {
player.startAnimation('flyingAnimation');
switch (direction) {
case Direction.Up:
player.yPos = player.yPos + 10;
break;
// Rest of cases
}
}
}
interface PlayerMovementStrategy {
move(player: Player, direction: Direction): void;
}
enum Direction {
Up,
Down,
Left,
Right,
}
const coolPlayer = new Player(new WalkingMovementStrategy());
coolPlayer.move(Direction.Up);
Property startAnimation is protected an is only accessible within the class Player.
Things to note
Changing the startAnimation
member accessibility to public solves the problem but exposes a method intended for internal usage.
Same goes for the other attributes xPos, yPos.
The delegate must change more than 1 thing of the delegator object, hence making returning a value to apply the changes possible but ugly.
Something like this
const result = movementStrategy.move(player, direction);
console.log(result);
// { nextAnimation: 'walkingAnimation', newYPos : 2 }
And what about extensibility? What happens if a movement strategy wants to trigger multiple animations?
Add the functionality: objectA delegateof objectB
delegateof
allows objectA
to have access to the protected members of objectB
.
class Player{
constructor(private movementStrategy: PlayerMovementStrategy){}
protected startAnimation(animationId: string){
// start the animation
}
move(direction: Direction){
this.movementStrategy.move(this, direction);
}
}
class WalkingMovementStrategy delegateof Player implements PlayerMovementStrategy{
move(player: Player, direction: Direction){
// No member accesibility errors yay
player.startAnimation('yayAnimation');
}
}
While the keyword suffices for the OOP style, I am not sure how could this be implemented for plain objects.
Example
const playerMovementStrategy = {
move(player: Player, direction: Direction){
// How to make this work here...
player.startAnimation('yayAnimation');
}
}
Hoping the proposal is understandable and eager to hear your thoughts on it,
Joel ✌️.