TypeScript:类组成 [英] TypeScript: class composition
问题描述
基于MPJ的这段很棒的在继承之上的组成视频,我一直在试图在TypeScript中制定组成.我要编写类,而不是对象或工厂函数.到目前为止,这是我的努力(在 lodash 的帮助下):
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 objects or factory functions. Here is my effort so far (with a little help from 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: Property 'bark' does not exist on type 'MurderRobotDog'
TS2339:"MurderRobotDog"类型上不存在属性"kill"
TS2339: Property 'kill' does not exist on type 'MurderRobotDog'
在TypeScript中进行类组合的正确方法是什么?
What's the right way of doing class composition in TypeScript?
推荐答案
组成与继承
我认为我们应该在构图和继承之间做出区分,并重新考虑我们要实现的目标.正如评论员所指出的那样,MPJ所做的实际上是使用mixins的一个示例.这基本上是一种继承形式,在目标对象上添加实现(混合).
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).
我试图提出一种简洁的方法来做到这一点,这是我最好的建议:
I tried to come up with a neat way to do this and this is my best suggestion:
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"
使用此方法的好处是,您可以在继承的方法的实现中允许访问所有者对象的某些属性.尽管可以使用更好的命名方式,但我认为这是一个非常有潜力的解决方案.
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 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.
这篇关于TypeScript:类组成的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!