¿Cómo creo una clase base abstracta en JavaScript?


Respuestas:


127

Una forma sencilla de crear una clase abstracta es la siguiente:

/**
 @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"clase" y lasay método son abstractos.

Crear una instancia arrojaría un error:

new Animal(); // throws

Así es como "heredas" 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 se parece a él.

Y así es como se desarrolla tu escenario:

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

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

Violín aquí (mira la salida de la consola).


¿Puede explicarme línea por línea, si no le importa, ya que soy nuevo en POO? ¡Gracias!
Desarrollador

2
@undefined: para entenderlo, le sugiero que busque la herencia prototípica en Javascript: esta es una buena guía.
Jordão

Gracias por la respuesta .. pasará por el enlace.
Desarrollador

La parte más importante de esto es que en el primer fragmento de código, se arroja un error. También puede lanzar una advertencia o devolver un valor nulo en lugar de un objeto para continuar con la ejecución de la aplicación, realmente depende de su implementación. Esta, en mi opinión, es la forma correcta de implementar "clases" abstractas de JS.
dudewad

1
@ G1P: esa es la forma habitual de ejecutar un "constructor de superclase" en Javascript, y debe hacerse así, manualmente.
Jordão

46

Clases de JavaScript y herencia (ES6)

De acuerdo con ES6, puede usar clases de JavaScript y herencia para lograr lo que necesita.

Las clases de JavaScript, introducidas en ECMAScript 2015, son principalmente azúcar sintáctico 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. No se puede crear una instancia de esta clase, pero se puede ampliar. También podemos definir funciones que se deben implementar en todas las clases que amplían 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 comportamientos 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.

27

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

Quizás me lo perdí. ¿Dónde está el resumen de la clase base (Animal)?
HairOfTheDog

5
@HairOfTheDog Sí, te perdiste que esto se respondió hace unos cinco años, que javascript en ese momento no tenía clases abstractas, que la pregunta era una forma de simularlo ( whattosayno está definido en animal ) y que esta respuesta claramente pregunta si la respuesta propuesta era la que buscaba el interrogador. No pretende dar una solución para clases abstractas en javascript. El interrogador no se molestó en responderme a mí ni a nadie más, así que no tengo idea de si le funcionó. Lamento si la respuesta propuesta por un niño de cinco años a la pregunta de otra persona no funcionó para usted.
algo del

15

Es posible que desee consultar la Clase base de Dean Edwards: http://dean.edwards.name/weblog/2006/03/base/

Alternativamente, existe este ejemplo / artículo de Douglas Crockford sobre la herencia clásica en JavaScript: http://www.crockford.com/javascript/inheritance.html


13
En cuanto al enlace de Crockford, es un montón de basura. Agregó una nota al final de ese artículo: "Ahora veo mis primeros intentos de apoyar el modelo clásico en JavaScript como un error".
Matt Ball

11

¿Es posible simular una clase base abstracta en JavaScript?

Ciertamente. Hay alrededor de mil formas de implementar sistemas de clases / instancias en JavaScript. Acá 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 ('gato');

Esa no es realmente una clase básica 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!

¿Por qué utilizar el constructo malvado New Function (...)? No var c = function () {...}; ser mejor?
fionbio

2
"Var c = function () {...}" crearía un cierre sobre cualquier cosa en la subclase () u otro alcance contenedor. Probablemente no sea importante, pero quería mantenerlo limpio de ámbitos principales potencialmente no deseados; el constructor de texto puro Function () evita cierres.
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! 

¿Puede el constructor de una subclase invocar al constructor de la superclase? (como llamar a super en algún idioma ... si es así, entonces generará incondicionalmente una excepción
polaridad

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

No se garantiza que esto funcione como __proto__ que no es una variable estándar, pero funciona al menos en Firefox y Safari.

Si no comprende cómo funciona, lea sobre la cadena de prototipos.


proto funciona AFAIK solo en FF y Chome (ni IE ni Opera lo admiten. No lo he probado en Safari). Por cierto, lo estás haciendo mal: la clase base (animal) debe editarse cada vez que se desee un nuevo tipo de animal.
alrededor del

Tanto Safari como Chrome usan el mismo motor javascript. No estaba realmente seguro de que solo quisiera saber cómo funciona la herencia, por lo que traté de seguir su ejemplo lo más cerca posible.
Georg Schölly

4
Safari y Chrome no usan el mismo motor de JavaScript, Safari usa JavaScriptCore y Chrome usa V8 . Lo que comparten ambos navegadores es el motor de diseño, WebKit .
CMS

@ GeorgSchölly Considere editar su respuesta para usar el nuevo constructo Object.getPrototypeOf
Benjamin Gruenbaum

@Benjamin: Según Mozilla, todavía no hay ningún setPrototypeOfmétodo que necesite para mi código.
Georg Schölly

5

Puede crear clases abstractas utilizando 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, puede visitar aquí .


5

La pregunta es bastante antigua, pero creé una posible solución sobre cómo crear una "clase" abstracta y bloquear la creación de un objeto 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 un error, es porque Animal en el prototipo tiene propiedad abstract. Para estar seguro de que es Animal, no algo que tenga Animal.prototypeen la cadena de prototipos que hago:

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

Así que verifico que mi objeto prototipo más cercano tenga la abstractpropiedad, solo el objeto creado directamente a partir del Animalprototipo tendrá esta condición en verdadera. La función hasOwnPropertycomprueba solo las propiedades del objeto actual, no sus prototipos, por lo que esto nos da un 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 utilizar para determinar si un objeto tiene la propiedad especificada como propiedad directa de ese objeto; a diferencia del operador in, este método no comprueba la cadena del prototipo del objeto. Más sobre esto:

En mi propuesta, no tenemos que cambiar constructorcada vez después Object.createcomo está en la mejor respuesta actual de @ Jordão.

La solución también permite crear muchas clases abstractas en jerarquía, solo necesitamos crear abstractpropiedades en el prototipo.


4

Otra cosa que puede querer hacer cumplir es asegurarse de que su clase abstracta no esté instanciada. Puede hacerlo definiendo una función que actúe como FLAG establecida como constructor de la clase Abstract. Esto luego intentará construir la BANDERA que llamará a su constructor que contiene la excepción para ser lanzada. 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();

})()


2

Podemos usar Factorypatrón de diseño en este caso. Uso de JavaScriptprototype para heredar los miembros de los padres.

Defina 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 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 el código de ejemplo completo.


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!

Esto es polimorfismo en JavaScript, puede hacer algunas comprobaciones para implementar la anulación.
Paul Orazulike

0

Creo que todas esas respuestas, especialmente las dos primeras (por algunos y jordão ) responden la pregunta claramente con el concepto convencional de JS base de prototipo.
Ahora que desea 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, Creational Patternspor ejemplo, Factory Pattern .

Aquí hice un pequeño enfoque 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

Enlazando a esto: programmers.stackexchange.com/questions/219543/… Este Animalconstructor puede verse como un anti-patrón, una superclase no debería tener conocimiento sobre subclases. (viola Liskov y el principio de apertura / cierre)
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();

En mi punto de vista, esta es la forma más elegante de obtener buenos resultados con menos código.
Tamas Romeo

1
Explique por qué es útil. Dar código no es tan útil como explicar por qué es útil el código. Es la diferencia entre entregar un pescado a alguien o enseñarle a pescar.
The Tin Man

Muchos no utilizan en sus técnicas de programación javascript prototipos o constructores. Incluso si son útiles en muchas situaciones. Para aquellos, considero que el código es útil. No porque el código sea mejor que otros ... sino porque es más fácil de entender
Tamas Romeo

0

Si desea asegurarse de que sus clases base y sus miembros sean estrictamente abstractos, aquí hay una clase base que hace esto 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 hace que sea imposible registrar a la persona que llama en el método throwAbstract, pero el error debería ocurrir en un entorno de depuración que mostraría el seguimiento de la pila.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.