JavaScript'te nasıl soyut bir temel sınıf oluşturabilirim?


Yanıtlar:


127

Soyut bir sınıf oluşturmanın basit bir yolu şudur:

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

Animal"Sınıf" ve sayyöntem soyut.

Örnek oluşturmak bir hataya neden olur:

new Animal(); // throws

Ondan bu şekilde "miras alırsınız":

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 aynen öyle görünüyor.

Ve senaryonuz şöyle işler:

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

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

Burada keman çalın (konsol çıkışına bakın).


OOP'larda yeni olduğum için, sakıncası yoksa lütfen satır satır açıklayabilir misiniz? Teşekkürler!
Geliştiriciye React

2
@undefined: Bunu anlamak için Javascript'te prototip kalıtıma bakmanızı öneririm: bu iyi bir kılavuzdur.
Jordão

Cevap için teşekkürler .. bağlantı üzerinden geçecek.
Geliştirici React

Bunun en önemli parçası, ilk kod parçacığında atılan bir hata olmasıdır. Ayrıca, uygulamanın yürütülmesine devam etmek için bir nesne yerine bir uyarı verebilir veya boş bir değer döndürebilirsiniz, bu gerçekten uygulamanıza bağlıdır. Bu benim görüşüme göre, soyut JS "sınıflarını" gerçekleştirmenin doğru yoludur.
dudewad

1
@ G1P: Javascript'te bir "süper sınıf kurucu" çalıştırmanın olağan yolu budur ve bu şekilde manuel olarak yapılması gerekir.
Jordão

46

JavaScript Sınıfları ve Kalıtım (ES6)

ES6'ya göre, ihtiyacınız olanı gerçekleştirmek için JavaScript sınıflarını ve kalıtımı kullanabilirsiniz.

ECMAScript 2015'te tanıtılan JavaScript sınıfları, JavaScript'in mevcut prototip tabanlı kalıtımına göre öncelikli olarak sözdizimsel şeklidir.

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

Öncelikle soyut sınıfımızı tanımlıyoruz. Bu sınıf başlatılamaz, ancak genişletilebilir. Bunu genişleten tüm sınıflarda uygulanması gereken işlevleri de tanımlayabiliriz.

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

Bundan sonra somut Sınıflarımızı oluşturabiliriz. Bu sınıflar, tüm işlevleri ve davranışları soyut sınıftan alacaklardır.

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

Ve sonuçlar ...

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

Bunun gibi birşey mi demek istedin:

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

Belki de kaçırdım. Temel sınıf (Hayvan) soyut nerede?
HairOfTheDog

5
@HairOfTheDog Evet, bunun yaklaşık beş yıl önce yanıtlandığını, o dönemde o javascript'in soyut sınıflarının olmadığını, sorunun onu simüle etmenin bir yolu olduğunu ( hayvandawhattosay tanımlanmadığını ) ve bu cevabın açıkça sorduğunu kaçırdınız Önerilen yanıt, soruyu soran kişinin aradığı şeyse. Javascript'te soyut sınıflar için bir çözüm sunma iddiasında değildir. Soru soran bana ya da başka birine cevap verme zahmetine girmedi, bu yüzden onun için işe yarayıp yaramadığını bilmiyorum. Başka birinin sorusuna beş yaşında önerilen bir cevap sizin için işe yaramadıysa özür dilerim.
bazı

15

Dean Edwards'ın Temel Sınıfına göz atmak isteyebilirsiniz: http://dean.edwards.name/weblog/2006/03/base/

Alternatif olarak, Douglas Crockford'un JavaScript'te klasik miras üzerine yazdığı şu örnek / makale var: http://www.crockford.com/javascript/inheritance.html


13
Crockford bağlantısıyla ilgili olarak, bu bir yığın çöp. Bu makalenin sonuna bir not ekledi: "JavaScript'teki klasik modeli desteklemeye yönelik erken girişimlerimi artık bir hata olarak görüyorum."
Matt Ball

11

JavaScript'te soyut temel sınıfı simüle etmek mümkün mü?

Kesinlikle. JavaScript'te sınıf / örnek sistemleri uygulamanın yaklaşık bin yolu vardır. İşte burada:

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

Elbette bu gerçekten soyut bir temel sınıf değil. Şunun gibi bir şey mi demek istiyorsun:

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!

Kötü yeni Function (...) yapısını neden kullanıyorsunuz? C = function () {...} değişmez; daha iyi ol?
fionbio

2
"Var c = function () {...}", alt sınıf () veya diğer kapsamı içeren herhangi bir şey üzerinde bir kapanış oluşturacaktır. Muhtemelen önemli değil, ancak potansiyel olarak istenmeyen ebeveyn kapsamlarından uzak tutmak istedim; saf metin Function () yapıcısı kapanmalardan kaçınır.
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! 

bir alt sınıfın yapıcısı üst sınıfın yapıcısını çağırabilir mi? (böylece, o zaman koşulsuz bir istisna yükseltmek eğer ... bir dille süper demek gibi
nonopolarity

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

Bunun __proto__standart bir değişken olmadığı için çalışacağı garanti edilmez, ancak en azından Firefox ve Safari'de çalışır.

Nasıl çalıştığını anlamıyorsanız, prototip zinciri hakkında bilgi edinin.


proto work AFAIK sadece FF ve Chome'da (ne IE ne de Opera bunu desteklemiyor. Safari'de test etmedim). BTW, yanlış yapıyorsun: yeni bir hayvan türü her istendiğinde temel sınıf (hayvan) düzenlenmelidir.
Bazı

