Abstracte methode declareren in TypeScript

Ik probeer erachter te komen hoe ik abstracte methoden correct definieer in TypeScript:

Gebruik het originele overervingsvoorbeeld:

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

Ik zou graag willen weten hoe ik de methode makeSound correct definieer, zodat deze getypt is en overschreven kan worden.

Ik weet ook niet zeker hoe ik de juiste protectedmethoden moet definiëren – het lijkt een trefwoord te zijn, maar heeft geen effect en de code kan niet worden gecompileerd.


Antwoord 1, autoriteit 100%

De eigenschap nameis gemarkeerd als protected. Dit is toegevoegd in TypeScript 1.3 en is nu stevig verankerd.

De makeSoundmethode is gemarkeerd als abstract, net als de klasse. Je kunt nu niet direct een Animalinstantiëren, omdat het abstract is. Dit maakt deel uit van TypeScript 1.6, dat nu officieel live is.

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

De oude manier om een ​​abstracte methode na te bootsen was om een ​​fout te genereren als iemand het gebruikte. U hoeft dit niet meer te doen als TypeScript 1.6 eenmaal in uw project is beland:

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

Antwoord 2, autoriteit 7%

Als je het antwoord van Erics wat verder doorneemt, kun je een behoorlijk behoorlijke implementatie van abstracte klassen maken, met volledige ondersteuning voor polymorfisme en de mogelijkheid om geïmplementeerde methoden vanuit de basisklasse aan te roepen. Laten we beginnen met de code:

/**
 * 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"

Aangezien de klasse Animaleen implementatie van IAnimalvereist, is het onmogelijk om een ​​object van het type Animalte construeren zonder een geldige implementatie van de samenvatting methoden. Merk op dat om polymorfisme te laten werken, je instanties van IAnimalmoet doorgeven, niet Animal. Bijv.:

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

Het belangrijkste verschil met het antwoord van Eric is dat de “abstracte” basisklasse een implementatie van de interface vereisten dus niet op zichzelf kan worden geïnstantieerd.


Antwoord 3

Ik geloof dat het gebruik van een combinatie van interfaces en basisklassen voor jou zou kunnen werken. Het zal tijdens het compileren gedragsvereisten afdwingen (rq_ post “below” verwijst naar een post hierboven, die niet deze is).

De interface stelt de gedrags-API in waaraan de basisklasse niet voldoet. U kunt geen basisklassemethoden instellen om methoden aan te roepen die in de interface zijn gedefinieerd (omdat u die interface niet in de basisklasse kunt implementeren zonder dat gedrag te hoeven definiëren). Misschien kan iemand een veiligetruc bedenken om het aanroepen van de interfacemethoden in de ouder toe te staan.

Je moet onthouden om uit te breiden en te implementeren in de klasse die je gaat instantiëren. Het voldoet aan zorgen over het definiëren van runtime-fail-code. Je kunt ook niet eens de methoden aanroepen die zouden kotsen als je de interface niet hebt geïmplementeerd (zoals wanneer je probeert de Animal-klasse te instantiëren). Ik heb geprobeerd de interface de BaseAnimal hieronder te laten uitbreiden, maar het verborg de constructor en het ‘naam’-veld van BaseAnimal voor Snake. Als ik dat had kunnen doen, had het gebruik van een module en exports kunnen voorkomen dat de klasse BaseAnimal per ongeluk direct werd aangemaakt.

Plak dit hier om te zien of het voor jou werkt: 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);

Ik kwam het antwoord van FristvanCampen tegen zoals hieronder gelinkt. Hij zegt dat abstracte klassen een anti-patroon zijn, en stelt voor om ‘abstracte’ basisklassen te instantiëren met behulp van een geïnjecteerde instantie van een implementerende klasse. Dat is terecht, maar er zijn tegenargumenten. Lees zelf:
https://typescript.codeplex.com/discussions/449920

Deel 2:
Ik had een ander geval waar ik een abstracte klasse wilde, maar ik werd verhinderd mijn oplossing hierboven te gebruiken, omdat de gedefinieerde methoden in de “abstracte klasse” moesten verwijzen naar de methoden die zijn gedefinieerd in de bijpassende interface. Dus ik heb het advies van Fristvancemming, het soort van. Ik heb de onvolledige “abstracte” klasse, met methode-implementaties. Ik heb de interface met de niet-geïmplementeerde methoden; Deze interface breidt de “abstracte” klasse uit. Ik heb dan een klasse die de eerste en implementeert de tweede (het moet beide uitstrekken omdat de Super Constructor anders ontoegankelijk is). Zie het (niet-runnable) monster hieronder:

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

en

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

4

Ik gebruik om een ​​uitzondering in de basisklasse te gooien.

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

Dan moet u in de subklasse implementeren.
De nadelen is dat er geen build-fout is, maar run-time. De profs is dat u deze methode van de superklasse kunt noemen, ervan uitgaande dat het werkt 🙂

HTH!

Milton

Other episodes