Como faço para criar uma classe base abstrata em JavaScript?

Respostas:

127

Uma maneira simples de criar uma classe abstrata é 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!");
}

A Animal"classe" e o saymétodo são abstratos.

Criar uma instância geraria um erro:

new Animal(); // throws

É assim que você "herda" dele:

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 com isso.

E é assim que seu cenário se desenrola:

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

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

Fiddle aqui (veja a saída do console).

Jordão
fonte
Você pode explicar linha por linha, se você não se importar, como eu sou novo em OOPs. Obrigado!
React Developer
2
@undefined: para entender, sugiro que você procure herança prototípica em Javascript: este é um bom guia.
Jordão
Obrigado pela resposta .. vai passar pelo link.
React Developer de
A parte mais importante disso é que no primeiro fragmento de código, há um erro sendo lançado. Você também pode lançar um aviso ou retornar um valor nulo em vez de um objeto para continuar a execução do aplicativo, realmente depende de sua implementação. Essa, em minha opinião, é a maneira correta de implementar "classes" JS abstratas.
dudewad
1
@ G1P: essa é a maneira usual de executar um "construtor de superclasse" em Javascript, e precisa ser feito assim, manualmente.
Jordão
46

Classes e herança de JavaScript (ES6)

De acordo com o ES6, você pode usar classes e herança JavaScript para realizar o que precisa.

As classes JavaScript, introduzidas no ECMAScript 2015, são principalmente açúcar sintático sobre a herança baseada em protótipo existente do JavaScript.

Referência: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

Em primeiro lugar, definimos nossa classe abstrata. Esta classe não pode ser instanciada, mas pode ser estendida. Também podemos definir funções que devem ser implementadas em todas as classes que estendem 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");
  }
}

Depois disso, podemos criar nossas Classes concretas. Essas classes herdarão todas as funções e comportamentos da classe abstrata.

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

E os 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.
Daniel Possamai
fonte
27

Você quer dizer algo assim:

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
alguns
fonte
Talvez eu tenha perdido. Onde está o abstrato da classe base (Animal)?
HairOfTheDog
5
@HairOfTheDog Sim, você perdeu que isso foi respondido cerca de cinco anos atrás, que o javascript na época não tinha classes abstratas, que a pergunta era para uma forma de simular ( whattosaynão está definido em animal ), e que essa resposta pergunta claramente se a resposta proposta era a que o questionador estava procurando. Ele não pretende fornecer uma solução para classes abstratas em javascript. O questionador não se preocupou em responder a mim ou a qualquer outra pessoa, então não tenho ideia se funcionou para ele. Lamento se uma criança de cinco anos sugeriu uma resposta para a pergunta de outra pessoa não funcionou para você.
cerca de
15

Você pode querer verificar a classe base de Dean Edwards: http://dean.edwards.name/weblog/2006/03/base/

Como alternativa, há este exemplo / artigo de Douglas Crockford sobre herança clássica em JavaScript: http://www.crockford.com/javascript/inheritance.html

Ian Oxley
fonte
13
Em relação ao link Crockford, é um monte de lixo. Ele acrescentou uma nota ao final do artigo: "Agora vejo minhas primeiras tentativas de oferecer suporte ao modelo clássico em JavaScript como um erro."
Matt Ball de
11

É possível simular uma classe base abstrata em JavaScript?

Certamente. Existem cerca de mil maneiras de implementar sistemas de classe / instância em JavaScript. Aqui está um:

// 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 = novo Animal ('gato');

