TypeScript'te soyut yöntem bildirme


195

Nasıl doğru TypeScript soyut yöntemleri tanımlamak anlamaya çalışıyorum:

Orijinal miras örneğini kullanma:

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 yönteminin doğru bir şekilde nasıl tanımlanacağını bilmek istiyorum, bu yüzden yazılmıştır ve aşırıya kaçmak mümkündür.

Ayrıca, doğru protectedyöntemlerin nasıl tanımlanacağından emin değilim - bir anahtar kelime gibi görünüyor, ancak hiçbir etkisi yok ve kod derlenmeyecek.


4
Soyut sınıflar ve yöntemler artık yaklaşmakta olan TypeScript 1.6'nın yeni bir özelliğidir.
falconepl

Yanıtlar:


285

nameMülkiyet olarak işaretlenmiş protected. Bu, TypeScript 1.3'e eklenmiştir ve şimdi sağlam bir şekilde kurulmuştur.

makeSoundYöntem olarak işaretlenmiş abstractsınıf olarak,. Bir anı doğrudan başlatamazsınız Animal, çünkü soyuttur. Bu, artık resmi olarak yayınlanan TypeScript 1.6'nın bir parçasıdır .

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

Soyut bir yöntemi taklit etmenin eski yolu, herhangi biri kullandıysa bir hata atmaktı. TypeScript 1.6 projenize ulaştığında bunu bir daha yapmanıza gerek yoktur:

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

Bir parametre kaçırırsam, parametre türünü değiştirirsem veya soyut bir yöntemi geçersiz kılarken dönüş türünü değiştirirsem derleyici şikayette bulunmaz mı?
Vetterjack

1
Bir parametreyi atlamak için geçerlidir (eğer kullanmazsanız, iletilen herhangi bir değeri yoksayabilirsiniz) ve uyumlu tipte parametreleriniz olabilir. Dize olması gereken makeSound(input : number) : string {yukarıdaki örneğe göre soyut bir yöntem uygulamaya çalıştığınızda bir hata alırsınız input. Type 'string' is not assignable to type 'number'..
Fenton

19

Erics'in cevabını biraz daha ileri götürürseniz, polimorfizm için tam destek ve temel sınıftan uygulanan yöntemleri çağırabilme yeteneği ile soyut sınıfların oldukça iyi bir uygulamasını oluşturabilirsiniz. Kodla başlayalım:

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

Yana Animalsınıf bir uygulama gerektirir ve IAnimalbu türde bir nesne oluşturmak için imkansız Animalsoyut yöntemlerin geçerli bir uygulama kalmadan. Polimorfizmin çalışması için IAnimaldeğil, örneklerinin etrafından geçmeniz gerektiğini unutmayın Animal. Örneğin:

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

Buradaki Erics cevabı ile temel fark, "soyut" temel sınıfın arayüzün uygulanmasını gerektirmesi ve dolayısıyla kendi başına somutlaştırılamamasıdır.


1
Benim için en azından Typescript v1 ile - süper yapmak için bir yapıcı içinde 'bu' referans olamaz. Düşünceler?
Kieran Benton

Derleyicinin tam olarak hangi sürümünü kullanıyorsunuz ve hangi hatayı alıyorsunuz? tsc 1.0.1 yukarıdaki parçacıkları mükemmel şekilde derler.
Tiddo

'This' anahtar kelimesine super () içinde izin verilmez. Im tsc 1.0.3 kullanıyorum
Zasz

Bu çok garip. CLI derleyicisini veya Visual Studio mu kullanıyorsunuz?
Tiddo

Ben de super () çağrısında "this" kullanamıyorum. Ebeveyn üyesini alt uygulama için ayarlamak için hemen kullanabilirim, ancak bu soyut sınıfın genişletilmesini zorunlu kılmaz. Palantir'in Eclispe Typsscript eklentisini v1.0.1 kullanıyorum. Süper (bu) typescriptlang.org/Playground'da iyi çalıştığını fark ettim .
Eric

2

Arayüzler ve temel sınıfların bir kombinasyonunu kullanmanın sizin için yararlı olabileceğine inanıyorum. Bu, derleme zamanında davranışsal gereksinimleri zorunlu kılar (rq_ post "below" yukarıdaki bir gönderiyi belirtir, bu değil).

Arabirim, temel sınıf tarafından karşılanmayan davranışsal API'yi ayarlar. Arayüzde tanımlanan yöntemleri çağırmak için temel sınıf yöntemlerini ayarlayamazsınız (çünkü bu davranışları tanımlamak zorunda kalmadan bu arabirimi temel sınıfta uygulayamazsınız). Belki birisi ebeveynte arayüz yöntemlerinin çağrılmasına izin vermek için güvenli bir numara yapabilir .

Anlatacağınız sınıfı genişletmeyi ve uygulamayı hatırlamalısınız. Çalışma zamanı hatası kodunu tanımlama endişelerini giderir. Ayrıca, arabirimi uygulamadıysanız (örneğin Animal sınıfını başlatmaya çalıştığınızda) kusacak yöntemleri bile çağıramazsınız. Arayüzü aşağıdaki BaseAnimal genişletmek zorunda çalıştı, ancak yapıcı ve BaseAnimal 'isim' alanı Snake sakladı. Bunu yapabilseydim, bir modülün ve ihracatın kullanılması BaseAnimal sınıfının kazara doğrudan örneklenmesini engelleyebilirdi.

İşinize yarayıp yaramadığını görmek için buraya yapıştırın: 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);

