Làm cách nào để tạo một lớp cơ sở trừu tượng trong JavaScript?


109

Có thể mô phỏng lớp cơ sở trừu tượng trong JavaScript không? Cách thanh lịch nhất để làm điều đó là gì?

Nói rằng, tôi muốn làm điều gì đó như: -

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

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

Nó sẽ xuất ra: 'sủa', 'meo'


Câu trả lời:


127

Một cách đơn giản để tạo một lớp trừu tượng là:

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

Các Animal"lớp học" và các sayphương pháp là trừu tượng.

Tạo một phiên bản sẽ gặp lỗi:

new Animal(); // throws

Đây là cách bạn "thừa hưởng" từ nó:

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 trông giống như nó.

Và đây là cách kịch bản của bạn diễn ra:

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

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

Fiddle ở đây (nhìn vào đầu ra của bảng điều khiển).


bạn có thể vui lòng giải thích từng dòng một, nếu bạn không phiền, vì tôi là người mới sử dụng OOP. Cảm ơn!
Phản ứng nhà phát triển

2
@undefined: để hiểu nó, tôi khuyên bạn nên tra cứu kế thừa nguyên mẫu trong Javascript: đây là một hướng dẫn tốt.
Jordão

Cảm ơn đã trả lời .. sẽ đi qua liên kết.
Nhà phát triển React

Phần quan trọng nhất của điều này là trong đoạn mã đầu tiên, có một lỗi được đưa ra. Bạn cũng có thể ném một cảnh báo hoặc trả về giá trị null thay vì một đối tượng để tiếp tục thực thi ứng dụng, điều đó thực sự phụ thuộc vào việc triển khai của bạn. Theo tôi, đây là cách chính xác để triển khai các "lớp" JS trừu tượng.
dudewad

1
@ G1P: đó là cách thông thường để thực thi một "hàm tạo siêu lớp" trong Javascript và nó cần phải được thực hiện theo cách thủ công.
Jordão

46

Các lớp JavaScript và Kế thừa (ES6)

Theo ES6, bạn có thể sử dụng các lớp JavaScript và kế thừa để thực hiện những gì bạn cần.

Các lớp JavaScript, được giới thiệu trong ECMAScript 2015, chủ yếu là đường cú pháp thay vì kế thừa dựa trên nguyên mẫu hiện có của JavaScript.

Tham khảo: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

Trước hết, chúng ta định nghĩa lớp trừu tượng của chúng ta. Lớp này không thể được khởi tạo, nhưng có thể được mở rộng. Chúng ta cũng có thể xác định các hàm phải được triển khai trong tất cả các lớp mở rộng lớp này.

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

Sau đó, chúng ta có thể tạo các Lớp cụ thể của mình. Các lớp này sẽ kế thừa tất cả các chức năng và hành vi từ lớp trừu tượng.

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

Và kết quả ...

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

Ý bạn là như thế này:

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

Có lẽ tôi đã bỏ lỡ nó. Lớp cơ sở (Động vật) trừu tượng ở đâu?
HairOfTheDog

5
@HairOfTheDog Có, bạn đã bỏ lỡ rằng câu này đã được trả lời cách đây khoảng 5 năm, rằng javascript vào thời điểm đó không có các lớp trừu tượng, câu hỏi dành cho cách mô phỏng nó ( whattosaykhông được định nghĩa ở động vật ) và câu trả lời này rõ ràng là nếu câu trả lời được đề xuất là những gì người hỏi đang tìm kiếm. Nó không tuyên bố đưa ra giải pháp cho các lớp trừu tượng trong javascript. Người hỏi không thèm trả lời tôi hoặc cho bất kỳ ai khác nên tôi không biết liệu nó có hiệu quả với anh ta hay không. Tôi xin lỗi nếu một câu trả lời đề xuất năm tuổi cho một câu hỏi của ai đó không phù hợp với bạn.
một số

15

Bạn có thể muốn xem Lớp cơ sở của Dean Edwards: http://dean.edwards.name/weblog/2006/03/base/

Ngoài ra, có ví dụ / bài viết này của Douglas Crockford về kế thừa cổ điển trong JavaScript: http://www.crockford.com/javascript/inheritance.html


