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


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;
    }
}

내가하고 싶은 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.