Comment créer une classe de base abstraite en JavaScript?

Réponses:

127

Une façon simple de créer une classe abstraite est la suivante:

/**
 @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" et la sayméthode sont abstraites.

La création d'une instance générerait une erreur:

new Animal(); // throws

Voici comment vous en «héritez»:

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 lui ressemble.

Et voici comment se déroule votre scénario:

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

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

Fiddle ici (regardez la sortie de la console).

Jordão
la source
pouvez-vous s'il vous plaît expliquer ligne par ligne, si cela ne vous dérange pas, car je suis nouveau dans la POO. Merci!
React Developer
2
@undefined: pour le comprendre , je vous suggère de regarder en héritage prototypique en Javascript: c'est un bon guide.
Jordão
Merci pour la réponse .. passera par le lien.
React Developer
L'élément le plus important de ceci est que dans le tout premier extrait de code, une erreur est générée. Vous pouvez également lancer un avertissement ou renvoyer une valeur nulle au lieu d'un objet afin de continuer l'exécution de l'application, cela dépend vraiment de votre implémentation. C'est, à mon avis, la manière correcte d'implémenter des "classes" JS abstraites.
dudewad
1
@ G1P: c'est la façon habituelle d'exécuter un "constructeur de super-classe" en Javascript, et cela doit être fait comme ça, manuellement.
Jordão
46

Classes JavaScript et héritage (ES6)

Selon ES6, vous pouvez utiliser les classes JavaScript et l'héritage pour accomplir ce dont vous avez besoin.

Les classes JavaScript, introduites dans ECMAScript 2015, sont principalement du sucre syntaxique par rapport à l'héritage basé sur prototype existant de JavaScript.

Référence: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

Tout d'abord, nous définissons notre classe abstraite. Cette classe ne peut pas être instanciée, mais peut être étendue. Nous pouvons également définir des fonctions qui doivent être implémentées dans toutes les classes qui étendent celle-ci.

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

Après cela, nous pouvons créer nos classes concrètes. Ces classes hériteront de toutes les fonctions et comportements de la classe abstraite.

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

Et les résultats ...

// 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
la source
27

Voulez-vous dire quelque chose comme ça:

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
certains
la source
Peut-être que je l'ai manqué. Où est le résumé de la classe de base (Animal)?
HairOfTheDog
5
@HairOfTheDog Oui, vous avez manqué que cela a été répondu il y a environ cinq ans, que javascript à cette époque n'avait pas de classes abstraites, que la question était de trouver un moyen de le simuler ( whattosayn'est pas défini dans l' animal ), et que cette réponse demande clairement si la réponse proposée correspondait à ce que le questionneur recherchait. Il ne prétend pas donner une solution pour les classes abstraites en javascript. Le questionneur n'a pas pris la peine de répondre à moi ou à quelqu'un d'autre, donc je ne sais pas si cela a fonctionné pour lui. Je suis désolé si une réponse proposée par un enfant de cinq ans à une autre question n'a pas fonctionné pour vous.
certains
15

Vous voudrez peut-être consulter la classe de base de Dean Edwards: http://dean.edwards.name/weblog/2006/03/base/

Alternativement, il y a cet exemple / article de Douglas Crockford sur l'héritage classique en JavaScript: http://www.crockford.com/javascript/inheritance.html

Ian Oxley
la source
13
En ce qui concerne le lien Crockford, c'est un tas de déchets. Il a ajouté une note à la fin de cet article: "Je considère maintenant mes premières tentatives de support du modèle classique en JavaScript comme une erreur."
Matt Ball
11

Est-il possible de simuler une classe de base abstraite en JavaScript?

Certainement. Il existe environ mille façons d'implémenter des systèmes de classes / instances en JavaScript. En voici une:

// 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 = nouvel animal ('chat');

Ce n'est pas vraiment une classe de base abstraite bien sûr. Voulez-vous dire quelque chose comme:

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
la source
Pourquoi utiliser la nouvelle construction maléfique de Function (...)? Ne serait pas var c = function () {...}; être meilleur?
fionbio
2
«Var c = function () {...}» créerait une fermeture sur tout ce qui se trouve dans la sous-classe () ou toute autre portée contenant. Probablement pas important, mais je voulais le garder propre des portées parent potentiellement indésirables; le constructeur Function () pur-text évite les fermetures.
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
la source
le constructeur d'une sous-classe peut-il invoquer le constructeur de la superclasse? (comme appeler super dans une langue ... si c'est le cas, alors cela soulèvera inconditionnellement une exception
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();

Il n'est pas garanti que cela fonctionne comme __proto__ n'est pas une variable standard, mais cela fonctionne au moins dans Firefox et Safari.

Si vous ne comprenez pas comment cela fonctionne, lisez la chaîne de prototypes.

Georg Schölly
la source
proto fonctionne AFAIK uniquement dans FF et Chome (ni IE ni Opera ne le supportent. Je n'ai pas testé dans Safari). BTW, vous vous trompez: la classe de base (animal) doit être modifiée chaque fois qu'un nouveau type d'animal est souhaité.
certains
Safari et Chrome utilisent tous les deux le même moteur JavaScript. Je n'étais pas vraiment sûr qu'il voulait seulement savoir comment fonctionne l'héritage, j'ai donc essayé de suivre son exemple au plus près.
Georg Schölly
4
Safari et Chrome n'utilisent pas le même moteur JavaScript, Safari utilise JavaScriptCore et Chrome utilise V8 . La chose que les deux navigateurs partagent est le moteur de mise en page, WebKit .
CMS
@ GeorgSchölly Veuillez envisager de modifier votre réponse pour utiliser la nouvelle construction Object.getPrototypeOf
Benjamin Gruenbaum
@Benjamin: Selon Mozilla, il n'y a pas setPrototypeOfencore de méthode dont j'aurais besoin pour mon code.
Georg Schölly
5

Vous pouvez créer des classes abstraites à l'aide de prototypes d'objets, un exemple simple peut être le suivant:

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

Vous pouvez changer la méthode ci-dessus ou non, c'est à vous de la mettre en œuvre. Pour une observation détaillée, vous pouvez visiter ici .

yusufaytas
la source
5

La question est assez ancienne, mais j'ai créé une solution possible pour créer une "classe" abstraite et bloquer la création d'objets de ce type.

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

Comme vous pouvez le voir, le dernier objet nous donne une erreur, c'est parce que Animal dans le prototype a une propriété abstract. Pour être sûr que c'est Animal, pas quelque chose qui a Animal.prototypedans la chaîne de prototypes que je fais:

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

Je vérifie donc que mon objet prototype le plus proche a la abstractpropriété, seul l'objet créé directement à partir du Animalprototype aura cette condition sur true. La fonction hasOwnPropertyne vérifie que les propriétés de l'objet actuel et non ses prototypes, ce qui nous donne la certitude à 100% que la propriété est déclarée ici et non dans la chaîne de prototypes.

Chaque objet descendant d'Object hérite de la méthode hasOwnProperty . Cette méthode peut être utilisée pour déterminer si un objet a la propriété spécifiée en tant que propriété directe de cet objet; contrairement à l'opérateur in, cette méthode ne vérifie pas la chaîne de prototypes de l'objet. En savoir plus:

Dans ma proposition, nous n'avons pas à changer constructor chaque fois après, Object.createcomme c'est le cas dans la meilleure réponse actuelle de @ Jordão.

La solution permet également de créer de nombreuses classes abstraites dans la hiérarchie, il suffit de créer une abstractpropriété dans le prototype.

Maciej Sikora
la source
4

Une autre chose que vous voudrez peut-être appliquer est de vous assurer que votre classe abstraite n'est pas instanciée. Vous pouvez le faire en définissant une fonction qui agit comme des FLAG définies comme constructeur de classe abstraite. Cela tentera ensuite de construire le FLAG qui appellera son constructeur contenant l'exception à lever. Exemple ci-dessous:

(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
la source
2

Nous pouvons utiliser Factoryun modèle de conception dans ce cas. Javascript utilise prototypepour hériter des membres du parent.

Définissez le constructeur de la classe parent.

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

Et puis créez une classe pour enfants.

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

Définissez ensuite le constructeur de la classe enfants.

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

Essaye-le.

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

Voici le lien Codepen pour l'exemple de codage complet.

Eric Tan
la source
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
la source
C'est du polymorphisme en JavaScript, vous pouvez faire quelques vérifications pour implémenter le remplacement.
Paul Orazulike
0

Je pense que toutes ces réponses spécialement les deux premières (par certains et par jordão ) répondent clairement à la question avec le concept JS de base prototype conventionnel.
Maintenant que vous voulez que le constructeur de la classe animale se comporte en fonction du paramètre passé à la construction, je pense que c'est très similaire au comportement de base de, Creational Patternspar exemple, Factory Pattern .

Ici, j'ai fait une petite approche pour que cela fonctionne de cette façon.

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
la source
Lien vers ceci: programmers.stackexchange.com/questions/219543/... Ce Animalconstructeur peut être vu comme un anti-pattern, une superclasse ne devrait pas avoir de connaissances sur les sous-classes. (viole Liskov et le principe Open / Close)
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
la source
À mon avis, c'est la manière la plus élégante d'obtenir de bons résultats avec moins de code.
Tamas Romeo
1
Veuillez expliquer pourquoi cela est utile. Distribuer du code n'est pas aussi utile que d'expliquer pourquoi le code est utile. C'est la différence entre donner un poisson à quelqu'un ou lui apprendre à pêcher.
the Tin Man
Beaucoup n'utilisent pas dans leurs techniques de programmation javascript des prototypes ou des constructeurs. Même s'ils sont utiles dans de nombreuses situations. Pour ceux-là, je considère que le code est utile. Pas parce que le code est meilleur que les autres ... mais parce que c'est plus facile à comprendre
Tamas Romeo
0

Si vous voulez vous assurer que vos classes de base et leurs membres sont strictement abstraits, voici une classe de base qui le fait pour vous:

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

Le mode strict rend impossible la journalisation de l'appelant dans la méthode throwAbstract, mais l'erreur devrait se produire dans un environnement de débogage qui afficherait la trace de la pile.

pasx
la source