Оголошення абстрактного методу в 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'..
— Фентон

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 - я не можу посилати «це» з конструктора на перехід до супер. Думки?
— Кіран Бентон

Яку точну версію компілятора ви використовуєте та яку помилку ви отримуєте? tsc 1.0.1 ідеально поєднує наведені вище фрагменти.
— Тіддо

Ключове слово "це" заборонено в супер (). Я використовую tsc 1.0.3
— Zasz

Це досить дивно. Чи використовуєте ви компілятор CLI або Visual Studio?
— Тіддо

Я також не можу використовувати "це" у дзвінку super (). Я можу використовувати його відразу після того, щоб встановити члена батька для дитини, але це не примушує продовження абстрактного класу. Я використовую плагін Palantir Eclispe Typsscript, v1.0.1. Зауважую, що супер (це) чудово працює у typecriptlang.org/Playground .
— Ерік

2

Я вважаю, що використання комбінації інтерфейсів та базових класів могло б допомогти вам. Він буде виконувати поведінкові вимоги під час компіляції (rq_ пост "внизу" посилається на повідомлення вище, яке не є цим).

Інтерфейс встановлює API поведінки, який не відповідає базовому класу. Ви не зможете встановити методи базового класу для використання методів, визначених в інтерфейсі (тому що ви не зможете реалізувати цей інтерфейс у базовому класі, не визначаючи ці поведінки). Можливо, хтось може придумати безпечний трюк, щоб дозволити виклик методів інтерфейсу у батьків.

Ви повинні пам'ятати про розширення та впровадження у класі, який ви будете інстанціювати. Це задовольняє занепокоєння щодо визначення коду під час виконання програми. Ви навіть не зможете зателефонувати до методів, які могли б викликати, якщо ви не реалізували інтерфейс (наприклад, якщо ви намагаєтесь інстанціювати клас Animal). Я спробував, щоб інтерфейс розширив BaseAnimal внизу, але він приховав від Snake конструктор та поле "ім'я" BaseAnimal. Якби мені вдалося це зробити, використання модуля та експорту могло б запобігти випадковій прямій інстанції класу 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 потребує абстрактної підтримки класу. Поки це не сталося ...
— Маверик

Я хотів, щоб у JavaScript були класи, умовиводи, статичне введення та інтерфейси, і здогадуюсь, що це, Typescript має. Було б те ж саме для абстрактного методу, компілятор просто повинен перевірити , що будь-який клас , який розширює абстрактні знаряддя класу абстрактного методу, як це вже робить для інтерфейсів (інтерфейс, по суті , просто клас тільки з абстрактним методом)
— Тоні BenBrahim

1
Я схильний погодитися з @rq_ тут. Суть абстрактних методів полягає в тому, щоб перевірити час компіляції, що програма не може перейти в недійсний стан. Запропоновані рішення просто дають вам перевірити час виконання, а значить, коли програма працює, ви не можете бути впевнені, що вона в дійсному стані. Це означає, що вам слід працювати, припускаючи, що метод не реалізований, і зберігати відповідно. Брехати собі, що у вас є абстрактні методи - це просто просити вас вкусити несподіваною поведінкою під час виконання.
— Міхей Золту
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.