question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Proposal: delegateof

See original GitHub issue

delegateof Proposal

delegate (noun)

A person sent or authorized to represent others, in particular an elected representative sent to a conference.


Motivation

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.

Problem

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

Given

class Player {
    protected xPos: number;
    protected yPos: number;

    constructor(private movementStrategy: PlayerMovementStrategy) {
    }

    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.xPos + 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,
}

Then

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?

Solution

Add the functionality: objectA delegateof objectB

What does it do?

delegateof allows objectA to have access to the protected members of objectB.

Example

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');
    }
}

Closing thoughts

While the keyword suffices for the OOP style, I am not sure how could this be implemented for plain objects or if it should even be attempted to.

Example

const playerMovementStrategy = {
    move(player: Player, direction: Direction){
        // How to make this work here... or do we even need this?
        player.startAnimation('yayAnimation');
    }
}

Eager to hear your thoughts on it,

Joel.

Edit on member privacy

@andy-ms mentioned privacy as a concern. If we would like to retain the privacy of protected and private members we could introduce a new accessor delegated which functions as protected but allows classes which declare themselves as delegates of the class to be able to access the member.

class Player{
    constructor(private movementStrategy: PlayerMovementStrategy){}

    delegated 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 ;-)
        player.startAnimation('yayAnimation');
    }

}

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:7
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

4reactions
dleppikcommented, Dec 19, 2017

I really think TypeScript could use delegate/proxy support. JavaScript has mixins, and TypeScript doesn’t have a good equivalent. I’m less concerned about supporting a “Friend” pattern.

Another way to do this which feels more TypeScripty to me would be to specify a proxied behavior, similar to (Kotlin’s delegation.)

A less formal syntax would be to specify a proxied object in the constructor:

interface A {
    giveMeAString: () => string
}

class B implements A {
     constructor(delegate a: A) {}
}

This would be syntactic sugar for:

class B implements A {
    constructor( private a: A) {}
    
    giveMeAString() { return this.a.giveMeAString(); }
}

The only methods exposed would be ones which are specified by the class definition or interface.

Another example:

class MyJQueryExtension {
    constructor(delegate $: JQuery) {}
}

Note that this isn’t just like a JavaScript jQuery mixin, in that we’re proxying the object rather than adding methods directly to it.

2reactions
andy-mscommented, Dec 19, 2017

@dleppik What you’re describing would seem to require a type-directed emit – we would need to know the properties available on a in order to generate delegates to them. So it probably is out of scope of our design goals. It is possible to declare a bunch of new methods on a class without writing code for them, and then use dynamic magic to inject them:

interface A {
    m(): void;
}

// Gives 'C' all methods of 'A' without having to write their bodies
interface C extends A {}
class C {
    constructor(a: A) {
        for (const key in a) {
            const value = (a as any)[key];
            if (typeof value === "function") {
                (this as any)[key] = value.bind(a);
            }
        }
    }
}

const a: A & { name: string } = { m() { console.log("I'm the delegate, " + this.name); }, name: "Della" };
new C(a).m();
Read more comments on GitHub >

github_iconTop Results From Across the Web

Delegate Definition & Meaning - Merriam-Webster
To delegate is, literally or figuratively, to send another in one's place, an idea that is reflected in the word's origin: it is...
Read more >
Delegate of Communications, Publications, and Outreach
Job Description. Your interest in policy, content creation, and keeping records are going to set you apart in this role.
Read more >
proposal made by the Delegate of Australia, Mr. W.J. Weeden
Reports Committee: proposal made by the Delegate of Australia, Mr. W.J. Weeden. Conference : UNESCO. General Conference, 10th, 1958.
Read more >
GC(06)/218 - Credentials of delegates to the sixth regular ...
The delegate of the United States of America proposed that the Committee should recommend to the General Conference the adoption of the draft....
Read more >
Second Session. Proposal of the delegate of Belgium - GATT Digital ...
Proposal of the delegate of Belgium; Corporate Author: Committee of Contracting Parties on Special Exchange Agreements; Language: English; Date: April 11, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found