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'
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).
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
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.
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
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!
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!
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 .
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.
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.
Javascript peut avoir un héritage, consultez l'URL ci-dessous:
http://www.webreference.com/js/column79/
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();
}) ()
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.
//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!
/****************************************/
/* 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();
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.
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