Wie erstelle ich eine abstrakte Basisklasse in JavaScript?

Antworten:

127

Eine einfache Möglichkeit, eine abstrakte Klasse zu erstellen, 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 sayMethode 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 spielt sich Ihr Szenario ab:

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

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

Fiddle hier (Blick auf die Konsolenausgabe).

Jordão
quelle
Kannst du bitte Zeile für Zeile erklären, wenn es dir nichts ausmacht, da ich neu bei OOPs bin? Vielen Dank!
Reagieren Entwickler
2
@undefined: Um es zu verstehen, schlage ich vor, dass Sie die prototypische Vererbung in Javascript nachschlagen: Dies ist eine gute Anleitung.
Jordão
Danke für die Antwort .. werde über den Link gehen.
Reagieren Entwickler
Das Wichtigste dabei ist, dass im allerersten Code-Snippet ein Fehler ausgelöst wird. Sie können auch eine Warnung auslösen oder anstelle eines Objekts einen Nullwert zurückgeben, um die Anwendungsausführung fortzusetzen. Dies hängt wirklich von Ihrer Implementierung ab. Dies ist meiner Meinung nach der richtige Weg, um abstrakte JS- "Klassen" zu implementieren.
Dudewad
1
@ G1P: Das ist die übliche Methode, um einen "Super-Class-Konstruktor" in Javascript auszuführen, und dies muss manuell erfolgen.
Jordão
46

JavaScript-Klassen und Vererbung (ES6)

Laut 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, sondern erweitert werden. Wir können auch Funktionen definieren, die in allen Klassen implementiert werden müssen, die diese 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.
Daniel Possamai
quelle
27

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
etwas
quelle
Vielleicht habe ich es verpasst. Wo ist die Basisklasse (Tier) abstrakt?
HairOfTheDog
5
@HairOfTheDog Ja, Sie haben übersehen, dass dies vor ungefähr fünf Jahren beantwortet wurde, dass Javascript zu dieser Zeit keine abstrakten Klassen hatte, dass die Frage eine Möglichkeit war, es zu simulieren ( whattosayist nicht im Tier definiert ) und dass diese Antwort eindeutig fragt ob die vorgeschlagene Antwort das war, wonach der Fragesteller suchte. Es wird nicht behauptet, eine Lösung für abstrakte Klassen in Javascript anzugeben. Der Fragesteller hat sich nicht die Mühe gemacht, mir oder jemand anderem zu antworten, daher habe ich keine Ahnung, ob es für ihn funktioniert hat. Es tut mir leid, wenn eine fünf Jahre alte vorgeschlagene Antwort auf eine andere Frage für Sie nicht funktioniert hat.
einige
15

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

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

Ian Oxley
quelle
13
In Bezug auf die Crockford-Verbindung ist es eine Menge Müll. Er hat am Ende dieses Artikels eine Notiz hinzugefügt: "Ich sehe meine frühen Versuche, das klassische Modell in JavaScript zu unterstützen, jetzt als Fehler."
Matt Ball
11

