TypeScript에서 추상 메서드 선언


195

TypeScript에서 추상 메서드를 올바르게 정의하는 방법을 찾으려고합니다.

원래 상속 예제 사용 :

class Animal {
    constructor(public name) { }
    makeSound(input : string) : string;
    move(meters) {
        alert(this.name + " moved " + meters + "m.");
    }
}

class Snake extends Animal {
    constructor(name) { super(name); }
    makeSound(input : string) : string {
        return "sssss"+input;
    }
    move() {
        alert("Slithering...");
        super.move(5);
    }
}

메소드 makeSound를 올바르게 정의하는 방법을 알고 싶습니다. 그래서 입력하고 덮어 쓸 수 있습니다.

또한 올바른 protected방법 을 정의하는 방법을 잘 모르겠습니다 . 키워드 인 것처럼 보이지만 효과가 없으며 코드가 컴파일되지 않습니다.


4
추상 클래스와 메소드는 이제 곧 TypeScript 1.6의 새로운 기능입니다.
— falconepl

답변:


285

name속성은 다음과 같이 표시된다 protected. 이것은 TypeScript 1.3에 추가되었으며 이제 확실하게 설정되었습니다.

makeSound방법으로 표시된 abstract클래스처럼. Animal지금은 추상적이기 때문에 직접 인스턴스화 할 수 없습니다 . 이것은 TypeScript 1.6의 일부 이며 현재 공식적으로 제공됩니다.

abstract class Animal {
    constructor(protected name: string) { }

    abstract makeSound(input : string) : string;

    move(meters) {
        alert(this.name + " moved " + meters + "m.");
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }

    makeSound(input : string) : string {
        return "sssss"+input;
    }

    move() {
        alert("Slithering...");
        super.move(5);
    }
}

추상적 인 방법을 모방 한 오래된 방법은 누군가가 그것을 사용하면 오류를 던졌습니다. TypeScript 1.6이 프로젝트에 도달하면 더 이상이 작업을 수행 할 필요가 없습니다.

class Animal {
    constructor(public name) { }
    makeSound(input : string) : string {
        throw new Error('This method is abstract');
    }
    move(meters) {
        alert(this.name + " moved " + meters + "m.");
    }
}

class Snake extends Animal {
    constructor(name) { super(name); }
    makeSound(input : string) : string {
        return "sssss"+input;
    }
    move() {
        alert("Slithering...");
        super.move(5);
    }
}

추상적 인 메소드를 재정의 할 때 매개 변수를 놓치거나 매개 변수 유형을 변경하거나 반환 유형을 변경하면 컴파일러가 불평하지 않는 것이 정상적인 동작입니까?
— Vetterjack

