如何在JavaScript中创建抽象基类?


Answers:


127

创建抽象类的一种简单方法是:

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

在这里摆弄(查看控制台输出)。


如果您不介意的话,能否请您逐行解释,因为我是OOP的新手。谢谢!
React Developer 2014年

2
@undefined:要理解它,我建议您使用Javascript查找原型继承:是一个很好的指南。
乔丹

感谢您的答复..将通过链接。
React Developer 2014年

最重要的一点是,在第一个代码段中,抛出了一个错误。您可能还会发出警告或返回空值而不是对象,以便继续执行应用程序,这实际上取决于您的实现。我认为,这是实现抽象JS“类”的正确方法。
dudewad

1
@ G1P:这是在Javascript中执行“超类构造函数”的常用方法,因此需要手动完成。
乔丹

46

JavaScript类和继承(ES6)

根据ES6,您可以使用JavaScript类和继承来完成所需的操作。

ECMAScript 2015中引入的JavaScript类主要是对JavaScript现有的基于原型的继承的语法糖。

参考:https : //developer.mozilla.org/zh-CN/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.

27

您的意思是这样的吗:

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

也许我错过了。基类(动物)的抽象在哪里?
HairOfTheDog 2014年

5
@HairOfTheDog是的,您错过了大约五年前回答的问题,当时的javascript没有抽象类,问题是用于模拟它的方法(whattosay动物中未定义),并且该答案显然要求如果建议的答案是发问者在寻找什么。它没有声称为javascript中的抽象类提供解决方案。发问者没有打扰我或其他人,所以我不知道它是否对他有用。如果对别人的问题提出的五年建议答案对您没有帮助,我感到抱歉。
一些


11

是否可以在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!

为什么要使用邪恶的new Function(...)构造?不会var c = function(){...}; 会更好?
fionbio

2
“ var c = function(){...}”将创建对subclass()或其他包含范围的任何内容的关闭。可能并不重要,但我想保持它远离可能有害的父范围;纯文本Function()构造函数避免闭包。
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! 

子类的构造函数可以调用超类的构造函数吗?(例如以某种语言调用super ...如果是这样,那么它将无条件引发异常
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();

不能保证这样工作 __proto__它不是标准变量,但至少在Firefox和Safari中有效。

如果您不了解其工作原理,请阅读原型链。


原型只能在FF和Chome中使用AFAIK(IE和Opera均不支持。我尚未在Safari中进行测试)。顺便说一句,您做错了:每次需要新的动物类型时,都应编辑基类(动物)。
一些

Safari和Chrome都使用相同的JavaScript引擎。我不确定他是否只想知道继承的工作原理,因此我尝试尽可能地遵循他的榜样。
格奥尔格·舍利(GeorgSchölly),2009年

4
Safari和Chrome使用不同的JavaScript引擎,Safari使用JavaScriptCore,而Chrome使用V8。两种浏览器共享的东西是Layout Engine WebKit
CMS 2010年

@GeorgSchölly请考虑编辑您的答案以使用新的Object.getPrototypeOf构造
Benjamin Gruenbaum 2013年

@Benjamin:据Mozilla称setPrototypeOf,我的代码还没有方法。
GeorgSchölly2013年

5

您可以使用对象原型创建抽象类,一个简单的示例如下:

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

您可以更改上面的方法,也可以不更改,这取决于您实现它。有关详细的观察,您可能想要访问这里


5

问题已经很老了,但是我创建了一些可能的解决方案,如何创建抽象的“类”并阻止创建该类型的对象。

//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具有property abstract。可以肯定的是,Animal.prototype我不是原型链中的动物。

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

因此,我检查了最接近的原型对象是否具有abstract属性,只有直接从Animal原型创建的对象才具有true的条件。函数hasOwnProperty仅检查当前对象的属性,而不检查其原型,因此这可以使我们100%确保此处的属性不在原型链中声明。

从Object派生的每个对象都继承hasOwnProperty方法。此方法可用于确定对象是否具有指定属性作为该对象的直接属性;与in运算符不同,此方法不会检查对象的原型链。关于它的更多信息:

在我的建议中,我们不必像@Jordão的当前最佳答案中那样constructor每次都进行更改Object.create

解决方案还允许在层次结构中创建许多抽象类,我们只需要abstract在原型中创建属性。


4

您可能要强制执行的另一件事是确保未实例化抽象类。您可以通过定义一个充当FLAG函数的函数来实现这一目的,该函数被设置为Abstract类构造函数。然后,这将尝试构造FLAG,该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();

})()


2

Factory在这种情况下,我们可以使用设计模式。使用Java prototype来继承父级的成员。

定义父类的构造函数。

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

然后创建儿童班。

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

然后定义子类的构造函数。

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

测试一下。

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

这是完整示例代码的Codepen链接


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!

这是JavaScript中的多态性,您可以做一些检查来实现重写。
Paul Orazulike

0

我认为所有那些答案特别是前两个(由 somejordão)用常规的原型基础JS概念清楚地回答了这个问题。
现在,当您希望动物类构造函数根据传递给构造的参数进行操作时,我认为这与Creational Patterns例如Factory Pattern的基本行为非常相似 。

在这里,我采取了一些方法使其工作正常。

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

链接到此:developers.stackexchange.com/questions/219543/…Animal构造函数可以看作是反模式,超类不应该了解子类。(违反了Liskov和Open / Close原则)
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();

以我的观点,这是用更少的代码获得良好结果的最优雅的方法。
塔玛斯罗密欧

1
请解释为什么这很有用。分发代码几乎没有解释为什么代码有用的有用。递给别人一条鱼或教他们怎样钓鱼是区别。
Tin Man

许多人在他们的JavaScript编程技术中没有使用原型或构造函数。即使它们在许多情况下很有用。对于那些,我认为代码是有用的。不是因为代码比其他代码更好..而是因为它更容易理解
Tamas Romeo

0

如果要确保您的基类及其成员严格都是抽象的,请参见以下基类:

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

严格模式使得不可能将调用者记录在throwAbstract方法中,但是该错误应该在显示堆栈跟踪的调试环境中发生。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.