Объявление абстрактного метода в 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 - это похоже на ключевое слово, но не имеет никакого эффекта, и код не будет компилироваться.

Ответ 1

Свойство 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);
    }
}

Ответ 2

Если вы ответите Эрику немного дальше, вы можете создать довольно приличную реализацию абстрактных классов с полной поддержкой полиморфизма и возможностью вызова реализованных методов из базового класса. Начнем с кода:

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

Основное отличие здесь от ответа Эрики заключается в том, что для "абстрактного" базового класса требуется реализация интерфейса, и поэтому он не может быть создан на его основе.

Ответ 3

Я считаю, что использование комбинации интерфейсов и базовых классов может сработать для вас. Он будет обеспечивать соблюдение поведенческих требований во время компиляции (rq_ post "ниже" относится к сообщению выше, которое не относится к этому).

Интерфейс устанавливает поведенческий API, который не удовлетворяет базовому классу. Вы не сможете установить методы базового класса для вызова методов, определенных в интерфейсе (потому что вы не сможете реализовать этот интерфейс в базовом классе, не определяя эти поведения). Может быть, кто-то может придумать безопасный трюк, чтобы разрешить вызов методов интерфейса в родительском.

Вы должны помнить о расширении и реализации в классе, который вы создадите. Он удовлетворяет проблемы с определением кода выполнения с ошибкой. Вы даже не сможете вызвать методы, которые могли бы запутать, если вы не реализовали интерфейс (например, если вы пытаетесь создать экземпляр класса Animal). Я попробовал, чтобы интерфейс расширил BaseAnimal ниже, но он скрыл конструктор и поле "name" BaseAnimal от Snake. Если бы я мог это сделать, использование модуля и экспорта могло бы предотвратить случайное непосредственное создание класса 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, вроде как. У меня есть неполный "абстрактный" класс, с реализациями метода. У меня есть интерфейс с нереализованными методами; этот интерфейс расширяет "абстрактный" класс. Тогда у меня есть класс, который расширяет первый и реализует второй (он должен расширяться и потому, что суперструктор недоступен в противном случае). См. Образец (non-runnable) ниже:

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;

}

Ответ 4

Я использую для исключения исключения в базовом классе.

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

Затем вы должны реализовать в подклассе. Недостатком является то, что ошибки сборки нет, но время выполнения. Плюсы в том, что вы можете вызывать этот метод из суперкласса, предполагая, что он будет работать:)

НТН!

Милтон

Ответ 5

Нет, нет, нет!. Не пытайтесь создавать собственные "абстрактные" классы и методы, когда язык не поддерживает эту функцию; то же самое касается любой языковой функции, которую вы хотите, чтобы данный язык поддерживался. Не существует правильного способа реализации абстрактных методов в TypeScript. Просто структурируйте свой код с соглашениями об именах, чтобы определенные классы никогда не создавались напрямую, но без явного применения этого запрета.

Кроме того, приведенный выше пример будет обеспечивать это принудительное выполнение во время выполнения, а не во время компиляции, как и следовало ожидать в Java/С#.