Как мне создать абстрактный базовый класс в JavaScript? - PullRequest
99 голосов
/ 28 февраля 2009

Можно ли имитировать абстрактный базовый класс в JavaScript? Какой самый элегантный способ сделать это?

Скажи, я хочу сделать что-то вроде: -

var cat = new Animal('cat');
var dog = new Animal('dog');

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

Должно появиться: «кора», «мяу»

Ответы [ 16 ]

113 голосов
/ 19 января 2014

Один простой способ создать абстрактный класс - это:

/**
 @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 и метод say являются абстрактными.

Создание экземпляра приведет к ошибке:

new Animal(); // throws

Вот как вы «наследуете» от него:

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 выглядит точно так же.

И вот как ваш сценарий заканчивается:

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

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

Fiddle здесь (посмотрите на вывод консоли).

25 голосов
/ 28 февраля 2009

Вы имеете в виду что-то вроде этого:

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
20 голосов
/ 24 января 2018

Классы JavaScript и наследование (ES6)

Согласно ES6, вы можете использовать классы JavaScript и наследование, чтобы выполнить то, что вам нужно.

Классы JavaScript, представленные в ECMAScript 2015, в основном являются синтаксическим сахаром по сравнению с существующим наследованием JavaScript на основе прототипов.

Ссылка: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

Прежде всего, мы определяем наш абстрактный класс. Этот класс не может быть создан, но может быть расширен. Мы также можем определить функции, которые должны быть реализованы во всех классах, расширяющих этот класс.

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

После этого мы можем создавать наши конкретные классы. Эти классы будут наследовать все функции и поведение от абстрактного класса.

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

И результаты ...

// 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.
14 голосов
/ 02 марта 2009

Возможно, вы захотите проверить базовый класс Дина Эдвардса: http://dean.edwards.name/weblog/2006/03/base/

В качестве альтернативы есть следующий пример / статья Дугласа Крокфорда о классическом наследовании в JavaScript: http://www.crockford.com/javascript/inheritance.html

10 голосов
/ 19 сентября 2013
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! 
10 голосов
/ 28 февраля 2009

Можно ли имитировать абстрактный базовый класс в JavaScript?

Конечно. Существует около тысячи способов реализации систем классов / экземпляров в JavaScript. Вот один из них:

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

Конечно, это не абстрактный базовый класс. Вы имеете в виду что-то вроде:

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!
5 голосов
/ 19 сентября 2016

Вопрос довольно старый, но я создал возможное решение, как создать абстрактный «класс» и заблокировать создание объекта такого типа.

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

Как видите, последний объект дает нам ошибку, потому что у Animal в прототипе есть свойство abstract. Конечно, это Animal, а не что-то, что имеет Animal.prototype в цепочке прототипов, которые я делаю:

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

Итак, я проверяю, что у моего ближайшего объекта-прототипа есть свойство abstract, только условие, созданное непосредственно из Animal прототипа, будет иметь это условие. Функция hasOwnProperty проверяет только свойства текущего объекта, а не его прототипов, так что это дает нам 100% уверенность, что свойство объявлено здесь не в цепочке прототипов.

Каждый объект, произошедший от Object, наследует метод hasOwnProperty . Этот метод может использоваться, чтобы определить, имеет ли объект указанное свойство как прямое свойство этого объекта; в отличие от оператора in, этот метод не проверяет цепочку прототипов объекта. Подробнее об этом:

В моем предложении нам не нужно менять constructor каждый раз после Object.create, как в текущем лучшем ответе @ Jordão.

Решение также позволяет создавать много абстрактных классов в иерархии, нам нужно только создать свойство abstract в прототипе.

5 голосов
/ 22 мая 2013

Вы можете создавать абстрактные классы, используя прототипы объектов, простой пример может быть следующим:

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

Вы можете изменить вышеуказанный метод или нет, это зависит от вас, когда вы его реализуете. Для подробного наблюдения вы можете посетить здесь .

4 голосов
/ 28 февраля 2009
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();

Не гарантируется, что __proto__ не является стандартной переменной, но она работает по крайней мере в Firefox и Safari.

Если вы не понимаете, как это работает, прочитайте о цепочке прототипов.

3 голосов
/ 08 марта 2012

Еще одна вещь, которую вы, возможно, захотите применить, - убедиться, что ваш абстрактный класс не создан. Вы можете сделать это, определив функцию, которая действует как функция FLAG, установленная как конструктор класса Abstract. Затем он попытается создать FLAG, который вызовет его конструктор, содержащий исключение, которое будет выдано. Пример ниже:

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

}) ()

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...