web-dev-qa-db-fra.com

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

Est-il possible de simuler une classe de base abstraite en JavaScript? Quelle est la manière la plus élégante de le faire?

Dis, je veux faire quelque chose comme: -

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

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

Il devrait produire: 'écorce', 'miaou'

95
Sabya

Voici un moyen simple de créer une classe abstraite:

/**
 @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 "classe" Animal et la méthode say sont abstraites.

Créer 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 y ressemble.

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

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

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

Violon ici (regardez la sortie de la console).

112
Jordão

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
25
some

Classes JavaScript et héritage (ES6)

Selon ES6, vous pouvez utiliser les classes et l'héritage JavaScript pour répondre à vos besoins.

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

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 étend 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.
14
Daniel Possamai

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

Vous pouvez également consulter cet exemple/article de Douglas Crockford sur l’héritage classique en 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

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 classe/instance en JavaScript. En voici un:

// 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 un cours de base abstrait 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!
10
bobince

Vous pouvez créer des classes abstraites à l’aide de prototypes d’objets. Voici un exemple simple: 

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

Vous pouvez changer la méthode ci-dessus ou non, c'est vous qui décidez. Pour une observation détaillée, vous pouvez visiter ici .

5
yusufaytas

La question est assez ancienne, mais j'ai créé une solution possible pour créer une "classe" abstraite et bloquer la création d'objet 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 voir le dernier objet nous donner une erreur, c'est parce que Animal dans le prototype a la propriété abstract. Pour être sûr que c'est Animal et pas quelque chose qui a Animal.prototype dans la chaîne de prototypes, je le fais:

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

Donc, je vérifie que mon objet prototype le plus proche a la propriété abstract, seul l’objet créé directement à partir du prototype Animal aura cette condition sur true. La fonction hasOwnProperty vérifie uniquement les propriétés de l'objet actuel, pas ses prototypes, ce qui nous donne à 100% la certitude que la propriété est déclarée ici, pas dans la chaîne de prototypes.

Chaque objet issu de Object hérite de la méthode hasOwnProperty . Cette méthode peut être utilisée pour déterminer si un objet possède 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. Plus à ce sujet:

Dans ma proposition, il n'est pas nécessaire de changer constructor à chaque fois après Object.create comme c'est actuellement la meilleure réponse de @ Jordão.

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

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

Cela ne garantit pas que fonctionner car __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 à propos de la chaîne de prototypes.

4
Georg Schölly

Javascript peut avoir un héritage, consultez l'URL ci-dessous:

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

Andrew

3
REA_ANDREW

Une autre chose que vous pourriez vouloir 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 en tant que FLAG définie en tant que constructeur de la classe Abstract. Ceci essayera ensuite de construire le FLAG qui appellera son constructeur contenant une 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();

}) ()

3
thecommoner

Nous pouvons utiliser le modèle de conception Factory dans ce cas. Javascript utilise prototype pour 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 ensuite créer une classe d'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 children.

// 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 un exemple complet de codage.

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

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 empêche la connexion de l'appelant à la méthode throwAbstract, mais l'erreur doit se produire dans un environnement de débogage affichant la trace de la pile.

0
pasx

Je pense que Toutes ces réponses spécialement les deux premières (de à certains et jordão ) répondent clairement à la question avec le concept de base du prototype JS conventionnel.
Maintenant que vous voulez que le constructeur de la classe animal se comporte en fonction du paramètre passé à la construction, je pense que cela ressemble beaucoup au comportement de base de Creational Patterns par exemple Factory Pattern

Ici, j’ai fait une petite approche pour que cela fonctionne ainsi.

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