webentwicklung-frage-antwort-db.com.de

Wie erstelle ich eine abstrakte Basisklasse in JavaScript?

Kann eine abstrakte Basisklasse in JavaScript simuliert werden? Was ist der eleganteste Weg, dies zu tun?

Sag mal, ich möchte so etwas machen: -

var cat = new Animal('cat');
var dog = new Animal('dog');

cat.say();
dog.say();

Es sollte ausgeben: 'bark', 'meow'

95
Sabya

Eine einfache Methode zum Erstellen einer abstrakten Klasse ist folgende:

/**
 @constructor
 @abstract
 */
var Animal = function() {
    if (this.constructor === Animal) {
      throw new Error("Can't instantiate abstract class!");
    }
    // Animal initialization...
};

/**
 @abstract
 */
Animal.prototype.say = function() {
    throw new Error("Abstract method!");
}

Die Animal "Klasse" und die say Methode sind abstrakt.

Das Erstellen einer Instanz würde einen Fehler auslösen: 

new Animal(); // throws

So erben Sie davon:

var Cat = function() {
    Animal.apply(this, arguments);
    // Cat initialization...
};
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.say = function() {
    console.log('meow');
}

Dog sieht genauso aus.

Und so entwickelt sich Ihr Szenario:

var cat = new Cat();
var dog = new Dog();

cat.say();
dog.say();

Geige hier (siehe Konsolenausgabe).

112
Jordão

Meinst du so etwas:

function Animal() {
  //Initialization for all Animals
}

//Function and properties shared by all instances of Animal
Animal.prototype.init=function(name){
  this.name=name;
}
Animal.prototype.say=function(){
    alert(this.name + " who is a " + this.type + " says " + this.whattosay);
}
Animal.prototype.type="unknown";

function Cat(name) {
    this.init(name);

    //Make a cat somewhat unique
    var s="";
    for (var i=Math.ceil(Math.random()*7); i>=0; --i) s+="e";
    this.whattosay="Me" + s +"ow";
}
//Function and properties shared by all instances of Cat    
Cat.prototype=new Animal();
Cat.prototype.type="cat";
Cat.prototype.whattosay="meow";


function Dog() {
    //Call init with same arguments as Dog was called with
    this.init.apply(this,arguments);
}

Dog.prototype=new Animal();
Dog.prototype.type="Dog";
Dog.prototype.whattosay="bark";
//Override say.
Dog.prototype.say = function() {
        this.openMouth();
        //Call the original with the exact same arguments
        Animal.prototype.say.apply(this,arguments);
        //or with other arguments
        //Animal.prototype.say.call(this,"some","other","arguments");
        this.closeMouth();
}

Dog.prototype.openMouth=function() {
   //Code
}
Dog.prototype.closeMouth=function() {
   //Code
}

var dog = new Dog("Fido");
var cat1 = new Cat("Dash");
var cat2 = new Cat("Dot");


dog.say(); // Fido the Dog says bark
cat1.say(); //Dash the Cat says M[e]+ow
cat2.say(); //Dot the Cat says M[e]+ow


alert(cat instanceof Cat) // True
alert(cat instanceof Dog) // False
alert(cat instanceof Animal) // True
25
some

JavaScript-Klassen und Vererbung (ES6)

Gemäß ES6 können Sie JavaScript-Klassen und Vererbung verwenden, um das zu erreichen, was Sie benötigen.

JavaScript-Klassen, die in ECMAScript 2015 eingeführt wurden, sind in erster Linie syntaktischer Zucker gegenüber der vorhandenen prototypbasierten Vererbung von JavaScript.

Referenz: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

Zunächst definieren wir unsere abstrakte Klasse. Diese Klasse kann nicht instanziiert werden, kann jedoch erweitert werden. Wir können auch Funktionen definieren, die in allen Klassen implementiert werden müssen, die diese Klasse erweitern.

/**
 * Abstract Class Animal.
 *
 * @class Animal
 */
class Animal {

  constructor() {
    if (this.constructor == Animal) {
      throw new Error("Abstract classes can't be instantiated.");
    }
  }

  say() {
    throw new Error("Method 'say()' must be implemented.");
  }

  eat() {
    console.log("eating");
  }
}

Danach können wir unsere konkreten Klassen erstellen. Diese Klassen erben alle Funktionen und Verhaltensweisen von der abstrakten Klasse.

/**
 * Dog.
 *
 * @class Dog
 * @extends {Animal}
 */
class Dog extends Animal {
  say() {
    console.log("bark");
  }
}

/**
 * Cat.
 *
 * @class Cat
 * @extends {Animal}
 */
class Cat extends Animal {
  say() {
    console.log("meow");
  }
}

