TypeScript: class composition

后端 未结 2 1040
一整个雨季
一整个雨季 2021-02-14 15:42

Based on this awesome Composition over Inheritance video by MPJ, I\'ve been trying to formulate composition in TypeScript. I want to compose classes, not object

2条回答
  •  感动是毒
    2021-02-14 16:44

    Composition vs Inheritance

    I think we should make a distinction between composition and inheritance and reconsider what we are trying to achieve. As a commenter pointed out, what MPJ does is actually an example of using mixins. This is basically a form of inheritance, adding implementation on the target object (mixing).

    Multiple inheritance

    I tried to come up with a neat way to do this and this is my best suggestion:

    type Constructor = new (...args: any[]) => I;
    
    class Base {}
    
    function Flies>(constructor: T = Base as any) {
      return class extends constructor implements IFlies {
        public fly() {
          console.log("Hi, I fly!");
        }
      };
    }
    
    function Quacks>(constructor: T = Base as any) {
      return class extends constructor implements ICanQuack {
        public quack(this: IHasSound, loud: boolean) {
          console.log(loud ? this.sound.toUpperCase() : this.sound);
        }
      };
    }
    
    interface IHasSound {
      sound: string;
    }
    
    interface ICanQuack {
      quack(loud: boolean): void;
    }
    
    interface IQuacks extends IHasSound, ICanQuack {}
    
    interface IFlies {
      fly(): void;
    }
    
    class MonsterDuck extends Quacks(Flies()) implements IQuacks, IFlies {
      public sound = "quackly!!!";
    }
    
    class RubberDuck extends Quacks() implements IQuacks {
      public sound = "quack";
    }
    
    const monsterDuck = new MonsterDuck();
    monsterDuck.quack(true); // "QUACKLY!!!"
    monsterDuck.fly(); // "Hi, I fly!"
    
    const rubberDuck = new RubberDuck();
    rubberDuck.quack(false); // "quack"
    

    The benefit of using this approach is that you can allow access to certain properties of the owner object in the implementation of the inherited methods. Although a bit better naming could be use, I see this as a very potential solution.

    Composition

    Composition is instead of mixing the functions into the object, we set what behaviours should be contained in it instead, and then implement these as self-contained libraries inside the object.

    interface IQuackBehaviour {
      quack(): void;
    }
    
    interface IFlyBehaviour {
      fly(): void;
    }
    
    class NormalQuack implements IQuackBehaviour {
      public quack() {
        console.log("quack");
      }
    }
    
    class MonsterQuack implements IQuackBehaviour {
      public quack() {
        console.log("QUACK!!!");
      }
    }
    
    class FlyWithWings implements IFlyBehaviour {
      public fly() {
        console.log("I am flying with wings");
      }
    }
    
    class CannotFly implements IFlyBehaviour {
      public fly() {
        console.log("Sorry! Cannot fly");
      }
    }
    
    interface IDuck {
      flyBehaviour: IFlyBehaviour;
      quackBehaviour: IQuackBehaviour;
    }
    
    class MonsterDuck implements IDuck {
      constructor(
        public flyBehaviour = new FlyWithWings(),
        public quackBehaviour = new MonsterQuack()
      ) {}
    }
    
    class RubberDuck implements IDuck {
      constructor(
        public flyBehaviour = new CannotFly(),
        public quackBehaviour = new NormalQuack()
      ) {}
    }
    
    const monsterDuck = new MonsterDuck();
    monsterDuck.quackBehaviour.quack(); // "QUACK!!!"
    monsterDuck.flyBehaviour.fly(); // "I am flying with wings"
    
    const rubberDuck = new RubberDuck();
    rubberDuck.quackBehaviour.quack(); // "quack"
    

    As you can see, the practical difference is that the composites doesn't know of any properties existing on the object using it. This is probably a good thing, as it conforms to the principle of Composition over Inheritance.

提交回复
热议问题