TypeScript:类组成

Gle*_*mad 5 javascript class composition typescript ecmascript-7

基于MPJ的这段出色的“ 继承中合成”视频,我一直试图在TypeScript中制定合成。我想组成,而不是对象或工厂函数。到目前为止,这是我的努力(在lodash的帮助):

class Barker {
  constructor(private state) {}

  bark() {
    console.log(`Woof, I am ${this.state.name}`);
  }
}

class Driver {
  constructor(private state) {}

  drive() {
    this.state.position = this.state.position + this.state.speed;
  }
}

class Killer {
  constructor(private state) {}

  kill() {
    console.log(`Burn the ${this.state.prey}`);
  }
}

class MurderRobotDog {
  constructor(private state) {
    return _.assignIn(
      {},
      new Killer(state),
      new Driver(state),
      new Barker(state)
    );
  }
}

const metalhead = new MurderRobotDog({ 
  name: 'Metalhead', 
  position: 0, 
  speed: 100, 
  prey: 'witch' 
});

metalhead.bark(); // expected: "Woof, I am Metalhead"
metalhead.kill(); // expected: "Burn the witch"
Run Code Online (Sandbox Code Playgroud)

结果是:

TS2339:类型“ MurderRobotDog”上不存在属性“ bark”

TS2339:类型“ MurderRobotDog”上不存在属性“ kill”

在TypeScript中进行类组合的正确方法是什么?

th3*_*guy 5

不幸的是,没有简单的方法可以做到这一点。目前有一个提案允许关键字允许您执行此操作,但仍在本 GitHub 问题extends中讨论。

您唯一的其他选择是使用TypeScript 中提供的Mixins 功能,但该方法的问题是您必须重新定义要从“继承”类中重用的每个函数或方法。


nom*_*oda 5

组合与继承

我认为我们应该区分组成和继承,并重新考虑我们要实现的目标。正如评论员所指出的那样,MPJ所做的实际上是使用mixins的示例。这基本上是一种继承形式,在目标对象上添加了实现(混合)。

多重继承

我试图提出一种巧妙的方法来做到这一点,这是我最好的建议:

type Constructor<I extends Base> = new (...args: any[]) => I;

class Base {}

function Flies<T extends Constructor<Base>>(constructor: T = Base as any) {
  return class extends constructor implements IFlies {
    public fly() {
      console.log("Hi, I fly!");
    }
  };
}

function Quacks<T extends Constructor<Base>>(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"
Run Code Online (Sandbox Code Playgroud)

使用这种方法的好处是,您可以在继承的方法的实现中允许访问所有者对象的某些属性。尽管可以使用更好的命名方式,但我认为这是一个非常有潜力的解决方案。

组成

合成不是将功能混合到对象中,而是设置应包含在其中的行为,然后将其实现为对象内部的独立库。

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"
Run Code Online (Sandbox Code Playgroud)

如您所见,实际的区别在于,复合材料不知道使用它的对象上存在的任何属性。这可能是一件好事,因为它符合“继承而不是继承”的原则。

  • 这根本不是组合的意思。打字稿中的“implements”仅确保类符合子类型(例如,具有变量“foo”或函数“bar”)。另一方面,组合也可以做到这一点,但更进一步,确保类也符合实现,即组合**为**类提供子类型和实现...例如,我们可以确保类获取默认值为 1 的变量“foo”。Typescript 目前本身不支持此功能,但您可以使用 mixin 部分实现组合。 (3认同)