13
Về liên kết Crockford, đó là một đống rác rưởi. Anh ấy đã thêm một ghi chú vào cuối bài viết đó: "Giờ đây, tôi thấy những nỗ lực ban đầu của mình để hỗ trợ mô hình cổ điển trong JavaScript là một sai lầm."
Matt Ball

11

Có thể mô phỏng lớp cơ sở trừu tượng trong JavaScript không?

Chắc chắn. Có khoảng một nghìn cách để triển khai hệ thống lớp / phiên bản trong JavaScript. Đây là một:

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

Tất nhiên, đó không thực sự là một lớp cơ sở trừu tượng. Ý bạn là gì như:

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!

Tại sao lại sử dụng hàm new function (...)? Sẽ không var c = function () {...}; để tốt hơn?
fionbio

2
“Var c = function () {...}” sẽ tạo một bao đóng đối với bất kỳ thứ gì trong lớp con () hoặc phạm vi chứa khác. Có lẽ không quan trọng, nhưng tôi muốn giữ nó sạch sẽ về các phạm vi cha mẹ có thể không mong muốn; hàm khởi tạo Function () thuần văn bản tránh đóng.
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! 

phương thức khởi tạo của lớp con có thể gọi phương thức khởi tạo của lớp cha không? (như gọi siêu trong một số ngôn ngữ ... nếu như vậy, sau đó nó sẽ vô điều kiện nâng cao một ngoại lệ
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();

Điều này không được đảm bảo hoạt động như __proto__ không phải là một biến tiêu chuẩn, nhưng nó hoạt động ít nhất trong Firefox và Safari.

Nếu bạn không hiểu cách nó hoạt động, hãy đọc về chuỗi nguyên mẫu.


proto hoạt động AFAIK chỉ trong FF và Chome (cả IE và Opera đều không hỗ trợ nó. Tôi chưa thử nghiệm trong Safari). BTW, bạn đang làm sai: lớp cơ sở (động vật) nên được chỉnh sửa mỗi khi muốn có một loại động vật mới.
một số

Safari và Chrome đều sử dụng cùng một công cụ javascript. Tôi không thực sự chắc chắn rằng anh ấy chỉ muốn biết cách thức hoạt động của quyền thừa kế, vì vậy tôi đã cố gắng noi gương anh ấy càng kỹ càng tốt.
Georg Schölly

4
Safari và Chrome không sử dụng cùng một công cụ JavaScript, Safari sử dụng JavaScriptCore và Chrome sử dụng V8 . Thứ mà cả hai trình duyệt chia sẻ là Công cụ bố cục, WebKit .
CMS

@ GeorgSchölly Vui lòng xem xét chỉnh sửa câu trả lời của bạn để sử dụng Object.getPrototypeOf xây dựng mới
Benjamin Gruenbaum

@Benjamin: Theo Mozilla , vẫn chưa có setPrototypeOfphương pháp nào tôi cần cho mã của mình.
Georg Schölly

5

Bạn có thể tạo các lớp trừu tượng bằng cách sử dụng các nguyên mẫu đối tượng, một ví dụ đơn giản có thể như sau:

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

Bạn có thể thay đổi phương pháp trên hoặc không, tùy thuộc vào bạn khi bạn thực hiện nó. Để quan sát chi tiết, bạn có thể muốn truy cập vào đây .


5

Câu hỏi khá cũ, nhưng tôi đã tạo ra một số giải pháp khả thi làm thế nào để tạo "lớp" trừu tượng và tạo khối của đối tượng kiểu đó.

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

Như Bạn có thể thấy đối tượng cuối cùng cung cấp cho chúng tôi lỗi, đó là vì Động vật trong nguyên mẫu có thuộc tính abstract. Để chắc chắn nó không phải là Động vật không phải là thứ có Animal.prototypetrong chuỗi nguyên mẫu, tôi làm:

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

Vì vậy, tôi kiểm tra xem đối tượng nguyên mẫu gần nhất của tôi có thuộc abstracttính hay không, chỉ đối tượng được tạo trực tiếp từ Animalnguyên mẫu mới có điều kiện này đúng. Hàm hasOwnPropertychỉ kiểm tra các thuộc tính của đối tượng hiện tại không phải nguyên mẫu của nó, vì vậy điều này cho chúng ta 100% chắc chắn rằng thuộc tính được khai báo ở đây không phải trong chuỗi nguyên mẫu.

Mọi đối tượng xuất phát từ Object đều kế thừa phương thức hasOwnProperty . Phương pháp này có thể được sử dụng để xác định xem một đối tượng có thuộc tính được chỉ định là thuộc tính trực tiếp của đối tượng đó hay không; không giống như toán tử in, phương thức này không kiểm tra chuỗi nguyên mẫu của đối tượng. Thông tin thêm về nó:

Theo đề xuất của tôi, chúng ta không phải thay đổi constructormọi lúc sauObject.create như nó là trong câu trả lời tốt nhất hiện nay bởi @ Jordão.

Giải pháp cũng cho phép tạo nhiều lớp trừu tượng trong hệ thống phân cấp, chúng ta chỉ cần tạo abstractthuộc tính trong nguyên mẫu.


4

Một điều khác mà bạn có thể muốn thực thi là đảm bảo rằng lớp trừu tượng của bạn không được khởi tạo. Bạn có thể làm điều đó bằng cách xác định một hàm hoạt động như FLAG, hàm được đặt làm hàm tạo lớp Trừu tượng. Điều này sau đó sẽ cố gắng xây dựng FLAG sẽ gọi hàm tạo của nó có chứa ngoại lệ sẽ được ném ra. Ví dụ bên dưới:

(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

Chúng ta có thể sử dụng Factorymẫu thiết kế trong trường hợp này. Sử dụng Javascript prototypeđể kế thừa các thành viên của cha mẹ.

Định nghĩa hàm tạo lớp cha.

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

Và sau đó tạo lớp trẻ em.

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

Sau đó xác định hàm tạo lớp con.

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

Kiểm tra nó.

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

Đây là liên kết Codepen để mã hóa ví dụ đầy đủ.


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!

Đây là tính đa hình trong JavaScript, bạn có thể thực hiện một số kiểm tra để thực hiện ghi đè.
Paul Orazulike

0

Tôi nghĩ Tất cả những câu trả lời đó, đặc biệt là hai câu trả lời đầu tiên (của một số ngườijordão ) trả lời câu hỏi một cách rõ ràng với khái niệm JS cơ sở nguyên mẫu thông thường.
Bây giờ khi bạn muốn hàm tạo lớp động vật hoạt động theo tham số được truyền vào cấu trúc, tôi nghĩ điều này rất giống với hành vi cơ bản của Creational Patternsví dụ Factory Pattern .

Ở đây tôi đã thực hiện một cách tiếp cận nhỏ để làm cho nó hoạt động theo cách đó.

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

Liên kết đến cái này: programmers.stackexchange.com/questions/219543/… Hàm Animaltạo này có thể được coi là một phản mẫu, một lớp siêu không nên có kiến ​​thức về lớp con. (vi phạm Liskov và Mở / Đóng nguyên tắc)
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();

Theo quan điểm của tôi, đây là cách thanh lịch nhất để có kết quả tốt với ít mã hơn.
Tamas Romeo

1
Vui lòng giải thích tại sao điều này lại hữu ích. Việc cung cấp mã gần như không hữu ích bằng việc giải thích tại sao mã lại hữu ích. Đó là sự khác biệt giữa giao cho ai đó một con cá, hoặc dạy họ cách câu cá.
the Tin Man

Nhiều người không sử dụng nguyên mẫu hoặc trình tạo kỹ thuật lập trình javascript của họ. Ngay cả khi chúng hữu ích trong nhiều tình huống. Đối với những người đó, tôi cho rằng mã đó hữu ích. Không phải vì mã nó là tốt hơn so với những người khác .. nhưng vì nó là dễ hiểu
Tamas Romeo

0

Nếu bạn muốn đảm bảo rằng các lớp cơ sở của bạn và các thành viên của chúng hoàn toàn trừu tượng thì đây là một lớp cơ sở thực hiện điều này cho bạn:

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

Chế độ nghiêm ngặt làm cho người gọi không thể ghi nhật ký trong phương thức némAbstract nhưng lỗi sẽ xảy ra trong môi trường gỡ lỗi sẽ hiển thị dấu vết ngăn xếp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.