Safari ve Chrome, aynı javascript motorunu kullanır. Sadece mirasın nasıl işlediğini bilmek istediğinden gerçekten emin değildim, bu yüzden örneğini olabildiğince yakından takip etmeye çalıştım.
Georg Schölly

4
Safari ve Chrome aynı JavaScript motorunu kullanmaz, Safari JavaScriptCore kullanır ve Chrome V8 kullanır . Her iki tarayıcının da paylaştığı şey Layout Engine, WebKit'tir .
CMS

@ GeorgSchölly Lütfen yeni Object.getPrototypeOf yapısını kullanmak için cevabınızı düzenlemeyi düşünün
Benjamin

@Bünyamin: Mozilla göre hiçbir yoktur setPrototypeOfbenim kodu için gerekir yöntem henüz.
Georg Schölly

5

Nesne prototiplerini kullanarak soyut sınıflar oluşturabilirsiniz, basit bir örnek aşağıdaki gibi olabilir:

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

Yukarıdaki yöntemi değiştirebilir veya değiştiremezsiniz, ne zaman uygulayacağınız size kalmıştır. Detaylı bir gözlem için burayı ziyaret etmek isteyebilirsiniz .


5

Soru oldukça eski, ancak soyut "sınıf" ın nasıl yaratılacağı ve bu tür nesnelerin nasıl oluşturulacağı konusunda bazı olası çözümler yarattım.

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

Gördüğünüz gibi, son nesne bize hata veriyor, çünkü prototipteki Animal özelliğine sahip abstract. Hayvan olduğundan emin olmak için Animal.prototypeprototip zincirinde olan bir şey değil :

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

Bu yüzden en yakın prototip nesnemin abstractözelliğe sahip olup olmadığını kontrol ediyorum , yalnızca doğrudan Animalprototipten oluşturulan nesnenin bu koşulu true'da olacak. İşlev hasOwnProperty, prototiplerini değil yalnızca mevcut nesnenin özelliklerini kontrol eder, bu nedenle bu bize özelliğin prototip zincirinde değil burada bildirildiğinden% 100 emin olur.

Object'ten türetilen her nesne hasOwnProperty yöntemini miras alır . Bu yöntem, bir nesnenin belirtilen özelliğe, o nesnenin doğrudan bir özelliği olarak sahip olup olmadığını belirlemek için kullanılabilir; in operatörünün aksine, bu yöntem nesnenin prototip zincirini kontrol etmez. Bununla ilgili daha fazlası:

Benim önerime göre, @ Jordão'nun mevcut en iyi cevabında olduğu gibi constructorher seferinde değişmemiz gerekmiyor Object.create.

Çözüm ayrıca hiyerarşide birçok soyut sınıf oluşturmayı sağlar, sadece abstractprototipte özellik oluşturmamız gerekir .


4

Zorlamak isteyebileceğiniz başka bir şey de soyut sınıfınızın somutlaştırılmadığından emin olmaktır. Bunu, Özet sınıf yapıcısı olarak ayarlanmış BAYRAK işlevi gören bir işlevi tanımlayarak yapabilirsiniz. Bu, daha sonra atılacak istisna içeren yapıcısını çağıracak olan FLAG'ı oluşturmaya çalışacaktır. Aşağıdaki örnek:

(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

FactoryBu durumda tasarım desenini kullanabiliriz . Javascript prototypeebeveynin üyelerini devralmak için kullanılır .

Ana sınıf yapıcısını tanımlayın.

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

Ve sonra çocuk sınıfı yaratın.

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

Daha sonra alt sınıf yapıcısını tanımlayın.

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

Dene.

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

İşte tam örnek kodlama için Codepen bağlantısı .


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!

Bu, JavaScript'teki polimorfizmdir, geçersiz kılmayı uygulamak için bazı kontroller yapabilirsiniz.
Paul Orazulike

0

Bence Tüm Bu cevaplar özellikle ilk ikisinin ( bazıları ve jordão tarafından ) soruyu geleneksel prototip tabanlı JS konseptiyle net bir şekilde yanıtladığını düşünüyorum.
Şimdi, hayvan sınıfı kurucusunun inşaata aktarılan parametreye göre davranmasını istediğinizden, bunun, Creational Patternsörneğin Fabrika Modeli'nin temel davranışına çok benzediğini düşünüyorum .

Bu şekilde çalışmasını sağlamak için burada küçük bir yaklaşım yaptım.

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

Buna bağlanmak: programmers.stackexchange.com/questions/219543/… Bu Animalkurucu bir anti-model olarak görülebilir, bir üst sınıfın alt sınıflar hakkında bilgisi olmamalıdır. (
Liskov

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

Bana göre bu, daha az kodla iyi sonuçlar almanın en zarif yoludur.
Tamas Romeo

1
Lütfen bunun neden yararlı olduğunu açıklayın. Kodu dağıtmak, kodun neden yararlı olduğunu açıklamak kadar kullanışlı değildir. Birine balık vermekle balık tutmayı öğretmek arasındaki fark budur.
The Tin Man

Birçoğu kendi javascript programlama teknikleri prototipinde veya yapıcılarında kullanmaz. Birçok durumda faydalı olsalar bile. Bunlar için kodun faydalı olduğunu düşünüyorum. Kodlama diğerlerinden daha iyi olduğu için değil .. anlaşılması daha kolay olduğu için
Tamas Romeo

0

Temel sınıflarınızın ve üyelerinin kesinlikle soyut olduğundan emin olmak istiyorsanız, burada bunu sizin için yapan bir temel sınıf vardır:

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

Katı mod, çağıranın throwAbstract yönteminde günlüğe kaydedilmesini imkansız hale getirir, ancak hata, yığın izlemesini gösterecek bir hata ayıklama ortamında meydana gelmelidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.