Aşağıda bağlantılı olarak FristvanCampen'in cevabına rastladım. Soyut sınıfların bir anti-desen olduğunu söylüyor ve bir uygulayıcı sınıfın enjekte edilmiş bir örneğini kullanarak bir anlık temel 'soyut' sınıfların olduğunu öne sürüyor. Bu adil, ama karşı argümanlar yapıldı. Kendiniz okuyun: https://typescript.codeplex.com/discussions/449920

Bölüm 2: Soyut bir sınıf istediğim başka bir durumum vardı, ancak yukarıdaki çözümü kullanmam engellendi, çünkü "soyut sınıf" daki tanımlı yöntemlerin eşleşen arayüzde tanımlanan yöntemlere başvurması gerekiyordu. Ben de FristvanCampen'in tavsiyesini kullanıyorum. Metot uygulamaları ile eksik "soyut" sınıfım var. Uygulanmayan yöntemlerle ara yüzüm var; bu arabirim "soyut" sınıfı genişletir. Sonra ilk genişleten ve ikinci uygular bir sınıf var (süper yapıcı aksi takdirde erişilemez çünkü her ikisini de uzatmak gerekir). Aşağıdaki (çalıştırılamaz) örneğe bakın:

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

ve

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

Ben temel sınıf bir istisna atmak için kullanın.

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

O zaman alt sınıfta uygulamanız gerekir. Eksileri hiçbir yapı hatası, ama çalışma zamanı olmasıdır. Artıları, bu yöntemi çalışacağını varsayarak, süper sınıftan çağırabilirsiniz :)

HTH!

Milton


-20

Hayır hayır hayır! Dil bu özelliği desteklemediğinde lütfen kendi 'soyut' sınıflarınızı ve yöntemlerinizi oluşturmaya çalışmayın; aynı şey, belirli bir dilin desteklenmesini istediğiniz herhangi bir dil özelliği için de geçerlidir. TypeScript'te soyut yöntemleri uygulamanın doğru bir yolu yoktur. Kodunuzu, belirli sınıfların hiçbir zaman doğrudan başlatılmayacağı, ancak bu yasağı açıkça uygulamadan, isimlendirme kurallarıyla yapılandırın.

Ayrıca, yukarıdaki örnek Java / C # 'da beklediğiniz gibi bu zorlamayı derleme zamanında değil, yalnızca çalışma zamanında sağlayacaktır.


4
Nereden geldiğini görebiliyorum, ama saygıyla katılmıyorum. Bir dil bir şeyi uygularsa, onu kendiniz yeniden uygulamak kötüdür. Ama eğer bir şeyiniz yoksa, bir şekilde kendiniz uygulamaktan başka seçeneğiniz yoktur. Elbette, sorunları çalışma zamanına kadar görmezsiniz, ancak bir şeyi ilk kez test ettiğinizde bir istisna atmak, oldukça hızlı bir şekilde doping yaptığınızı bilmenizi sağlar. Tabii ki ideal değil - bu yüzden IMO, Typcript'in soyut sınıf desteğine ihtiyacı var. Ancak bu kadar ...
Maverick

JavaScript sınıfları, tür çıkarım, statik yazım ve arayüzleri vardı ve tahmin ne, Typcript var diledi. Soyut yöntem için aynı olurdu, derleyici, soyut sınıfı genişleten herhangi bir sınıfın, arayüzler için zaten olduğu gibi soyut yöntemi uyguladığını kontrol etmelidir (bir arayüz aslında sadece soyut yöntemle bir sınıftır)
Tony BenBrahim

1
Burada @ rq_ ile hemfikirim. Soyut yöntemlerin amacı, programın geçersiz bir duruma getiremediği derleme zamanı doğrulaması elde etmektir. Önerilen çözümler sadece çalışma zamanı kontrolleri verir, yani programınız çalıştığında geçerli bir durumda olduğundan emin olamazsınız. Bu, yöntemin uygulanmadığı varsayımı altında çalışmanız ve buna göre korunmanız gerektiği anlamına gelir. Soyut yöntemlere sahip olduğunuzu kendinize söylemek, sadece beklenmedik çalışma zamanı davranışından ısırılmayı istemektir.
Micah Zoltu
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.