it-swarm.dev

إعلان طريقة مجردة في 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 بشكل صحيح - يبدو أنها كلمة رئيسية ، ولكن ليس لها أي تأثير ولن يتم تجميع الشفرة.

159
Vojtěch

يتم تحديد الخاصية 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);
    }
}
228
Fenton

إذا كنت تأخذ إجابة 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 هو أن الفئة الأساسية "المجردة" تتطلب تنفيذ الواجهة ، وبالتالي لا يمكن إنشاء مثيل لها من تلقاء نفسها.

19
Tiddo

أعتقد أن استخدام مجموعة من الواجهات والطبقات الأساسية يمكن أن يناسبك. سيتم فرض المتطلبات السلوكية في وقت الترجمة (يشير rq_ post "أدناه" إلى منشور أعلاه ، وهو ليس هذا واحد).

تعين الواجهة واجهة برمجة التطبيقات السلوكية التي لا تتحقق من خلال الفئة الأساسية. لن تتمكن من تعيين أساليب الطبقة الأساسية لاستدعاء الأساليب المحددة في الواجهة (لأنك لن تتمكن من تنفيذ تلك الواجهة في الفئة الأساسية دون الحاجة إلى تحديد تلك السلوكيات). ربما شخص ما يمكن أن يأتي مع آمنة خدعة للسماح استدعاء أساليب واجهة في الأصل.

عليك أن تتذكر أن تقوم بتوسيع وتنفيذ الفصل الذي ستقوم بإنشائه. يرضي المخاوف حول تعريف رمز وقت التشغيل - الفشل. لن تتمكن أيضًا من استدعاء الطرق التي من شأنها أن تتعطل إذا لم تقم بتطبيق الواجهة (مثل إذا كنت تحاول إنشاء مثيل لـ فئة الحيوان). لقد حاولت أن تقوم الواجهة بتوسيع BaseAnimal أدناه ، لكنها أخفت المُنشئ وحقل "اسم" 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 ، نوعا ما. لدي فئة "مجردة" غير مكتملة ، مع تطبيقات طريقة. لدي واجهة مع الأساليب غير المنفذة. يمتد هذا الواجهة فئة "مجردة". لدي بعد ذلك فصل يمتد الأول وينفذ الفصل الثاني (يجب أن يمتد كلاهما لأن المُنشئ الفائق يتعذر الوصول إليه على خلاف ذلك). انظر العينة (غير قابلة للتشغيل) أدناه:

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;

}
2
Eric

أنا استخدم لرمي استثناء في الفئة الأساسية.

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

ثم عليك أن تنفذ في الطبقة الفرعية. سلبيات هو أنه لا يوجد خطأ في البناء ، ولكن في وقت التشغيل. الايجابيات هي أنه يمكنك استدعاء هذه الطريقة من الطبقة الفائقة ، على افتراض أنها ستعمل :)

HTH!

ميلتون

1
Milton