【问题标题】:How do I create an abstract base class in JavaScript?如何在 JavaScript 中创建抽象基类?
【发布时间】:2010-10-10 12:17:10
【问题描述】:

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

说,我想做这样的事情:-

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

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

它应该输出:'bark', 'meow'

【问题讨论】:

标签: javascript oop abstract


【解决方案1】:

我认为所有这些答案特别是前两个(somejordão)用传统的原型基础 JavaScript 概念清楚地回答了这个问题。

现在您希望动物类构造函数根据传递给构造的参数进行行为,我认为这与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

【讨论】:

【解决方案2】:

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

})()

【讨论】:

    【解决方案3】:

    JavaScript 类和继承 (ES6)

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

    在 ECMAScript 2015 中引入的 JavaScript 类主要是 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.
    

    【讨论】:

    • 这应该是 2021 年的最佳答案。
    【解决方案4】:

    如果您想确保您的基类及其成员是严格抽象的,这里有一个基类可以为您执行此操作:

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

    【讨论】:

      【解决方案5】:
      //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 中的多态,你可以做一些检查来实现覆盖。
      【解决方案6】:

      在这种情况下,我们可以使用Factory 设计模式。 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 link

      【讨论】:

        【解决方案7】:

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

        //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 运算符不同,此方法不检查对象的原型链。更多信息:

        在我的建议中,我们不必每次在Object.create 之后都更改constructor,就像@Jordão 目前的最佳答案一样。

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

        【讨论】:

          【解决方案8】:
          /****************************************/
          /* 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();
          

          【讨论】:

          • 在我看来,这是用更少的代码获得好结果的最优雅的方式。
          • 请解释为什么这很有用。分发代码不如解释为什么代码有用那么有用。这是给某人一条鱼或教他们如何钓鱼的区别。
          • 许多人在他们的 javascript 编程技术中不使用原型或构造函数。即使它们在许多情况下都很有用。对于那些,我认为代码很有用。不是因为代码比其他代码更好......而是因为它更容易理解
          【解决方案9】:

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

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

          上面的方法你可以改变与否,实现时由你决定。如需详细观察,您可以访问here

          【讨论】:

            【解决方案10】:

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

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

            小提琴here(查看控制台输出)。

            【讨论】:

            • 如果您不介意,请您逐行解释,因为我是 OOP 新手。谢谢!
            • @undefined: 要理解它我建议你在 Javascript 中查找原型继承:this 是一个很好的指南。
            • 感谢您的回复..将通过链接。
            • 最重要的一点是,在第一个代码 sn-p 中,抛出了一个错误。您也可能会抛出警告或返回空值而不是对象以继续执行应用程序,这实际上取决于您的实现。在我看来,这是实现抽象 JS“类”的正确方法。
            • @G1P:这是在 Javascript 中执行“超类构造函数”的常用方法,需要手动完成。
            【解决方案11】:
            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 ......如果是这样,那么它将无条件地引发异常
            【解决方案12】:

            您可能想查看 Dean Edwards 的基类:http://dean.edwards.name/weblog/2006/03/base/

            另外,还有 Douglas Crockford 撰写的关于 JavaScript 中的经典继承的示例/文章:http://www.crockford.com/javascript/inheritance.html

            【讨论】:

            • 关于 Crockford 链接,这是一堆垃圾。他在那篇文章的末尾添加了一条注释:“我现在认为我早期尝试支持 JavaScript 中的经典模型是一个错误。”
            【解决方案13】:

            你的意思是这样的:

            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 是的,你错过了大约五年前回答的问题,当时的 javascript 没有抽象类,问题是模拟的方法它(whattosay 未在 animal 中定义),并且该答案清楚地询问了提出的答案是否是提问者正在寻找的答案。它没有声称为 javascript 中的抽象类提供解决方案。提问者没有费心回复我或其他任何人,所以我不知道它是否对他有用。很抱歉,如果一个五年前提出的对别人问题的回答不适合你。
            【解决方案14】:

            是否可以在 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 () { ... };会更好吗?
            • “var c= function() {...}” 将在 subclass() 或其他包含范围内的任何内容上创建一个闭包。可能并不重要,但我想让它远离潜在的不需要的父作用域;纯文本 Function() 构造函数避免了闭包。
            【解决方案15】:
            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 中工作。

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

            【讨论】:

            • proto 仅在 FF 和 Chome 中工作 AFAIK(IE 和 Opera 都不支持。我没有在 Safari 中测试过)。顺便说一句,你做错了:每次需要一种新型动物时都应该编辑基类(动物)。
            • Safari 和 Chrome 都使用相同的 JavaScript 引擎。我不确定他只是想知道继承是如何工作的,因此我试图尽可能地效仿他的例子。
            • Safari 和 Chrome 不使用相同的 JavaScript 引擎,Safari 使用 JavaScriptCore 而 Chrome 使用 V8。两个浏览器共享的是布局引擎,WebKit
            • @GeorgSchölly 请考虑编辑您的答案以使用新的 Object.getPrototypeOf 构造
            • @Benjamin:According to Mozilla 我的代码还没有 setPrototypeOf 方法。
            【解决方案16】:

            Javascript 可以有继承,查看下面的 URL:

            http://www.webreference.com/js/column79/

            安德鲁

            猜你喜欢
            • 2012-05-11
            • 2015-08-14
            • 2016-06-27
            • 2017-05-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多