打字稿:如何扩展两个类?


85

我想节省时间,并在跨PIXI类(一个2D webGL渲染器库)的类之间重用通用代码。

对象接口:

module Game.Core {
    export interface IObject {}

    export interface IManagedObject extends IObject{
        getKeyInManager(key: string): string;
        setKeyInManager(key: string): IObject;
    }
}

我的问题是,里面的代码getKeyInManagersetKeyInManager不会改变,我想重新使用它,而不是复制它,这里是实现:

export class ObjectThatShouldAlsoBeExtended{
    private _keyInManager: string;

    public getKeyInManager(key: string): string{
        return this._keyInManager;
    }

    public setKeyInManager(key: string): DisplayObject{
        this._keyInManager = key;
        return this;
    }
}

我想要做的是通过a自动添加Manager.add()管理器中用来在其属性中引用对象本身内部的对象的键_keyInManager

因此,让我们以Texture为例。这里去了TextureManager

module Game.Managers {
    export class TextureManager extends Game.Managers.Manager {

        public createFromLocalImage(name: string, relativePath: string): Game.Core.Texture{
            return this.add(name, Game.Core.Texture.fromImage("/" + relativePath)).get(name);
        }
    }
}

当我这样做时this.add(),我希望该Game.Managers.Manager add()方法调用存在于所返回的对象上的方法Game.Core.Texture.fromImage("/" + relativePath)。在这种情况下,此对象将是Texture

module Game.Core {
    // I must extends PIXI.Texture, but I need to inject the methods in IManagedObject.
    export class Texture extends PIXI.Texture {

    }
}

我知道这IManagedObject是一个接口,不能包含实现,但是我不知道写什么将类注入ObjectThatShouldAlsoBeExtended我的Texture类中。知道,同样的过程将需要为SpriteTilingSpriteLayer等等。

在这里,我需要有经验的TypeScript反馈/建议,但必须能够做到,但不能进行多次扩展,因为当时只有一种可能,我没有找到其他解决方案。


7
提示,每当我遇到多重继承问题时,我都会提醒自己思考“偏重于继承”而不是“继承”。
冒泡

2
同意 2年前没有那样想;)
Vadorequest

6
@bubbleking在这里偏爱于组合而不是继承如何适用?
Seanny123 '17

Answers:


94

TypeScript中有一个鲜为人知的功能,可让您使用Mixins创建可重复使用的小对象。您可以使用多重继承将它们组合成更大的对象(类不允许多重继承,但mixin则允许多重继承-就像具有关联的实现的接口一样)。

有关TypeScript Mixins的更多信息

我认为您可以使用此技术在游戏中的多个类之间共享通用组件,并重用游戏中单个类的许多组件:

这是一个快速的Mixins演示...首先,要混合的口味:

class CanEat {
    public eat() {
        alert('Munch Munch.');
    }
}

class CanSleep {
    sleep() {
        alert('Zzzzzzz.');
    }
}

然后是创建Mixin的魔术方法(您只需在程序中的某个位置使用此方法一次...)

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
             if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    }); 
}

然后,您可以从mixin风味创建具有多个继承的类:

class Being implements CanEat, CanSleep {
        eat: () => void;
        sleep: () => void;
}
applyMixins (Being, [CanEat, CanSleep]);

请注意,此类中没有实际的实现-仅足以使其满足“接口”的要求。但是,当我们使用此类时-一切正常。

var being = new Being();

// Zzzzzzz...
being.sleep();

3
这是TypeScript手册中的Mixins部分(但Steve几乎涵盖了您需要在此答案中以及在他的链接文章中知道的所有内容)typescriptlang.org/Handbook#mixins
Troy Gizzi 2015年

2
Typescript 2.2现在支持
Mixins

1
@FlavienVolken您知道Microsoft为什么在其手册文档中保留了旧的mixins部分吗?顺便说一下,对于像我这样的TS初学者来说,发行说明真的很难理解。使用TS 2.2+ mixins的教程的任何链接吗?谢谢。
David D.

