Come creo una classe base astratta in JavaScript?


Risposte:


127

Un modo semplice per creare una classe astratta è questo:

/**
 @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!");
}

La Animal"classe" e il saymetodo sono astratti.

La creazione di un'istanza genererebbe un errore:

new Animal(); // throws

Ecco come "erediti" da esso:

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 sembra proprio così.

Ed è così che si svolge il tuo scenario:

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

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

Gioca qui (guarda l'output della console).


puoi spiegare riga per riga, se non ti dispiace, dato che sono nuovo agli OOP. Grazie!
React Developer

2
@undefined: per capirlo ti consiglio di cercare l'ereditarietà prototipica in Javascript: questa è una buona guida.
Jordão

Grazie per la risposta .. passerò attraverso il link.
React Developer

La parte più importante di questo è che nel primo snippet di codice viene generato un errore. Potresti anche lanciare un avviso o restituire un valore null invece di un oggetto in modo da continuare l'esecuzione dell'applicazione, dipende davvero dalla tua implementazione. Questo, a mio parere, è il modo corretto per implementare "classi" JS astratte.
dudewad

1
@ G1P: questo è il solito modo per eseguire un "costruttore di super classi" in Javascript, e deve essere fatto così, manualmente.
Jordão

46

Classi JavaScript ed ereditarietà (ES6)

Secondo ES6, puoi utilizzare le classi e l'ereditarietà JavaScript per realizzare ciò di cui hai bisogno.

Le classi JavaScript, introdotte in ECMAScript 2015, sono principalmente zucchero sintattico sull'eredità esistente basata su prototipi di JavaScript.

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

Prima di tutto, definiamo la nostra classe astratta. Questa classe non può essere istanziata, ma può essere estesa. Possiamo anche definire funzioni che devono essere implementate in tutte le classi che estendono questa.

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

Dopodiché, possiamo creare le nostre classi concrete. Queste classi erediteranno tutte le funzioni e il comportamento dalla classe astratta.

/**
 * 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 {}

E i risultati ...

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

27

Intendi qualcosa come questo:

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

Forse l'ho perso. Dov'è l'abstract della classe base (Animale)?
HairOfTheDog

5
@HairOfTheDog Sì, ti sei perso che questa è stata risposta circa cinque anni fa, che javascript in quel momento non aveva classi astratte, che la domanda era per un modo per simularlo ( whattosaynon è definito in animale ) e che questa risposta chiede chiaramente se la risposta proposta era ciò che l'interrogante stava cercando. Non pretende di fornire una soluzione per le classi astratte in javascript. L'interrogante non si è preoccupato di rispondere a me oa chiunque altro, quindi non ho idea se ha funzionato per lui. Mi dispiace se una risposta proposta da un bambino di cinque anni alla domanda di qualcun altro non ha funzionato per te.
alcuni il

15

Potresti voler controllare la classe base di Dean Edwards: http://dean.edwards.name/weblog/2006/03/base/

In alternativa, c'è questo esempio / articolo di Douglas Crockford sull'ereditarietà classica in JavaScript: http://www.crockford.com/javascript/inheritance.html


13
Per quanto riguarda il collegamento Crockford, è un carico di spazzatura. Ha aggiunto una nota alla fine dell'articolo: "Ora vedo i miei primi tentativi di supportare il modello classico in JavaScript come un errore".
Matt Ball

11

È possibile simulare una classe base astratta in JavaScript?

Certamente. Esistono circa mille modi per implementare sistemi di classi / istanze in JavaScript. Eccone uno:

// 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 = new Animal ('cat');

Ovviamente non è proprio una classe base astratta. Intendi qualcosa come:

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!

Perché usare il malvagio nuovo costrutto Function (...)? Non var c = function () {...}; essere migliore?
fionbio

2
"Var c = function () {...}" creerebbe una chiusura su qualsiasi cosa nella sottoclasse () o in un altro ambito contenitore. Probabilmente non è importante, ma volevo tenerlo pulito da ambiti genitore potenzialmente indesiderati; il costruttore Function () di testo puro evita le chiusure.
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! 

può il costruttore di una sottoclasse invocare il costruttore della superclasse? (come chiamare super in qualche lingua ... se è così, allora solleverà incondizionatamente un'eccezione
nonopolarità

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();

Non è garantito che funzioni in quanto __proto__non è una variabile standard, ma funziona almeno in Firefox e Safari.

Se non capisci come funziona, leggi la catena di prototipi.


proto funziona AFAIK solo in FF e Chome (né IE né Opera lo supportano. Non l'ho provato in Safari). A proposito, stai sbagliando: la classe base (animale) dovrebbe essere modificata ogni volta che si desidera un nuovo tipo di animale.
alcuni il

Safari e Chrome utilizzano entrambi lo stesso motore javascript. Non ero proprio sicuro che volesse solo sapere come funziona l'eredità, quindi ho cercato di seguire il suo esempio il più fedelmente possibile.
Georg Schölly,

4
Safari e Chrome non utilizzano lo stesso motore JavaScript, Safari utilizza JavaScriptCore e Chrome utilizza V8 . La cosa che condividono entrambi i browser è il Layout Engine, WebKit .
CMS

@ GeorgSchölly Per favore considera la possibilità di modificare la tua risposta per usare il nuovo costrutto Object.getPrototypeOf
Benjamin Gruenbaum

@Benjamin: Secondo Mozilla non esiste setPrototypeOfancora un metodo di cui avrei bisogno per il mio codice.
Georg Schölly

5

È possibile creare classi astratte utilizzando prototipi di oggetti, un semplice esempio può essere il seguente:

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

Puoi cambiare il metodo sopra o no, dipende da te quando lo implementerai. Per un'osservazione dettagliata, potresti visitare qui .


5

La domanda è piuttosto vecchia, ma ho creato qualche possibile soluzione su come creare una "classe" astratta e bloccare la creazione di oggetti di quel tipo.

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

Come puoi vedere l'ultimo oggetto ci dà un errore, è perché Animal in prototype ha proprietà abstract. Per essere sicuri che sia Animal non qualcosa che ha Animal.prototypenella catena di prototipi che faccio:

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

Quindi controllo che il mio oggetto prototipo più vicino abbia abstractproprietà, solo l'oggetto creato direttamente dal Animalprototipo avrà questa condizione su true. La funzione hasOwnPropertycontrolla solo le proprietà dell'oggetto corrente, non i suoi prototipi, quindi questo ci dà la certezza al 100% che la proprietà sia dichiarata qui non nella catena dei prototipi.

Ogni oggetto discendente da Object eredita il metodo hasOwnProperty . Questo metodo può essere utilizzato per determinare se un oggetto ha la proprietà specificata come proprietà diretta di quell'oggetto; a differenza dell'operatore in, questo metodo non controlla la catena di prototipi dell'oggetto. Di più su di esso:

Nella mia proposta non dobbiamo cambiare constructorogni volta Object.createcome è nella migliore risposta attuale di @ Jordão.

La soluzione consente anche di creare molte classi astratte nella gerarchia, abbiamo solo bisogno di creare abstractproprietà nel prototipo.


4

Un'altra cosa che potresti voler applicare è assicurarti che la tua classe astratta non sia istanziata. Puoi farlo definendo una funzione che funge da FLAG impostata come costruttore della classe Abstract. Questo proverà quindi a costruire il FLAG che chiamerà il suo costruttore contenente l'eccezione da lanciare. Esempio sotto:

(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();

})()


2

In Factoryquesto caso possiamo usare il design pattern. Javascript utilizza prototypeper ereditare i membri del genitore.

Definisci il costruttore della classe genitore.

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

E poi crea una classe per bambini.

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

Quindi definire il costruttore della classe figlio.

// 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;
}

Provalo.

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

Ecco il collegamento Codepen per la codifica di esempio completa.


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!

Questo è polimorfismo in JavaScript, puoi fare qualche controllo per implementare l'override.
Paul Orazulike

0

Penso che tutte quelle risposte specialmente le prime due (da parte di alcuni e jordão ) rispondano chiaramente alla domanda con il concetto JS di base del prototipo convenzionale.
Ora, poiché vuoi che il costruttore della classe animale si comporti in base al parametro passato alla costruzione, penso che questo sia molto simile al comportamento di base, Creational Patternsad esempio, di Factory Pattern .

Qui ho fatto un piccolo approccio per farlo funzionare in questo modo.

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

Collegamento a questo: programmers.stackexchange.com/questions/219543/… Questo Animalcostruttore può essere visto come un anti-pattern, una superclasse non dovrebbe avere conoscenza delle sottoclassi. (viola il principio di Liskov e Apri / Chiudi)
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();

Dal mio punto di vista, questo è un modo più elegante per ottenere buoni risultati con meno codice.
Tamas Romeo

1
Spiega perché è utile. Distribuire il codice non è utile quanto spiegare perché il codice è utile. È la differenza tra dare un pesce a qualcuno o insegnargli a pescare.
Tin Man

Molti non usano nelle loro tecniche di programmazione javascript prototipi o costruttori. Anche se sono utili in molte situazioni. Per quelli, ritengo che il codice sia utile. Non perché il codice sia migliore di altri .. ma perché è più facile da capire
Tamas Romeo

0

Se vuoi assicurarti che le tue classi base ei loro membri siano strettamente astratti, ecco una classe base che fa questo per te:

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

La modalità Strict rende impossibile registrare il chiamante nel metodo throwAbstract ma l'errore dovrebbe verificarsi in un ambiente di debug che mostrerebbe la traccia dello stack.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.