tipos - ¿Cómo creo una clase base abstracta en JavaScript?
static class javascript (16)
¿Es posible simular clases base abstractas en JavaScript? ¿Cuál es la forma más elegante de hacerlo?
Diga, quiero hacer algo como:
var cat = new Animal(''cat'');
var dog = new Animal(''dog'');
cat.say();
dog.say();
Debería salir: ''ladrar'', ''miau''
¿Es posible simular clases base abstractas en JavaScript?
Ciertamente. Hay aproximadamente mil maneras de implementar sistemas de clase / instancia en JavaScript. Aqui hay 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'');
Esa no es realmente una clase base abstracta, por supuesto. ¿Te refieres a algo como:
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!
Clases de Javascript y Herencia (ES6)
Según ES6, puede usar clases y herencia de JavaScript para lograr lo que necesita.
Las clases de JavaScript, introducidas en ECMAScript 2015, son principalmente azúcar sintáctica sobre la herencia existente basada en prototipos de JavaScript.
Referencia: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
En primer lugar, definimos nuestra clase abstracta. Esta clase no se puede instanciar, pero se puede extender. También podemos definir funciones que deben implementarse en todas las clases que amplíen esta.
/**
* 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");
}
}
Después de eso, podemos crear nuestras Clases concretas. Estas clases heredarán todas las funciones y el comportamiento de la clase abstracta.
/**
* 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 {}
Y los resultados ...
// 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.
Creo que Todas esas respuestas especialmente las dos primeras (por some y jordão ) responden claramente a la pregunta con el prototipo convencional de JS base.
Ahora, como quiere que el constructor de la clase animal se comporte de acuerdo con el parámetro pasado a la construcción, creo que esto es muy similar al comportamiento básico de los Creational Patterns
de Creational Patterns
por ejemplo , patrón de fábrica .
Aquí hice un pequeño acercamiento para que funcione de esa manera.
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
Javascript puede tener herencia, revisa la URL a continuación:
http://www.webreference.com/js/column79/
Andrés
La pregunta es bastante antigua, pero he creado una posible solución para crear "clases" abstractas y bloquear la creación de objetos de ese 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();
Como puede ver el último objeto nos da error, es porque Animal en prototipo tiene propiedad abstract
. Para estar seguro de que no es Animal lo que tiene el prototipo Animal.prototype
en la cadena de prototipos:
Object.getPrototypeOf(this).hasOwnProperty("abstract")
Así que compruebo que mi objeto prototipo más cercano tiene una propiedad abstract
, solo el objeto creado directamente del prototipo Animal
tendrá esta condición en verdadero. La función hasOwnProperty
comprueba solo las propiedades del objeto actual, no sus prototipos, por lo que nos da 100% de seguridad de que la propiedad se declara aquí no en la cadena de prototipos.
Cada objeto que desciende de Object hereda el método hasOwnProperty . Este método se puede usar para determinar si un objeto tiene la propiedad especificada como propiedad directa de ese objeto; a diferencia del operador en, este método no comprueba la cadena de prototipos del objeto. Más sobre esto:
En mi proposición no tenemos que cambiar de constructor
cada vez después de Object.create
como está en la mejor respuesta actual por @ Jordão.
La solución también permite crear muchas clases abstractas en jerarquía, solo necesitamos crear una propiedad abstract
en prototipo.
Otra cosa que querrás hacer cumplir es asegurarte de que tu clase abstracta no esté instanciada. Puedes hacerlo definiendo una función que actúe como los FLAG establecidos como el constructor de la clase Abstract. Esto intentará construir el FLAG que llamará a su constructor que contiene la excepción que se lanzará. Ejemplo a continuación:
(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();
}) ()
Podemos utilizar el patrón de diseño de Factory
en este caso. Javascript usa un prototype
para heredar los miembros del padre.
Definir el constructor de la clase padre
var Animal = function() {
this.type = ''animal'';
return this;
}
Animal.prototype.tired = function() {
console.log(''sleeping: zzzZZZ ~'');
}
Y luego crea una clase para niños.
// These are the child classes
Animal.cat = function() {
this.type = ''cat'';
this.says = function() {
console.log(''says: meow'');
}
}
Luego defina el constructor de la clase de los niños.
// 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;
}
Pruébalo.
var timmy = Animal.born(''cat'');
console.log(timmy.type) // cat
timmy.says(); // meow
timmy.tired(); // zzzZZZ~
Aquí está el enlace de Codepen para la codificación de ejemplo completa.
Puede consultar la clase base de Dean Edwards: http://dean.edwards.name/weblog/2006/03/base/
Alternativamente, hay un ejemplo / artículo de Douglas Crockford sobre la herencia clásica en JavaScript: http://www.crockford.com/javascript/inheritance.html
Puede crear clases abstractas mediante el uso de prototipos de objetos, un ejemplo simple puede ser el siguiente:
var SampleInterface = {
addItem : function(item){}
}
Puede cambiar el método anterior o no, depende de usted cuando lo implemente. Para una observación detallada, es posible que desee visitar here .
Si quiere asegurarse de que sus clases base y sus miembros sean estrictamente abstractos, aquí hay una clase base que lo hace por usted:
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
El modo estricto imposibilita el registro de la persona que llama en el método throwAbstract, pero el error debería ocurrir en un entorno de depuración que muestre el seguimiento de la pila.
Te refieres a algo como esto:
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
Una forma simple de crear una clase abstracta es esta:
/**
@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 "clase" Animal
y el método say
son abstractos.
Crear una instancia arrojaría un error:
new Animal(); // throws
Así es como "heredaste" de él:
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
parece a eso.
Y así es como se desarrolla su escenario:
var cat = new Cat();
var dog = new Dog();
cat.say();
dog.say();
Violín here (mira la salida de la consola).
/****************************************/
/* 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();
//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!
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!
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();
No se garantiza que esto funcione, ya que __proto__
no es una variable estándar, pero funciona al menos en Firefox y Safari.
Si no entiende cómo funciona, lea sobre la cadena de prototipos.