/**
 * Horse.
 *
 * @class Horse
 * @extends {Animal}
 */
class Horse extends Animal {}

Und die Ergebnisse ...

// RESULTS

new Dog().eat(); // eating
new Cat().eat(); // eating
new Horse().eat(); // eating

new Dog().say(); // bark
new Cat().say(); // meow
new Horse().say(); // Error: Method say() must be implemented.

new Animal(); // Error: Abstract classes can't be instantiated.
14
Daniel Possamai

Vielleicht möchten Sie sich die Basisklasse von Dean Edwards ansehen: http://dean.edwards.name/weblog/2006/03/base/

Alternativ dazu gibt es dieses Beispiel/einen Artikel von Douglas Crockford zur klassischen Vererbung in JavaScript: http://www.crockford.com/javascript/inheritance.html

14
Ian Oxley
Animal = function () { throw "abstract class!" }
Animal.prototype.name = "This animal";
Animal.prototype.sound = "...";
Animal.prototype.say = function() {
    console.log( this.name + " says: " + this.sound );
}

Cat = function () {
    this.name = "Cat";
    this.sound = "meow";
}

Dog = function() {
    this.name = "Dog";
    this.sound  = "woof";
}

Cat.prototype = Object.create(Animal.prototype);
Dog.prototype = Object.create(Animal.prototype);

new Cat().say();    //Cat says: meow
new Dog().say();    //Dog says: woof 
new Animal().say(); //Uncaught abstract class! 
10
Leven

Kann eine abstrakte Basisklasse in JavaScript simuliert werden?

Bestimmt. Es gibt ungefähr tausend Möglichkeiten, Klassen-/Instanzsysteme in JavaScript zu implementieren. Hier ist eine:

// Classes magic. Define a new class with var C= Object.subclass(isabstract),
// add class members to C.prototype,
// provide optional C.prototype._init() method to initialise from constructor args,
// call base class methods using Base.prototype.call(this, ...).
//
Function.prototype.subclass= function(isabstract) {
    if (isabstract) {
        var c= new Function(
            'if (arguments[0]!==Function.prototype.subclass.FLAG) throw(\'Abstract class may not be constructed\'); '
        );
    } else {
        var c= new Function(
            'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+
            'if (arguments[0]!==Function.prototype.subclass.FLAG && this._init) this._init.apply(this, arguments); '
        );
    }
    if (this!==Object)
        c.prototype= new this(Function.prototype.subclass.FLAG);
    return c;
}
Function.prototype.subclass.FLAG= new Object();

var cat = neues Tier ("Katze");

Das ist natürlich keine abstrakte Basisklasse. Meinst du etwas wie:

var Animal= Object.subclass(true); // is abstract
Animal.prototype.say= function() {
    window.alert(this._noise);
};

// concrete classes
var Cat= Animal.subclass();
Cat.prototype._noise= 'meow';
var Dog= Animal.subclass();
Dog.prototype._noise= 'bark';

// usage
var mycat= new Cat();
mycat.say(); // meow!
var mygiraffe= new Animal(); // error!
10
bobince

Sie können abstrakte Klassen mithilfe von Objektprototypen erstellen. Ein einfaches Beispiel kann wie folgt aussehen: 

var SampleInterface = {
   addItem : function(item){}  
}

Sie können die obige Methode ändern oder nicht, es liegt an Ihnen, wann Sie sie implementieren. Für eine detaillierte Beobachtung können Sie hier besuchen.

5
yusufaytas

Frage ist ziemlich alt, aber ich habe eine mögliche Lösung geschaffen, wie man abstrakte Klassen erstellt und die Erstellung von Objekten dieses Typs blockiert.

//our Abstract class
var Animal=function(){
  
    this.name="Animal";
    this.fullname=this.name;
    
    //check if we have abstract paramater in prototype
    if (Object.getPrototypeOf(this).hasOwnProperty("abstract")){
    
    throw new Error("Can't instantiate abstract class!");
    
    
    }
    

};

//very important - Animal prototype has property abstract
Animal.prototype.abstract=true;

Animal.prototype.hello=function(){

   console.log("Hello from "+this.name);
};

Animal.prototype.fullHello=function(){

   console.log("Hello from "+this.fullname);
};

//first inheritans
var Cat=function(){

	  Animal.call(this);//run constructor of animal
    
    this.name="Cat";
    
    this.fullname=this.fullname+" - "+this.name;

};

Cat.prototype=Object.create(Animal.prototype);

//second inheritans
var Tiger=function(){

    Cat.call(this);//run constructor of animal
    
    this.name="Tiger";
    
    this.fullname=this.fullname+" - "+this.name;
    
};

Tiger.prototype=Object.create(Cat.prototype);