4
本示例中显示的“旧方法”进行混合操作比“新方法”(Typescript 2.2+)更简单。我不知道他们为什么要这么难。
tocqueville

2
原因是“旧方法”无法正确获取类型。
联合会'17

25

我建议使用那里描述的新的mixins方法:https : //blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/

这种方法比Fenton描述的“ applyMixins”方法更好,因为自动编译器将帮助您并显示基继承和第二继承类的所有方法/属性。

可以在TS Playground网站上检查此方法。

这是实现:

class MainClass {
    testMainClass() {
        alert("testMainClass");
    }
}

const addSecondInheritance = (BaseClass: { new(...args) }) => {
    return class extends BaseClass {
        testSecondInheritance() {
            alert("testSecondInheritance");
        }
    }
}

// Prepare the new class, which "inherits" 2 classes (MainClass and the cass declared in the addSecondInheritance method)
const SecondInheritanceClass = addSecondInheritance(MainClass);
// Create object from the new prepared class
const secondInheritanceObj = new SecondInheritanceClass();
secondInheritanceObj.testMainClass();
secondInheritanceObj.testSecondInheritance();

SecondInheritanceClass不是故意的定义还是我失去了一些东西?将代码加载到TS操场时,显示expecting =>。最后,您是否可以准确分解addSecondInheritance功能中发生的事情,例如目的是new (...args)什么?
Seanny123 '17

这种mixins实现的要点是,两个类的所有方法和属性都将显示在自动完成的IDE帮助中。如果需要,您可以定义第二类并使用Fenton建议的方法,但是在这种情况下,IDE自动完成功能将无效。{新(参数... args)} -这个代码描述了一个对象,它应该是一个类(你可以多在手册中读到TS接口:typescriptlang.org/docs/handbook/interfaces.html
马克Dolbyrev

2
这里的问题是TS仍然对修改的类一无所知。我可以输入secondInheritanceObj.some(),但不会收到警告消息。
SF

如何检查混合类是否服从接口?
rilut

11
Typescript的这种“新mixins方法”看起来像是事后的想法。作为开发人员,我只想说“我希望此类继承ClassA和ClassB”或“我希望此类成为ClassA和ClassB的混合体”,并且我想用清晰的语法表达这一点,我记得在6个月内,这不是大型的。如果这是TS编译器功能的技术限制,那就这样吧,但这不是解决方案。
马里·马克斯

12

不幸的是,打字稿不支持多重继承。因此,没有完全平凡的答案,您可能必须重组程序

这里有一些建议:

  • 如果此附加类包含许多子类共享的行为,则将其插入类层次结构中位于顶部的位置是有意义的。也许您可以从此类中派生Sprite,Texture,Layer等常见的超类?如果您可以在层次结构类型中找到合适的位置,那么这将是一个不错的选择。但是我不建议只在随机点插入此类。继承表示“是-关系”,例如,狗是动物,纹理是此类的一个实例。您将不得不问自己,这是否真的在代码中模拟了对象之间的关系。逻辑继承树非常有价值

  • 如果附加类在逻辑上不适合类型层次结构,则可以使用聚合。这意味着您将此类的实例变量添加到Sprite,Texture,Layer等的公共超类中。然后,您可以在所有子类中使用其getter / setter访问该变量。这将模拟“具有-关系”。

  • 您也可以将您的类转换为接口。然后,您可以扩展所有类的接口,但必须在每个类中正确实现方法。这意味着一些代码冗余,但在这种情况下不多。

您必须自己决定最喜欢哪种方法。我个人建议将类转换为接口。

提示:Typescript提供属性,这是针对吸气剂和吸脂剂的语法糖。您可能需要看一下:http : //blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/


2
有趣。1)我不能这样做,仅仅是因为我扩展了PIXI并且不能更改库以在其顶部添加另一个类。2)这是我可以使用的一种可能的解决方案,但是如果可能的话,我宁愿避免使用它。3)我绝对不想重复该代码,现在可能很简单,但是接下来会发生什么呢?我只为该程序工作了一天,以后还会添加更多内容,这对我来说不是一个好的解决方案。我会看一下提示,谢谢您的详细回答。
Vadorequest

