【问题标题】:TypeScript: class compositionTypeScript:类组合
【发布时间】:2018-02-12 23:12:54
【问题描述】:

根据 MPJ 的这个精彩的 Composition over Inheritance 视频,我一直在尝试用 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"

这导致:

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

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

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

【问题讨论】:

  • 我不知道_.assignIn 是什么,但这看起来更像是混合而不是组合。不,你不能轻易混合多个classes
  • @Bergi lodash.com/docs/4.17.5#assignIn。是的,我的努力在这里解决了这个答案:stackoverflow.com/a/39243098/2013891 - 这表明我们应该总是更喜欢混合而不是原型语言中的成员组合。 ¯_(ツ)_/¯
  • MPJ 的构图完全错误,而实际的构图从未出现在他的视频中。您正在寻找的是所谓的多重继承或混合。 reddit.com/r/programming/comments/5dxq6i/…
  • @GlennMohammad ...但您是否也知道为什么MurderRobotDog 实例没有任何预期的(原型)方法?和/或您是否已经阅读过您的 TS 转译成的 JavaScript 代码?
  • @JeffM 感谢您的指点!该线程确实使我对实际构图有所了解。 ❤︎ 我一直认为mixin是一种组合形式。

标签: javascript class typescript composition ecmascript-2016


【解决方案1】:

组合与继承

我认为我们应该区分组合和继承,并重新考虑我们想要实现的目标。正如评论者所指出的,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"

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

作曲

组合不是将函数混合到对象中,而是设置应该包含在其中的行为,然后将这些行为作为对象内部的自包含库来实现。

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"

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

【讨论】:

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

不幸的是,没有简单的方法可以做到这一点。目前有一个提议允许extends 关键字允许你这样做,但它仍在this GitHub issue 中讨论。

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

【讨论】:

  • @th3n3wgy 看起来您指出的 github 问题与接口有关。我找不到与班级组成相关的类似内容
猜你喜欢
  • 2021-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-11
  • 2018-06-27
  • 2020-07-18
  • 2016-12-25
  • 2021-09-27
相关资源
最近更新 更多