Programing

TypeScript에서 추상 메서드 선언

lottogame 2020. 5. 27. 07:30
반응형

TypeScript에서 추상 메서드 선언


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방법 을 정의하는 방법을 잘 모르겠습니다 . 키워드 인 것처럼 보이지만 효과가 없으며 코드가 컴파일되지 않습니다.


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

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

The main difference here with Erics answer is that the "abstract" base class requires an implementation of the interface, and thus cannot be instantiated on it's own.


I believe that using a combination of interfaces and base classes could work for you. It will enforce behavioral requirements at compile time (rq_ post "below" refers to a post above, which is not this one).

The interface sets the behavioral API that isn't met by the base class. You will not be able to set base class methods to call on methods defined in the interface (because you will not be able to implement that interface in the base class without having to define those behaviors). Maybe someone can come up with a safe trick to allow calling of the interface methods in the parent.

You have to remember to extend and implement in the class you will instantiate. It satisfies concerns about defining runtime-fail code. You also won't even be able to call the methods that would puke if you haven't implemented the interface (such as if you try to instantiate the Animal class). I tried having the interface extend the BaseAnimal below, but it hid the constructor and the 'name' field of BaseAnimal from Snake. If I had been able to do that, the use of a module and exports could have prevented accidental direct instantiation of the BaseAnimal class.

Paste this in here to see if it works for you: 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);

I came across FristvanCampen's answer as linked below. He says abstract classes are an anti-pattern, and suggests that one instantiate base 'abstract' classes using an injected instance of an implementing class. This is fair, but there are counter arguments made. Read for yourself: https://typescript.codeplex.com/discussions/449920

Part 2: I had another case where I wanted an abstract class, but I was prevented from using my solution above, because the defined methods in the "abstract class" needed to refer to the methods defined in the matching interface. So, I tool FristvanCampen's advice, sort of. I have the incomplete "abstract" class, with method implementations. I have the interface with the unimplemented methods; this interface extends the "abstract" class. I then have a class that extends the first and implements the second (it must extend both because the super constructor is inaccessible otherwise). See the (non-runnable) sample below:

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

and

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;

}

I use to throw an exception in the base class.

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

Then you have to implement in the sub-class. The cons is that there is no build error, but run-time. The pros is that you can call this method from the super class, assuming that it will work :)

HTH!

Milton


No, no, no! Please do not try to make your own 'abstract' classes and methods when the language does not support that feature; the same goes for any language feature you wish a given language supported. There is no correct way to implement abstract methods in TypeScript. Just structure your code with naming conventions such that certain classes are never directly instantiated, but without explicitly enforcing this prohibition.

Also, the example above is only going to provide this enforcement at run time, NOT at compile time, as you would expect in Java/C#.

참고URL : https://stackoverflow.com/questions/13333489/declaring-abstract-method-in-typescript

반응형