Essa não é realmente uma classe base abstrata, é claro. Você quer dizer 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!
bobince
fonte
Por que usar a nova construção maligna de Função (...)? Não var c = function () {...}; sê melhor?
fionbio
2
“Var c = function () {...}” criaria um fechamento sobre qualquer coisa na subclasse () ou outro escopo contido. Provavelmente não é importante, mas eu queria mantê-lo livre de escopos pais potencialmente indesejados; o construtor de pure-text Function () evita fechamentos.
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
fonte
pode o construtor de uma subclasse invocar o construtor da superclasse? (como chamar super em algum idioma ... em caso afirmativo, ele irá gerar uma exceção incondicionalmente
polaridade
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();

Não é garantido que funcione, pois __proto__não é uma variável padrão, mas funciona pelo menos no Firefox e Safari.

Se você não entende como funciona, leia sobre a cadeia de protótipos.

Georg Schölly
fonte
proto funciona AFAIK apenas em FF e Chome (nem IE nem Opera suporta. Não testei no Safari). BTW, você está fazendo isso errado: a classe base (animal) deve ser editada toda vez que um novo tipo de animal é desejado.
cerca de
Safari e Chrome usam o mesmo mecanismo javascript. Eu não tinha certeza se ele apenas queria saber como funciona a herança, portanto, tentei seguir seu exemplo o mais fielmente possível.
Georg Schölly
4
Safari e Chrome não usam o mesmo motor JavaScript, Safari usa JavaScriptCore e Chrome usa V8 . O que ambos os navegadores compartilham é o Layout Engine, WebKit .
CMS
@ GeorgSchölly Por favor, considere editar sua resposta para usar a nova construção Object.getPrototypeOf
Benjamin Gruenbaum
@Benjamin: De acordo com a Mozilla, ainda não existe um setPrototypeOfmétodo que eu precise para meu código.
Georg Schölly,
5

Você pode criar classes abstratas usando protótipos de objeto, um exemplo simples pode ser o seguinte:

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

Você pode mudar o método acima ou não, depende de você quando você o implementa. Para uma observação detalhada, você pode querer visitar aqui .

yusufaytas
fonte
5

A questão é bastante antiga, mas criei alguma solução possível como criar "classe" abstrata e bloquear a criação de objeto desse 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 você pode ver, o último objeto nos dá erro, é porque Animal no protótipo tem propriedade abstract. Para ter certeza de que é Animal, não algo que tenha Animal.prototypena cadeia de protótipo que eu faço:

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

Então, eu verifico se meu objeto de protótipo mais próximo tem abstractpropriedade, apenas o objeto criado diretamente do Animalprotótipo terá essa condição como verdadeira. A função hasOwnPropertyverifica apenas as propriedades do objeto atual, não seus protótipos, então isso nos dá 100% de certeza de que a propriedade é declarada aqui e não na cadeia de protótipos.

Cada objeto descendente de Object herda o método hasOwnProperty . Este método pode ser usado para determinar se um objeto tem a propriedade especificada como uma propriedade direta desse objeto; ao contrário do operador in, este método não verifica a cadeia de protótipo do objeto. Mais sobre isso:

Na minha proposição não temos que mudar constructortodas as vezes depois Object.createcomo está na melhor resposta atual do @Jordão.

A solução também permite criar muitas classes abstratas na hierarquia, precisamos apenas criar abstractpropriedades no protótipo.

Maciej Sikora
fonte
4

Outra coisa que você pode querer impor é garantir que sua classe abstrata não seja instanciada. Você pode fazer isso definindo uma função que atua como FLAG definida como o construtor da classe Abstract. Isso tentará construir o FLAG que chamará seu construtor contendo a exceção a ser lançada. Exemplo abaixo:

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

})()
o convocador
fonte
2

Podemos usar o Factorypadrão de design neste caso. Javascript usa prototypepara herdar os membros do pai.

Defina o construtor da classe pai.

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

E então crie classes para crianças.

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

Em seguida, defina o construtor da classe filha.

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

Teste-o.

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

Aqui está o link Codepen para o exemplo completo de codificação.

Eric Tan
fonte
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
fonte
Isso é polimorfismo em JavaScript, você pode fazer algumas verificações para implementar a substituição.
Paul Orazulike
0

Acho que todas essas respostas, especialmente as duas primeiras (por alguns e jordão ) respondem à pergunta claramente com o conceito convencional de base de protótipo JS.
Agora, como você deseja que o construtor da classe animal se comporte de acordo com o parâmetro passado para a construção, acho que isso é muito semelhante ao comportamento básico de, Creational Patternspor exemplo, Factory Pattern .

Aqui, fiz uma pequena abordagem para que funcionasse dessa forma.

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
fonte
Vinculando a isso: programmers.stackexchange.com/questions/219543/… Este Animalconstrutor pode ser visto como um antipadrão, uma superclasse não deveria ter conhecimento sobre subclasses. (viola o princípio de Liskov e Abrir / Fechar)
SirLenz0rlot de
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
fonte
No meu ponto de vista, esta é a maneira mais elegante de ter bons resultados com menos código.
Tamas Romeo
1
Explique por que isso é útil. Distribuir código não é tão útil quanto explicar por que o código é útil. É a diferença entre entregar um peixe a alguém ou ensiná-lo a pescar.
The Tin Man
Muitos não usam em seus protótipos ou construtores de técnicas de programação javascript. Mesmo que sejam úteis em muitas situações. Para eles, considero que o código é útil. Não porque o código seja melhor do que os outros .. mas porque é mais fácil de entender
Tamas Romeo
0

Se você deseja ter certeza de que suas classes base e seus membros são estritamente abstratos, aqui está uma classe base que faz isso para você:

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

O modo estrito torna impossível registrar o chamador no método throwAbstract, mas o erro deve ocorrer em um ambiente de depuração que mostre o rastreamento da pilha.

pasx
fonte