Bagaimana cara membuat kelas dasar abstrak di JavaScript?

Jawaban:

127

Salah satu cara sederhana untuk membuat kelas abstrak adalah ini:

/**
 @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!");
}

The Animal"kelas" dan saymetode yang abstrak.

Membuat sebuah instance akan menimbulkan kesalahan:

new Animal(); // throws

Beginilah cara Anda "mewarisi" darinya:

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 terlihat seperti itu.

Dan beginilah skenario Anda:

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

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

Fiddle di sini (lihat keluaran konsol).

Jordão
sumber
bisakah u jelaskan baris demi baris, jika Anda tidak keberatan, karena saya baru mengenal OOP. Terima kasih!
React Developer
2
@undefined: untuk memahaminya, saya sarankan Anda mencari warisan prototipe dalam Javascript: ini adalah panduan yang bagus.
Jordão
Terima kasih atas balasannya .. akan melalui tautan.
React Developer
Bagian terpenting dari ini adalah bahwa di cuplikan kode pertama, ada kesalahan yang terjadi. Anda juga dapat memberikan peringatan atau mengembalikan nilai null sebagai ganti objek untuk melanjutkan eksekusi aplikasi, ini sangat bergantung pada implementasi Anda. Ini, menurut pendapat saya, adalah cara yang benar untuk mengimplementasikan "kelas" JS abstrak.
dudewad
1
@ G1P: itulah cara yang biasa untuk mengeksekusi "konstruktor kelas-super" dalam Javascript, dan itu perlu dilakukan seperti itu, secara manual.
Jordão
46

Kelas JavaScript dan Warisan (ES6)

Menurut ES6, Anda dapat menggunakan kelas JavaScript dan warisan untuk mencapai apa yang Anda butuhkan.

Kelas JavaScript, yang diperkenalkan di ECMAScript 2015, pada dasarnya adalah gula sintaksis atas warisan berbasis prototipe JavaScript yang ada.

Referensi: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

Pertama-tama, kami mendefinisikan kelas abstrak kami. Kelas ini tidak dapat dibuat instance-nya, tetapi dapat diperpanjang. Kita juga bisa mendefinisikan fungsi yang harus diimplementasikan di semua kelas yang memperluas kelas ini.

/**
 * 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");
  }
}

Setelah itu, kita dapat membuat Kelas beton kita. Kelas-kelas ini akan mewarisi semua fungsi dan perilaku dari kelas abstrak.

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

Dan hasilnya ...

// 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
sumber
27

Apakah yang Anda maksud seperti ini:

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
beberapa
sumber
Mungkin saya melewatkannya. Di mana kelas dasar (Hewan) abstrak?
HairOfTheDog
5
@HairOfTheDog Ya, Anda melewatkan jawaban ini sekitar lima tahun yang lalu, bahwa javascript pada saat itu tidak memiliki kelas abstrak, bahwa pertanyaannya adalah cara untuk mensimulasikannya ( whattosaytidak ditentukan pada hewan ), dan bahwa jawaban ini dengan jelas menanyakan jika jawaban yang diajukan adalah apa yang dicari oleh penanya. Itu tidak mengklaim memberikan solusi untuk kelas abstrak dalam javascript. Penanya tidak repot-repot membalas saya atau orang lain jadi saya tidak tahu apakah itu berhasil untuknya. Saya minta maaf jika usulan jawaban anak berusia lima tahun untuk pertanyaan orang lain tidak berhasil untuk Anda.
sekitar
15

Anda mungkin ingin melihat Kelas Dasar Dean Edwards: http://dean.edwards.name/weblog/2006/03/base/

Atau, ada contoh / artikel oleh Douglas Crockford tentang pewarisan klasik di JavaScript: http://www.crockford.com/javascript/inheritance.html

Ian Oxley
sumber
13
Mengenai tautan Crockford, ini adalah beban sampah. Dia menambahkan catatan di akhir artikel itu: "Sekarang saya melihat upaya awal saya untuk mendukung model klasik di JavaScript sebagai kesalahan."
Matt Ball
11

Apakah mungkin untuk mensimulasikan kelas dasar abstrak dalam JavaScript?

Pasti. Ada sekitar seribu cara untuk mengimplementasikan sistem kelas / instance dalam JavaScript. Ini salah satunya:

// 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 = Hewan baru ('kucing');

Tentu saja itu bukan kelas dasar abstrak. Apakah yang Anda maksud seperti:

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
sumber
Mengapa menggunakan konstruksi Fungsi baru (...) yang jahat? Tidak akan var c = function () {...}; Jadi lebih baik?
fionbio
2
“Var c = function () {...}” akan membuat penutupan atas apa pun di subclass () atau cakupan lain yang memuat. Mungkin tidak penting, tetapi saya ingin menjaganya tetap bersih dari cakupan induk yang mungkin tidak diinginkan; konstruktor Fungsi () teks murni menghindari penutupan.
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
sumber
dapatkah konstruktor subclass memanggil konstruktor superclass? (seperti menyebut super dalam beberapa bahasa ... jika demikian, maka tanpa syarat akan menimbulkan pengecualian
nonopolaritas
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();

Ini tidak dijamin akan berfungsi sebagai __proto__ bukan variabel standar, tetapi setidaknya berfungsi di Firefox dan Safari.

Jika Anda tidak memahami cara kerjanya, baca tentang rantai prototipe.

Georg Schölly
sumber
proto bekerja AFAIK hanya di FF dan Chome (baik IE maupun Opera tidak mendukungnya. Saya belum menguji di Safari). BTW, Anda salah melakukannya: kelas dasar (hewan) harus diedit setiap kali jenis hewan baru diinginkan.
sekitar
Safari dan Chrome menggunakan mesin javascript yang sama. Saya tidak begitu yakin dia hanya ingin tahu cara kerja warisan, oleh karena itu saya berusaha mengikuti teladannya sedekat mungkin.
Georg Schölly
4
Safari dan Chrome tidak menggunakan mesin JavaScript yang sama, Safari menggunakan JavaScriptCore, dan Chrome menggunakan V8 . Hal yang kedua browser berbagi adalah Layout Engine, WebKit .
CMS
@ GeorgSchölly Harap pertimbangkan untuk mengedit jawaban Anda untuk menggunakan konstruksi Object.getPrototypeOf baru
Benjamin Gruenbaum
@Benjamin: Menurut Mozilla, belum ada setPrototypeOfmetode yang saya perlukan untuk kode saya.
Georg Schölly
5

Anda dapat membuat kelas abstrak dengan menggunakan prototipe objek, contoh sederhananya adalah sebagai berikut:

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

Anda dapat mengubah metode di atas atau tidak, terserah Anda saat mengimplementasikannya. Untuk observasi yang lebih detail, Anda mungkin ingin berkunjung ke sini .

yusufaytas
sumber
5

Pertanyaan cukup lama, tetapi saya membuat beberapa solusi yang mungkin bagaimana membuat "kelas" abstrak dan memblokir pembuatan objek jenis itu.

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

Seperti yang Anda lihat objek terakhir memberi kami kesalahan, itu karena Animal dalam prototipe memiliki properti abstract. Untuk memastikan itu Hewan bukan sesuatu yang ada Animal.prototypedi rantai prototipe saya lakukan:

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

Jadi saya periksa apakah objek prototipe terdekat saya memiliki abstractproperti, hanya objek yang dibuat langsung dari Animalprototipe yang memiliki kondisi benar. Fungsi hasOwnPropertyhanya memeriksa properti dari objek saat ini bukan prototipe, jadi ini memberi kita 100% yakin bahwa properti dideklarasikan di sini bukan dalam rantai prototipe.

Setiap objek yang diturunkan dari Object mewarisi metode hasOwnProperty . Metode ini dapat digunakan untuk menentukan apakah suatu objek memiliki properti yang ditentukan sebagai properti langsung dari objek tersebut; tidak seperti operator in, metode ini tidak memeriksa rantai prototipe objek. Lebih lanjut tentang itu:

Dalam proposisi saya, kami tidak harus berubah constructorsetiap saatObject.create seperti yang ada di jawaban terbaik saat ini oleh @ Jordão.

Solusi juga memungkinkan untuk membuat banyak kelas abstrak dalam hierarki, kita hanya perlu membuat abstractproperti dalam prototipe.

Maciej Sikora
sumber
4

Hal lain yang mungkin ingin Anda terapkan adalah memastikan kelas abstrak Anda tidak dibuat instance-nya. Anda dapat melakukannya dengan mendefinisikan fungsi yang bertindak sebagai FLAG yang disetel sebagai konstruktor kelas Abstrak. Ini kemudian akan mencoba untuk membangun FLAG yang akan memanggil konstruktornya yang berisi pengecualian untuk dilempar. Contoh di bawah ini:

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

})()
orang biasa
sumber
2

Kita bisa menggunakan Factorypola desain dalam kasus ini. Javascript digunakan prototypeuntuk mewarisi anggota orang tua.

Tentukan konstruktor kelas induk.

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

Dan kemudian membuat kelas anak-anak.

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

Kemudian tentukan konstruktor kelas anak.

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

Menguji.

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

Berikut tautan Codepen untuk pengkodean contoh lengkap.

Eric Tan
sumber
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
sumber
Ini adalah polimorfisme dalam JavaScript, Anda dapat melakukan beberapa pemeriksaan untuk mengimplementasikan override.
Paul Orazulike
0

Saya pikir Semua Jawaban itu khususnya dua jawaban pertama (oleh beberapa dan jordão ) menjawab pertanyaan dengan jelas dengan konsep dasar JS prototipe konvensional.
Sekarang karena Anda ingin konstruktor kelas hewan berperilaku sesuai dengan parameter yang diteruskan ke konstruksi, saya rasa ini sangat mirip dengan perilaku dasar Creational Patternsmisalnya Pola Pabrik .

Di sini saya membuat sedikit pendekatan untuk membuatnya berfungsi seperti itu.

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
sumber
Menghubungkan ke ini: programmers.stackexchange.com/questions/219543/…Animal Konstruktor ini dapat dilihat sebagai anti-pola, superclass seharusnya tidak memiliki pengetahuan tentang subclass. (melanggar prinsip Liskov dan Buka / Tutup)
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();
Tamas Romeo
sumber
Menurut saya, ini adalah cara paling elegan untuk mendapatkan hasil yang bagus dengan kode yang lebih sedikit.
Tamas Romeo
1
Tolong jelaskan mengapa ini berguna. Membagikan kode hampir tidak berguna seperti menjelaskan mengapa kode itu berguna. Itulah perbedaan antara memberikan ikan kepada seseorang, atau mengajari mereka cara memancing.
Tin Man
Banyak yang tidak menggunakan prototipe atau konstruktor teknik pemrograman javascript mereka. Bahkan jika itu berguna dalam banyak situasi. Bagi mereka, saya menganggap kode itu berguna. Bukan karena kode itu lebih baik dari yang lain .. tetapi karena lebih mudah untuk dipahami
Tamas Romeo
0

Jika Anda ingin memastikan bahwa kelas dasar Anda dan anggotanya benar-benar abstrak, berikut adalah kelas dasar yang melakukan ini untuk Anda:

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

Mode ketat tidak memungkinkan untuk mencatat pemanggil dalam metode throwAbstract tetapi kesalahan harus terjadi di lingkungan debug yang akan menampilkan pelacakan tumpukan.

pasx
sumber