Ist es möglich, abstrakte Basisklassen in JavaScript zu simulieren?

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 so 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!
Bobince
quelle
Warum das böse neue Funktionskonstrukt (...) verwenden? Würde nicht var c = function () {...}; sei besser?
Fionbio
2
"Var c = function () {...}" würde einen Abschluss über alles in Unterklasse () oder einem anderen enthaltenden Bereich erzeugen. Wahrscheinlich nicht wichtig, aber ich wollte es frei von potenziell unerwünschten übergeordneten Bereichen halten. Der Reintext-Konstruktor Function () vermeidet Schließungen.
Bobince
10
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! 
Leven
quelle
Kann der Konstruktor einer Unterklasse den Konstruktor der Oberklasse aufrufen? (wie Super in einer Sprache anrufen ... wenn ja, dann wird es bedingungslos eine Ausnahme
auslösen
5
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 funktioniert garantiert nicht als __proto__ nicht als Standardvariable, funktioniert jedoch zumindest in Firefox und Safari.

Wenn Sie nicht verstehen, wie es funktioniert, lesen Sie mehr über die Prototypenkette.

Georg Schölly
quelle
Proto- Arbeit AFAIK nur in FF und Chome (weder IE noch Opera unterstützen es. Ich habe nicht in Safari getestet). Übrigens, Sie machen es falsch: Die Basisklasse (Tier) sollte jedes Mal bearbeitet werden, wenn ein neuer Tiertyp gewünscht wird.
einige
Safari und Chrome verwenden beide dieselbe Javascript-Engine. Ich war mir nicht sicher, ob er nur wissen wollte, wie Vererbung funktioniert, deshalb habe ich versucht, seinem Beispiel so genau wie möglich zu folgen.
Georg Schölly
4
Safari und Chrome verwenden nicht dieselbe JavaScript-Engine, Safari verwendet JavaScriptCore und Chrome verwendet V8 . Beide Browser teilen sich die Layout Engine WebKit .
CMS
@ GeorgSchölly Bitte überlegen Sie, Ihre Antwort zu bearbeiten, um das neue Object.getPrototypeOf-Konstrukt zu verwenden
Benjamin Gruenbaum
@Benjamin: Laut Mozilla gibt es noch keine setPrototypeOfMethode, die ich für meinen Code benötigen würde.
Georg Schölly
5

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

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 .

Yusufaytas
quelle
5

Die Frage ist ziemlich alt, aber ich habe eine mögliche Lösung erstellt, wie eine abstrakte "Klasse" erstellt und die Erstellung eines Objekts dieses Typs blockiert werden kann.

//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, gibt uns das letzte Objekt einen Fehler, weil das Tier im Prototyp die Eigenschaft hat abstract. Um sicher zu sein, dass es nicht Animal ist, was Animal.prototypeich in der Prototypenkette habe:

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

Ich überprüfe also, ob mein nächstgelegenes Prototypobjekt eine abstractEigenschaft hat. Nur Objekte, die direkt aus dem AnimalPrototyp erstellt wurden, haben diese Bedingung auf true. Die Funktion hasOwnPropertyüberprüft nur die Eigenschaften des aktuellen Objekts, nicht seine Prototypen. Dies gibt uns 100% ige Sicherheit, dass die Eigenschaft hier nicht in der Prototypenkette deklariert ist.

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

In meinem Vorschlag müssen wir uns nicht constructorjedes Mal ändern, Object.createwie es in der derzeit besten Antwort von @ Jordão ist.

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

Maciej Sikora
quelle
4

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, die als Konstruktor für die abstrakte Klasse festgelegt ist. Dies wird dann versuchen, die FLAG zu konstruieren, die ihren Konstruktor mit der auszulösenden Ausnahme 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();

})()
thecommoner
quelle
2

FactoryIn diesem Fall können wir ein Entwurfsmuster verwenden. Javascript wird verwendet prototype, um die Mitglieder der Eltern zu erben.

Definieren Sie den übergeordneten Klassenkonstruktor.

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

Und dann Kinderklasse erstellen.

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

Definieren Sie dann den untergeordneten Klassenkonstruktor.

// 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.

Eric Tan
quelle
1
//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!
Paul Orazulike
quelle
Dies ist Polymorphismus in JavaScript. Sie können einige Überprüfungen durchführen, um die Überschreibung zu implementieren.
Paul Orazulike
0

Ich denke, all diese Antworten, insbesondere die ersten beiden (von einigen und jordão ), beantworten die Frage klar mit dem konventionellen Prototyp-Basis-JS-Konzept.
Da Sie nun möchten, dass sich der Tierklassenkonstruktor gemäß dem an die Konstruktion übergebenen Parameter verhält, ist dies meiner Meinung nach dem Grundverhalten von Creational Patternsbeispielsweise 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
Saif
quelle
Link dazu: programmers.stackexchange.com/questions/219543/… Dieser AnimalKonstruktor kann als Anti-Pattern angesehen werden. Eine Superklasse sollte keine Kenntnisse über Unterklassen haben. (verstößt gegen Liskov und Open / Close-Prinzip)
SirLenz0rlot
0
/****************************************/
/* 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();
Tamas Romeo
quelle
Aus meiner Sicht ist dies eine äußerst elegante Methode, um mit weniger Code gute Ergebnisse zu erzielen.
Tamas Romeo
1
Bitte erklären Sie, warum dies nützlich ist. Das Verteilen von Code ist bei weitem nicht so nützlich wie das Erklären, warum der Code nützlich ist. Es ist der Unterschied, ob man jemandem einen Fisch gibt oder ihm das Fischen beibringt.
der Blechmann
Viele verwenden in ihren Javascript-Programmiertechniken keine Prototypen oder Konstruktoren. Auch wenn sie in vielen Situationen nützlich sind. Für diese halte ich Code für nützlich. Nicht weil Code besser ist als andere ... sondern weil es leichter zu verstehen ist
Tamas Romeo
0

Wenn Sie sicherstellen möchten, dass Ihre Basisklassen und ihre Mitglieder streng abstrakt sind, finden Sie hier eine Basisklasse, die dies für Sie erledigt:

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, aber der Fehler sollte in einer Debug-Umgebung auftreten, in der die Stapelverfolgung angezeigt wird.

pasx
quelle