//cat can be used
console.log("WE CREATE CAT:");
var cat=new Cat();
cat.hello();
cat.fullHello();

//tiger can be used

console.log("WE CREATE TIGER:");
var tiger=new Tiger();
tiger.hello();
tiger.fullHello();


console.log("WE CREATE ANIMAL ( IT IS ABSTRACT ):");
//animal is abstract, cannot be used - see error in console
var animal=new Animal();
animal=animal.fullHello();

Wie Sie sehen können, hat das letzte Objekt einen Fehler ergeben, weil Animal im Prototyp die Eigenschaft abstract hat. Um sicher zu sein, dass Animal nicht etwas ist, das Animal.prototype in der Prototypkette hat, mache ich:

Object.getPrototypeOf(this).hasOwnProperty("abstract")

Ich überprüfe also, dass mein nächstgelegenes Prototypobjekt die abstract-Eigenschaft hat. Nur ein Objekt, das direkt aus dem Animal-Prototyp erstellt wurde, hat diese Bedingung auf true. Die Funktion hasOwnProperty prüft nur die Eigenschaften des aktuellen Objekts und nicht dessen Prototypen. Dies gibt uns 100% ige Sicherheit, dass die Eigenschaft hier nicht in der Prototypkette deklariert wird.

Jedes von Object abgeleitete Objekt erbt die Methode hasOwnProperty . Diese Methode kann verwendet werden, um festzustellen, ob ein Objekt die angegebene Eigenschaft als direkte Eigenschaft dieses Objekts hat. Im Gegensatz zum in-Operator überprüft diese Methode die Prototypkette des Objekts nicht. Mehr dazu:

In meinem Vorschlag müssen wir constructor nicht jedes Mal nach Object.create ändern, wie es in der aktuell besten Antwort von @ Jordão ist.

Die Lösung ermöglicht auch das Erstellen vieler abstrakter Klassen in Hierarchien. Wir müssen nur die abstract-Eigenschaft im Prototyp erstellen.

5
Maciej Sikora
function Animal(type) {
    if (type == "cat") {
        this.__proto__ = Cat.prototype;
    } else if (type == "dog") {
        this.__proto__ = Dog.prototype;
    } else if (type == "fish") {
        this.__proto__ = Fish.prototype;
    }
}
Animal.prototype.say = function() {
    alert("This animal can't speak!");
}

function Cat() {
    // init cat
}
Cat.prototype = new Animal();
Cat.prototype.say = function() {
    alert("Meow!");
}

function Dog() {
    // init dog
}
Dog.prototype = new Animal();
Dog.prototype.say = function() {
    alert("Bark!");
}

function Fish() {
    // init fish
}
Fish.prototype = new Animal();

var newAnimal = new Animal("dog");
newAnimal.say();

Dies kann nicht garantiert werden, da __proto__ keine Standardvariable ist, aber es funktioniert zumindest in Firefox und Safari.

Wenn Sie nicht verstehen, wie es funktioniert, lesen Sie die Prototypkette.

4
Georg Schölly

Javascript kann Vererbung haben, überprüfen Sie die URL unten:

http://www.webreference.com/js/column79/

Andrew

3
REA_ANDREW

Eine andere Sache, die Sie erzwingen möchten, ist sicherzustellen, dass Ihre abstrakte Klasse nicht instanziiert wird. Sie können dies tun, indem Sie eine Funktion definieren, die als FLAG-Funktion fungiert und als Konstruktor der abstrakten Klasse definiert wird. Dann wird versucht, die FLAG zu erstellen, die ihren Konstruktor mit der Ausnahme zum Auslösen aufruft. Beispiel unten:

(function(){

var FLAG_ABSTRACT = function(__class){

    throw "Error: Trying to instantiate an abstract class:"+__class
}

var Class = function (){

    Class.prototype.constructor = new FLAG_ABSTRACT("Class");       
}

    //will throw exception
var  foo = new Class();

}) ()

3
thecommoner

In diesem Fall können wir Factory Design Pattern verwenden. Javascript verwenden Sie prototype, um die Mitglieder des Elternteils zu erben.

Definieren Sie den Konstruktor der übergeordneten Klasse.

var Animal = function() {
  this.type = 'animal';
  return this;
}
Animal.prototype.tired = function() {
  console.log('sleeping: zzzZZZ ~');
}

Und dann schaffe Kinderunterricht.

// These are the child classes
Animal.cat = function() {
  this.type = 'cat';
  this.says = function() {
    console.log('says: meow');
  }
}

Definieren Sie dann den Klassenkonstruktor für Kinder.