1
매개 변수를 생략하고 (사용하지 않으면 전달 된 값을 무시할 수 있음) 유효하며 호환 가능한 유형의 매개 변수를 가질 수 있습니다. makeSound(input : number) : string {위의 예제 를 기반으로 추상 메소드를 구현하려고 시도 input하면 문자열이 있어야합니다. Type 'string' is not assignable to type 'number'..
— Fenton

19

Erics가 조금 더 대답하면 다형성을 완벽하게 지원하고 기본 클래스에서 구현 된 메소드를 호출 할 수있는 추상 클래스의 꽤 괜찮은 구현을 실제로 만들 수 있습니다. 코드로 시작하자 :

/**
 * The interface defines all abstract methods and extends the concrete base class
 */
interface IAnimal extends Animal {
    speak() : void;
}

/**
 * The abstract base class only defines concrete methods & properties.
 */
class Animal {

    private _impl : IAnimal;

    public name : string;

    /**
     * Here comes the clever part: by letting the constructor take an 
     * implementation of IAnimal as argument Animal cannot be instantiated
     * without a valid implementation of the abstract methods.
     */
    constructor(impl : IAnimal, name : string) {
        this.name = name;
        this._impl = impl;

        // The `impl` object can be used to delegate functionality to the
        // implementation class.
        console.log(this.name + " is born!");
        this._impl.speak();
    }
}

class Dog extends Animal implements IAnimal {
    constructor(name : string) {
        // The child class simply passes itself to Animal
        super(this, name);
    }

    public speak() {
        console.log("bark");
    }
}

var dog = new Dog("Bob");
dog.speak(); //logs "bark"
console.log(dog instanceof Dog); //true
console.log(dog instanceof Animal); //true
console.log(dog.name); //"Bob"

Animal클래스는 구현이 필요하기 때문에 추상 메소드의 유효한 구현 없이는 IAnimal유형의 오브젝트를 구성 할 수 Animal없습니다. 다형성이 작동하려면가 IAnimal아닌 인스턴스를 전달해야합니다 Animal. 예 :

//This works
function letTheIAnimalSpeak(animal: IAnimal) {
    console.log(animal.name + " says:");
    animal.speak();
}
//This doesn't ("The property 'speak' does not exist on value of type 'Animal')
function letTheAnimalSpeak(animal: Animal) {
    console.log(animal.name + " says:");
    animal.speak();
}

Erics의 대답과의 주요 차이점은 "추상적 인"기본 클래스 는 인터페이스의 구현이 필요 하므로 자체적으로 인스턴스화 할 수 없다는 것입니다.


1
적어도 Typescript v1에서는 나를 위해-생성자 내에서 'this'를 참조하여 super로 전달할 수 없습니다. 생각?
— Kieran Benton

어떤 정확한 버전의 컴파일러를 사용하고 어떤 오류가 발생합니까? tsc 1.0.1은 위의 스 니펫을 완벽하게 컴파일합니다.
— Tiddo

'this'키워드는 super ()에서 허용되지 않습니다. tsc 1.0.3을 사용하는 Im
— Zasz

꽤 이상합니다. CLI 컴파일러 또는 Visual Studio를 사용합니까?
— Tiddo

나도 super () 호출에서 "this"를 사용할 수 없습니다. 자식 구현을 위해 부모 멤버를 설정하기 위해 바로 사용할 수 있지만 추상 클래스의 확장을 강제하지는 않습니다. Palantir의 Eclispe Typsscript 플러그인 v1.0.1을 사용하고 있습니다. typescriptlang.org/Playground 에서 super (this)가 제대로 작동한다는 것을 알았습니다 .
— Eric

2

인터페이스와 기본 클래스의 조합을 사용하면 효과가 있다고 생각합니다. 컴파일 타임에 행동 요구 사항을 시행합니다 (rq_ post "아래"는 위의 게시물을 의미합니다).

인터페이스는 기본 클래스가 충족하지 않는 동작 API를 설정합니다. 인터페이스에 정의 된 메소드를 호출하도록 기본 클래스 메소드를 설정할 수 없습니다 (이러한 동작을 정의하지 않고 기본 클래스에서 해당 인터페이스를 구현할 수 없기 때문에). 어쩌면 누군가 부모의 인터페이스 메소드 호출을 허용 하는 안전한 트릭을 만들 수 있습니다.

인스턴스화 할 클래스에서 확장하고 구현해야합니다. 런타임 실패 코드 정의에 대한 우려를 만족시킵니다. 또한 인터페이스를 구현하지 않은 경우 (예 : Animal 클래스를 인스턴스화하려는 경우) 발생하는 메소드를 호출 할 수도 없습니다. 인터페이스를 아래 BaseAnimal로 확장하려고 시도했지만 Snake에서 BaseAnimal의 생성자 및 'name'필드를 숨겼습니다. 내가 그렇게 할 수 있다면, 모듈과 내보내기를 사용하면 우연히 BaseAnimal 클래스를 직접 인스턴스화하지 못하게 될 수 있습니다.

여기에 붙여 넣어 자신에게 적합한 지 확인하십시오. http://www.typescriptlang.org/Playground/

// The behavioral interface also needs to extend base for substitutability
interface AbstractAnimal extends BaseAnimal {
    // encapsulates animal behaviors that must be implemented
    makeSound(input : string): string;
}

class BaseAnimal {
    constructor(public name) { }

    move(meters) {
        alert(this.name + " moved " + meters + "m.");
    }
}

// If concrete class doesn't extend both, it cannot use super methods.
class Snake extends BaseAnimal implements AbstractAnimal {
    constructor(name) { super(name); }
    makeSound(input : string): string {
        var utterance = "sssss"+input;
        alert(utterance);
        return utterance;
    }
    move() {
        alert("Slithering...");
        super.move(5);
    }
}

var longMover = new Snake("windy man");

longMover.makeSound("...am I nothing?");
longMover.move();

var fulture = new BaseAnimal("bob fossil");
// compile error on makeSound() because it is not defined.
// fulture.makeSound("you know, like a...")
fulture.move(1);

아래 링크 된 FristvanCampen의 답변을 발견했습니다. 그는 추상 클래스는 반 패턴이며, 하나는 구현 클래스의 주입 된 인스턴스를 사용하여 기본 '추상'클래스를 인스턴스화 할 것을 제안합니다. 이것은 공정하지만 반대 주장이 있습니다. 스스로 읽어보십시오 : https://typescript.codeplex.com/discussions/449920

2 부 : 추상 클래스를 원했던 또 다른 경우가 있었지만 "추상 클래스"의 정의 된 메소드가 일치하는 인터페이스에 정의 된 메소드를 참조해야하기 때문에 위의 솔루션을 사용하지 못했습니다. 그래서 FristvanCampen의 조언을 도구로 사용합니다. 메소드 구현과 함께 불완전한 "추상"클래스가 있습니다. 구현되지 않은 메소드와의 인터페이스가 있습니다. 이 인터페이스는 "추상"클래스를 확장합니다. 그런 다음 첫 번째를 확장하고 두 번째를 구현하는 클래스가 있습니다 (그렇지 않으면 수퍼 생성자가 액세스 할 수 없으므로 둘 다 확장해야합니다). 아래의 (실행 불가능한) 샘플을 참조하십시오.

export class OntologyConceptFilter extends FilterWidget.FilterWidget<ConceptGraph.Node, ConceptGraph.Link> implements FilterWidget.IFilterWidget<ConceptGraph.Node, ConceptGraph.Link> {

    subMenuTitle = "Ontologies Rendered"; // overload or overshadow?

    constructor(
        public conceptGraph: ConceptGraph.ConceptGraph,
        graphView: PathToRoot.ConceptPathsToRoot,
        implementation: FilterWidget.IFilterWidget<ConceptGraph.Node, ConceptGraph.Link>
        ){
        super(graphView);
        this.implementation = this;
    }
}

과

export class FilterWidget<N extends GraphView.BaseNode, L extends GraphView.BaseLink<GraphView.BaseNode>> {

    public implementation: IFilterWidget<N, L>

    filterContainer: JQuery;

    public subMenuTitle : string; // Given value in children

    constructor(
        public graphView: GraphView.GraphView<N, L>
        ){

    }

    doStuff(node: N){
        this.implementation.generateStuff(thing);
    }

}

export interface IFilterWidget<N extends GraphView.BaseNode, L extends GraphView.BaseLink<GraphView.BaseNode>> extends FilterWidget<N, L> {

    generateStuff(node: N): string;

}

1

기본 클래스에서 예외를 던지는 데 사용합니다.

protected abstractMethod() {
    throw new Error("abstractMethod not implemented");
}

그런 다음 하위 클래스에서 구현해야합니다. 단점은 빌드 오류가 없지만 런타임이라는 것입니다. 장점은 작동한다고 가정하고 수퍼 클래스 에서이 메소드를 호출 할 수 있다는 것입니다. :)

