Typescript : 두 클래스를 확장하는 방법은 무엇입니까?


85

시간을 절약하고 PIXI 클래스 (2d webGl 렌더러 라이브러리)를 확장하는 클래스간에 공통 코드를 재사용하고 싶습니다.

개체 인터페이스 :

module Game.Core {
    export interface IObject {}

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

내 문제는 코드 내부에 있다는 것입니다 getKeyInManager및 setKeyInManager변경되지 않습니다 내가하지 그것을 복제, 재사용하려면, 여기 구현은 다음과 같습니다

export class ObjectThatShouldAlsoBeExtended{
    private _keyInManager: string;

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

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

내가하고 싶은 Manager.add()것은 관리자에서 사용되는 키를 통해 자동으로 추가 하여 속성에서 개체 자체 내부 의 개체 를 참조하는 것입니다 _keyInManager.

자, 텍스처로 예를 들어 보겠습니다. 여기에 간다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. 동일한 프로세스가 요구 될 것이라는 점을 알고 Sprite, TilingSprite, Layer등.

경험이 풍부한 TypeScript 피드백 / 조언이 여기에 필요합니다. 가능해야하지만 한 번에 하나만 가능하기 때문에 여러 확장으로 확장 할 수 없습니다. 다른 해결책을 찾지 못했습니다.


7
하나의 팁으로, 다중 상속 문제를 접할 때마다 "상속보다 구성을 선호한다"고 생각하여 그게 효과가 있는지 확인하려고합니다.
— bubbleking

2
동의합니다. 전 그런 식으로 2Y를 생각하지 않았습니다)
— Vadorequest

6
@bubbleking 상속보다 작곡을 선호하는 방법이 여기에 적용됩니까?
— Seanny123

답변:


94

Mixins를 사용하여 재사용 가능한 작은 개체를 만들 수있는 TypeScript에는 약간 알려진 기능이 있습니다. 다중 상속을 사용하여이를 더 큰 객체로 구성 할 수 있습니다 (클래스에는 다중 상속이 허용되지 않지만 믹스 인은 허용됩니다.

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
여기에 타이프 핸드북의 유지 mixin 부분이다 (그러나 스티브는 꽤 많이 당신이 대답을 알고, 그의 링크 된 문서에서 필요한 모든 포함) typescriptlang.org/Handbook#mixins
— 트로이 Gizzi

2
Typescript 2.2는 이제 Mixins를 지원합니다
— Flavien Volken 2017

1
@FlavienVolken Microsoft가 핸드북 문서에 이전 믹스 인 섹션을 유지 한 이유를 알고 있습니까? 그건 그렇고, 릴리스 노트는 저와 같은 TS 초보자에게는 이해하기가 정말 어렵습니다. TS 2.2+ 믹스 인이 포함 된 튜토리얼 링크가 있습니까? 감사.
— David D.

4
이 예제에 표시된 믹스 인을 수행하는 "이전 방법"은 "새로운 방법"(Typescript 2.2 이상)보다 간단합니다. 왜 그렇게 어렵게 만들 었는지 모르겠습니다.
— tocqueville

2
그 이유는 "오래된 방법"이 유형을 올바르게 가져올 수 없기 때문입니다.
— unional

25

https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/에 설명 된 새로운 믹스 인 접근 방식을 사용하는 것이 좋습니다.

이 접근 방식은 Fenton이 설명한 "applyMixins"접근 방식보다 낫습니다. 자동 컴파일러가 기본 및 두 번째 상속 클래스의 모든 메서드 / 속성을 표시하고 도움을주기 때문입니다.

이 접근 방식은 TS 플레이 그라운드 사이트 에서 확인할 수 있습니다 .

다음은 구현입니다.

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

이러한 믹스 인 구현의 요점은 두 클래스의 모든 메서드와 속성이 자동 완성 IDE 도움말에 표시됩니다. 원하는 경우 두 번째 클래스를 정의하고 Fenton에서 제안한 접근 방식을 사용할 수 있지만이 경우 IDE 자동 완성이 작동하지 않습니다. {new (... args)}-이 코드는 클래스 여야하는 객체를 설명합니다 (핸드북에서 TS 인터페이스에 대해 자세히 읽을 수 있습니다 : typescriptlang.org/docs/handbook/interfaces.html
— Mark Dolbyrev

2
여기서 문제는 TS가 여전히 수정 된 클래스에 대한 단서가 없다는 것입니다. 입력 secondInheritanceObj.some()할 수 있지만 경고 메시지가 표시되지 않습니다.
— sf

Mixed 클래스가 인터페이스를 준수하는지 확인하는 방법은 무엇입니까?
— rilut

11
Typescript의이 '새로운 믹스 인 접근 방식'은 나중에 생각한 것처럼 보입니다. 개발자로서 저는 "이 클래스가 ClassA와 ClassB를 상속 받길 원합니다"또는 "이 클래스가 ClassA와 ClassB의 혼합이되기를 원합니다"라고 말할 수 있기를 원하며 기억할 수있는 명확한 구문으로 표현하고 싶습니다. 6 개월 만에 멍청한 점보가 아닙니다. TS 컴파일러의 가능성에 대한 기술적 제한이 있다면 그렇게 되겠지만 이것은 해결책이 아닙니다.
— Rui Marques

12

불행히도 typescript는 다중 상속을 지원하지 않습니다. 따라서 완전히 사소한 대답은 없으며 프로그램을 재구성해야 할 것입니다.

다음은 몇 가지 제안입니다.

  • 이 추가 클래스에 많은 하위 클래스가 공유하는 동작이 포함되어 있으면 클래스 계층 구조의 맨 위 어딘가에 삽입하는 것이 좋습니다. 이 클래스에서 Sprite, Texture, Layer 등의 공통 수퍼 클래스를 파생시킬 수 있습니까? hirarchy 유형에서 좋은 자리를 찾을 수 있다면 이것은 좋은 선택이 될 것입니다. 그러나이 클래스를 임의의 지점에 삽입하는 것은 권장하지 않습니다. 상속은 "Is a-관계"를 표현합니다. 예를 들어 개는 동물이고 텍스처는이 클래스의 인스턴스입니다. 이것이 실제로 코드의 객체 간의 관계를 모델링하는지 스스로에게 물어봐야 할 것입니다. 논리적 상속 트리는 매우 중요합니다.

  • 추가 클래스가 유형 계층 구조에 논리적으로 맞지 않는 경우 집계를 사용할 수 있습니다. 즉,이 클래스 유형의 인스턴스 변수를 Sprite, Texture, Layer, ...의 공통 수퍼 클래스에 추가하면 모든 하위 클래스에서 getter / setter를 사용하여 변수에 액세스 할 수 있습니다. 이것은 "관계 있음"을 모델링합니다.

  • 클래스를 인터페이스로 변환 할 수도 있습니다. 그런 다음 모든 클래스로 인터페이스를 확장 할 수 있지만 각 클래스에서 메서드를 올바르게 구현해야합니다. 이것은 일부 코드 중복을 의미하지만이 경우에는 많지 않습니다.

어떤 접근 방식을 가장 좋아하는지 스스로 결정해야합니다. 개인적으로 클래스를 인터페이스로 변환하는 것이 좋습니다.

한 가지 팁 : Typescript는 getter 및 setter를위한 구문 설탕 인 속성을 제공합니다. http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/를 살펴보십시오.


2
흥미 롭군. 1) 단순히 확장 PIXI하고 라이브러리를 변경하여 다른 클래스를 추가 할 수 없기 때문에 그렇게 할 수 없습니다. 2) 내가 사용할 수있는 가능한 해결책 중 하나이지만 가능하면 피하고 싶었습니다. 3) 분명히 그 코드를 복제하고 싶지 않습니다. 지금은 간단 할 수 있지만 다음에 무슨 일이 일어날까요? 나는이 프로그램을 하루 만에 작업했고 나중에 더 많은 것을 추가 할 것입니다. 좋은 해결책은 아닙니다. 자세한 답변에 감사드립니다.
— Vadorequest