// Define the child class constructor -- Factory Design Pattern.
Animal.born = function(type) {
  // Inherit all members and methods from parent class,
  // and also keep its own members.
  Animal[type].prototype = new Animal();
  // Square bracket notation can deal with variable object.
  creature = new Animal[type]();
  return creature;
}

Probier es aus.

var timmy = Animal.born('cat');
console.log(timmy.type) // cat
timmy.says(); // meow
timmy.tired(); // zzzZZZ~

Hier ist der Codepen-Link für die vollständige Beispielcodierung.

2
Eric Tan
//Your Abstract class Animal
function Animal(type) {
    this.say = type.say;
}

function catClass() {
    this.say = function () {
        console.log("I am a cat!")
    }
}
function dogClass() {
    this.say = function () {
        console.log("I am a dog!")
    }
}
var cat = new Animal(new catClass());
var dog = new Animal(new dogClass());

cat.say(); //I am a cat!
dog.say(); //I am a dog!
1
Paul Orazulike
/****************************************/
/* version 1                            */
/****************************************/

var Animal = function(params) {
    this.say = function()
    {
        console.log(params);
    }
};
var Cat = function() {
    Animal.call(this, "moes");
};

var Dog = function() {
    Animal.call(this, "vewa");
};


var cat = new Cat();
var dog = new Dog();

cat.say();
dog.say();


/****************************************/
/* version 2                            */
/****************************************/

var Cat = function(params) {
    this.say = function()
    {
        console.log(params);
    }
};

var Dog = function(params) {
    this.say = function()
    {
        console.log(params);
    }
};

var Animal = function(type) {
    var obj;

    var factory = function()
    {
        switch(type)
        {
            case "cat":
                obj = new Cat("bark");
                break;
            case "dog":
                obj = new Dog("meow");
                break;
        }
    }

    var init = function()
    {
        factory();
        return obj;
    }

    return init();
};


var cat = new Animal('cat');
var dog = new Animal('dog');

cat.say();
dog.say();
0
Tamas Romeo

Wenn Sie sicherstellen möchten, dass Ihre Basisklassen und ihre Mitglieder streng abstrakt sind, können Sie dies mit einer Basisklasse tun:

class AbstractBase{
    constructor(){}
    checkConstructor(c){
        if(this.constructor!=c) return;
        throw new Error(`Abstract class ${this.constructor.name} cannot be instantiated`);
    }
    throwAbstract(){
        throw new Error(`${this.constructor.name} must implement abstract member`);}    
}

class FooBase extends AbstractBase{
    constructor(){
        super();
        this.checkConstructor(FooBase)}
    doStuff(){this.throwAbstract();}
    doOtherStuff(){this.throwAbstract();}
}

class FooBar extends FooBase{
    constructor(){
        super();}
    doOtherStuff(){/*some code here*/;}
}

var fooBase = new FooBase(); //<- Error: Abstract class FooBase cannot be instantiated
var fooBar = new FooBar(); //<- OK
fooBar.doStuff(); //<- Error: FooBar must implement abstract member
fooBar.doOtherStuff(); //<- OK

Der strikte Modus macht es unmöglich, den Aufrufer in der throwAbstract-Methode zu protokollieren. Der Fehler sollte jedoch in einer Debug-Umgebung auftreten, in der der Stack-Trace angezeigt wird.

0
pasx

Ich denke, All That Antworten speziell die ersten beiden (von einige und jordão ) beantworten die Frage klar mit einem herkömmlichen Prototyp-Basis-JS-Konzept.
Nun, da Sie möchten, dass sich der Konstruktor der Tierklasse entsprechend dem übergebenen Parameter der Konstruktion verhält, ist dies meiner Meinung nach dem grundlegenden Verhalten von Creational Patterns zum Beispiel Factory Pattern sehr ähnlich. 

Hier habe ich einen kleinen Ansatz gemacht, damit es so funktioniert.

var Animal = function(type) {
    this.type=type;
    if(type=='dog')
    {
        return new Dog();
    }
    else if(type=="cat")
    {
        return new Cat();
    }
};



Animal.prototype.whoAreYou=function()
{
    console.log("I am a "+this.type);
}

Animal.prototype.say = function(){
    console.log("Not implemented");
};




var Cat =function () {
    Animal.call(this);
    this.type="cat";
};

Cat.prototype=Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.say=function()
{
    console.log("meow");
}



var Dog =function () {
    Animal.call(this);
    this.type="dog";
};

Dog.prototype=Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.say=function()
{
    console.log("bark");
}


var animal=new Animal();


var dog = new Animal('dog');
var cat=new Animal('cat');

animal.whoAreYou(); //I am a undefined
animal.say(); //Not implemented


dog.whoAreYou(); //I am a dog
dog.say(); //bark

cat.whoAreYou(); //I am a cat
cat.say(); //meow
0
Saif