microsoft / microsoft/TypeScript

Proposal: delegateof

オープン
#19,151 コメント 6 件 リアクション 8 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

In Discussion Suggestion
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
1日 19時間
マージ済み PR(30日)
117

説明

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

}

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

リポジトリのファイル、テスト、コンパイラのエントリーポイントは指定されていません。まず、提案されている delegateof 構文、その protected メンバーとプライバシーに関する要件、および plain-object に関する未解決の問題を確認してください。言語設計と受け入れ基準について合意されるまで、作業を開始できる状態ではありません。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
typescript
領域
compilers
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
説明が足りない
初心者へのやさしさ
20/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。