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

167

在JavaScript中是否有可能模拟抽象基类?最优雅的方法是什么?

比如说,我想做这样的事情:

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

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

它应该输出:

meow
bark

如何在JavaScript中创建无法实例化的抽象基类 - Bergi
18个回答

3

其他可能性包括http://javascript.crockford.com/inheritance.html和http://javascript.crockford.com/prototypal.html。 - fionbio

2
我们可以在这种情况下使用“工厂”设计模式。Javascript使用“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

1
使用ES6类和new.target

class Animal {
  constructor() {
    if(new.target===Animal)
    throw new Error('Cannot be instantiated')
  }
  //Non-abstract method
  makeSound()
  {
     console.log(this.sound);
  }
}

class Cat extends Animal {
  constructor(sound) {
    super();
    this.sound = sound;
  }
}

class Dog extends Animal {
  constructor(sound) {
    super();
    this.sound = sound;
  }
}

let cat1 = new Cat('Meow')
cat1.makeSound();
let dog1 = new Dog('Bark')
dog1.makeSound();
let genericAnimal = new Animal(); //Throws Error

//ES6 - Abstract class with Abstract and Non-Abstract methods
class Animal {
  constructor() {
    if(new.target===Animal)
    throw new Error('Abstract Class cannot be instantiated')
  }
  //abstract method
    makeSound()
  {
     throw new Error('Abstract Method cannot be called')
  }
  //non-abstract method
  displayType()
  {
     console.log(this.name," instanceof Animal",this instanceof Animal);
  }
}

class Cat extends Animal {
  constructor(name,sound) {
    super();
    this.name = name;
    this.sound = sound;
  }
  //abstract method defined in child class implementation
  makeSound()
  {
     console.log("Cat ",this.name, " is making ", this.sound);
  }
}

class Dog extends Animal {
  constructor(name,sound) {
    super();
    this.name = name;
    this.sound = sound;
  }
}
//Dog.prototype.constructor = Dog;

let cat1 = new Cat('Bella','Meow')
cat1.makeSound();
cat1.displayType();
let dog1 = new Dog('Jimmy','Bark')
dog1.displayType();
dog1.makeSound(); //throws error
//let genericAnimal = new Animal(); //throws error


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 方法中记录调用者不可能,但错误应该发生在能显示堆栈跟踪的调试环境中。


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
1
请解释为什么这很有用。仅仅分发代码并不像解释代码的有用之处那样实用。这就好比是给别人一条鱼和教别人如何钓鱼的区别。 - the Tin Man
许多人在他们的JavaScript编程技术中不使用原型或构造函数,即使它们在许多情况下都很有用。对于那些人来说,我认为这段代码很有用。不是因为代码比其他代码更好,而是因为它更容易理解。 - Tamas Romeo

0
我认为所有这些答案,特别是前两个(由somejordão提供的答案),用传统的原型基础JavaScript概念清楚地回答了问题。
现在,您希望动物类构造函数根据传递给构造函数的参数来行事,我认为这非常类似于创建型模式的基本行为,例如工厂模式
在这里,我做了一点尝试,使其按照这种方式运作。

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


1
这个Animal构造函数可以被视为一种反模式,超类不应该知道它的子类。(违反了Liskov原则和开闭原则) Link for reference: http://programmers.stackexchange.com/questions/219543/should-a-class-know-about-its-subclasses - Len

0

"use strict";



function Abstract (...arg){
    
    // create abstract constructor
    if(  this.constructor.name === 'Object' || this.constructor === Abstract ) throw { ErrorType : "can't call abstract class with new !" , }
 

    // ceate abstract method
    Object.defineProperty( this , 'config' , {
        value : function(){
            console.log('config parent')
        }
    });


    // or other 
    return this ;
};
 

class Home extends Abstract{
    name = '';
    constructor(...arg){
        super(...arg) ; 
    }

    config(){
        // this method not working
        console.log('config child')
    }
}

let y = new Home( "home" , 'dasd');
y.config();


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接