HTH!

밀턴


-20

아니, 아니! 언어가 해당 기능을 지원하지 않을 때 자신 만의 '추상'클래스와 메소드를 만들려고 시도하지 마십시오. 주어진 언어가 지원되기를 원하는 언어 기능도 마찬가지입니다. TypeScript에서 추상 메서드를 구현하는 올바른 방법은 없습니다. 특정 클래스가 직접 인스턴스화되지는 않지만 명시 적 으로이 금지 사항을 적용하지 않도록 이름 지정 규칙으로 코드를 구성하십시오.

또한 위의 예제는 Java / C #에서 예상 한 것처럼 컴파일 타임이 아닌 런타임에만이 적용을 제공합니다.


4
어디에서 왔는지 알 수 있지만 정중하게 동의하지 않습니다. 언어가 무언가를 구현한다면, 그것을 직접 구현하는 것은 좋지 않습니다. 그러나 무언가가 없다면 어떻게 든 직접 구현할 수밖에 없습니다. 물론, 런타임까지 문제를 볼 수는 없지만 처음 테스트 할 때 예외를 던지면 꽤 빨리 던지기를 알 수 있습니다. 물론 이상적인 것은 아닙니다. IMO, Typescript는 추상 클래스 지원이 필요한 이유입니다. 그러나 그 때까지 ...
— Maverick

JavaScript에 클래스, 형식 유추, 정적 타이핑 및 인터페이스가 있고 Typescript에 무엇이 있는지 추측했습니다. 추상 메소드와 동일합니다. 컴파일러는 추상 클래스를 확장하는 모든 클래스가 이미 인터페이스에 대해 수행하는 것처럼 추상 메소드를 구현하는지 확인해야합니다 (인터페이스는 본질적으로 추상 메소드 만있는 클래스 임)
— Tony BenBrahim

1
나는 @rq_에 동의하는 경향이 있습니다. 추상 메소드의 요점은 프로그램이 유효하지 않은 상태가 될 수없는 컴파일 시간 유효성 검증을 얻는 것입니다. 제안 된 솔루션은 런타임 검사를 제공하므로 프로그램이 실행될 때 유효한 상태인지 확인할 수 없습니다. 즉, 방법이 구현되지 않았다는 가정하에 운영해야하며 그에 따라 보호해야합니다. 추상적 인 메소드가 있다고 생각하면 예기치 않은 런타임 동작으로 물릴 수 있습니다.
— Micah Zoltu 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.