실제로 흥미로운 링크이지만 여기서 문제를 해결하기위한 사용 사례가 없습니다.
— Vadorequest

이러한 모든 클래스가 PIXI를 확장하는 경우 ObjectThatShouldAlsoBeExtended 클래스가 PIXI를 확장하고 여기에서 Texture, Sprite, ... 클래스를 파생 시키십시오. 그게 내가 hirarchy 유형에 클래스를 삽입한다는 의미입니다
— lhk

PIXI그 자체는 클래스가 아니라 모듈이고 확장 할 수 없습니다.
— Vadorequest 2014

1
그래서 각 클래스는 PIXI 모듈에서 다른 클래스를 확장합니까? 그렇다면 당신이 맞습니다. ObjectThatShouldAlso 클래스를 hirarchy 유형에 삽입 할 수 없습니다. 그건 불가능하다. 여전히 has-a 관계 또는 인터페이스를 선택할 수 있습니다. 모든 클래스가 공유하는 일반적인 동작을 설명하고 싶으므로 인터페이스를 사용하는 것이 좋습니다. 코드가 중복 되더라도 가장 깔끔한 디자인입니다.
— lhk

10

TypeScript는 데코레이터를 지원하며 해당 기능과 typescript-mix 라는 작은 라이브러리 를 사용하면 믹스 인 을 사용하여 단 몇 줄로 여러 상속을 가질 수 있습니다.

// 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클래스에 구현을 추가해야합니다 . 다음 인터페이스도 구현하는 클래스 믹스 인을 사용할 수 있습니다.

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클래스 믹스 인으로 클래스 확장 :

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 함수의 반환 된 유형은 무엇입니까?
— Luke Skywalker

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과는 ChildB이제 액세스 할 수 있습니다 Parent하지만 그들은 당신과 같은 경고를 받게됩니다 것을 의미 인식되지 않습니다, 클래스Property '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);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.