确实有趣的链接,但是在这里我看不到任何用例来解决问题。
Vadorequest 2014年

如果所有这些类都扩展了PIXI,则只需使ObjectThatShouldAlsoBeExtended类扩展PIXI并从中派生Texture,Sprite等类。这就是我将类插入阶层结构中的意思
lhk 2014年

PIXI本身不是一个类,它是一个模块,无法扩展,但是您是对的,如果可以的话,那将是一个可行的选择!
Vadorequest 2014年

1
那么每个类都从PIXI模块扩展了另一个类?然后,您是对的,您不能将ObjectThatShouldAlso类插入到层次结构类型中。那不可能 您仍然可以选择has-a关系或界面。由于您想描述所有类共享的常见行为,因此建议您使用一个接口。即使代码重复,这也是最干净的设计。
lhk 2014年

10

TypeScript支持装饰器,并使用该功能以及一个名为typescript-mix的小库,您可以使用mixins仅几行即可具有多个继承

// The following line is only for intellisense to work
interface Shopperholic extends Buyer, Transportable {}

class Shopperholic {
  // The following line is where we "extend" from other 2 classes
  @use( Buyer, Transportable ) this 
  price = 2000;
}

7

我认为有一种更好的方法,可以实现可靠的类型安全性和可伸缩性。

首先声明要在目标类上实现的接口:

interface IBar {
  doBarThings(): void;
}

interface IBazz {
  doBazzThings(): void;
}

class Foo implements IBar, IBazz {}

现在我们必须将实现添加到Foo类中。我们可以使用还实现以下接口的类mixin:

class Base {}

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

function Bar<T extends Constructor>(constructor: T = Base as any) {
  return class extends constructor implements IBar {
    public doBarThings() {
      console.log("Do bar!");
    }
  };
}

function Bazz<T extends Constructor>(constructor: T = Base as any) {
  return class extends constructor implements IBazz {
    public doBazzThings() {
      console.log("Do bazz!");
    }
  };
}

Foo用类mixins扩展类:

class Foo extends Bar(Bazz()) implements IBar, IBazz {
  public doBarThings() {
    super.doBarThings();
    console.log("Override mixin");
  }
}

const foo = new Foo();
foo.doBazzThings(); // Do bazz!
foo.doBarThings(); // Do bar! // Override mixin

Bar和Bazz函数返回的类型是什么?
卢克·天行者

3

一个非常棘手的解决方案是遍历要继承的类,方法是将函数一个接一个地添加到新的父类中

class ChildA {
    public static x = 5
}

class ChildB {
    public static y = 6
}

class Parent {}

for (const property in ChildA) {
    Parent[property] = ChildA[property]
}
for (const property in ChildB) {
    Parent[property] = ChildB[property]
}


Parent.x
// 5
Parent.y
// 6

现在可以从该类中访问ChildA和的所有属性,但是不会被识别,这意味着您将收到警告,例如ChildBParentProperty 'x' does not exist on 'typeof Parent'


1

在设计模式中,有一个原则叫做“偏重于继承”。它说而不是从类A继承类B,而是将类B内的类A实例作为属性,然后可以在类B内使用类A的功能。您可以在此处此处看到一些示例。



0

这里已经有很多好的答案,但是我只想举例说明,您可以为正在扩展的类添加其他功能;

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    });
}

class Class1 {
    doWork() {
        console.log('Working');
    }
}

class Class2 {
    sleep() {
        console.log('Sleeping');
    }
}

class FatClass implements Class1, Class2 {
    doWork: () => void = () => { };
    sleep: () => void = () => { };


    x: number = 23;
    private _z: number = 80;

    get z(): number {
        return this._z;
    }

    set z(newZ) {
        this._z = newZ;
    }

    saySomething(y: string) {
        console.log(`Just saying ${y}...`);
    }
}
applyMixins(FatClass, [Class1, Class2]);


let fatClass = new FatClass();

fatClass.doWork();
fatClass.saySomething("nothing");
console.log(fatClass.x);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.