เป็นไปได้ไหมที่จะจำลองคลาสพื้นฐานแบบนามธรรมใน JavaScript วิธีที่สง่างามที่สุดที่จะทำคืออะไร?
พูดว่าฉันต้องการทำสิ่งที่ชอบ: -
var cat = new Animal('cat');
var dog = new Animal('dog');
cat.say();
dog.say();
ควรส่งออก: 'bark', 'meow'
เป็นไปได้ไหมที่จะจำลองคลาสพื้นฐานแบบนามธรรมใน JavaScript วิธีที่สง่างามที่สุดที่จะทำคืออะไร?
พูดว่าฉันต้องการทำสิ่งที่ชอบ: -
var cat = new Animal('cat');
var dog = new Animal('dog');
cat.say();
dog.say();
ควรส่งออก: 'bark', 'meow'
คำตอบ:
วิธีง่ายๆอย่างหนึ่งในการสร้างคลาสนามธรรมคือ:
/**
@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 ที่นี่ (ดูที่เอาต์พุตคอนโซล)
ตาม 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.
คุณหมายถึงสิ่งนี้:
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
whattosay
ไม่ได้กำหนดไว้ในสัตว์ ) และคำตอบนี้ถามอย่างชัดเจน หากคำตอบที่เสนอคือสิ่งที่ผู้ถามกำลังมองหา ไม่ได้อ้างว่าให้วิธีแก้ปัญหาสำหรับคลาสนามธรรมในจาวาสคริปต์ ผู้ถามไม่รำคาญที่จะตอบกลับฉันหรือคนอื่นดังนั้นฉันไม่รู้ว่ามันใช้ได้ผลกับเขาไหม ฉันขอโทษถ้าคำตอบที่เสนอให้กับคนอื่นอายุห้าขวบไม่ได้ผลสำหรับคุณ
คุณอาจต้องการตรวจสอบคลาสฐานของ Dean Edwards: http://dean.edwards.name/weblog/2006/03/base/
หรือมีตัวอย่าง / บทความนี้โดย Douglas Crockford เกี่ยวกับการสืบทอดแบบคลาสสิกใน JavaScript: http://www.crockford.com/javascript/inheritance.html
เป็นไปได้ไหมที่จะจำลองคลาสพื้นฐานแบบนามธรรมใน 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 = สัตว์ใหม่ ('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!
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!
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
หากคุณไม่เข้าใจวิธีการทำงานอ่านเกี่ยวกับห่วงโซ่ต้นแบบ
setPrototypeOf
วิธีการใดที่ฉันต้องการรหัสของฉัน
คำถามค่อนข้างเก่า แต่ฉันได้สร้างวิธีการสร้าง "คลาส" นามธรรมและบล็อกการสร้างวัตถุประเภทนั้น
//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();
abstract
ที่คุณสามารถดูวัตถุที่ผ่านมาทำให้เรามีข้อผิดพลาดก็เป็นเพราะสัตว์ในต้นแบบที่มีคุณสมบัติ เพื่อให้แน่ใจว่ามันไม่ใช่ Animal ไม่ใช่สิ่งที่มีAnimal.prototype
อยู่ในห่วงโซ่ต้นแบบฉันทำ:
Object.getPrototypeOf(this).hasOwnProperty("abstract")
ดังนั้นฉันจึงตรวจสอบว่าวัตถุต้นแบบที่ใกล้เคียงที่สุดของฉันมีabstract
คุณสมบัติเฉพาะวัตถุที่สร้างโดยตรงจากAnimal
ต้นแบบเท่านั้นที่จะมีเงื่อนไขนี้เป็นจริง ฟังก์ชันhasOwnProperty
ตรวจสอบเฉพาะคุณสมบัติของวัตถุปัจจุบันไม่ใช่ต้นแบบของเขาดังนั้นสิ่งนี้ทำให้เราแน่ใจได้ 100% ว่าคุณสมบัติถูกประกาศที่นี่ไม่ได้อยู่ในห่วงโซ่ต้นแบบ
ทุกออบเจ็กต์ที่สืบเชื้อสายมาจาก Object จะสืบทอดเมธอดhasOwnProperty วิธีนี้สามารถใช้เพื่อพิจารณาว่าวัตถุมีคุณสมบัติที่ระบุเป็นคุณสมบัติโดยตรงของวัตถุนั้นหรือไม่ ซึ่งแตกต่างจากตัวดำเนินการในวิธีนี้ไม่ได้ตรวจสอบห่วงโซ่ต้นแบบของวัตถุ เพิ่มเติมเกี่ยวกับเรื่องนี้:
ในโจทย์ของฉันเราไม่จำเป็นต้องเปลี่ยนconstructor
ทุกครั้งหลังจากObject.create
ชอบคำตอบที่ดีที่สุดในปัจจุบันโดย @ Jordão
โซลูชันยังช่วยให้สามารถสร้างคลาสนามธรรมจำนวนมากตามลำดับชั้นเราจำเป็นต้องสร้าง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();
})()
เราสามารถใช้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สำหรับตัวอย่างการเข้ารหัสแบบเต็ม
//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!
ฉันคิดว่าคำตอบทั้งหมดเหล่านั้นเป็นคำตอบเฉพาะสองข้อแรก (โดย บางคนและjordã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
Animal
สร้างนี้สามารถมองได้ว่าเป็นการต่อต้านรูปแบบซึ่งซูเปอร์คลาสไม่ควรมีความรู้เกี่ยวกับคลาสย่อย (ละเมิดหลักการ Liskov และ Open / Close)
/****************************************/
/* 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();
หากคุณต้องการให้แน่ใจว่าคลาสพื้นฐานของคุณและสมาชิกของพวกเขาเป็นนามธรรมอย่างเคร่งครัดนี่คือคลาสพื้นฐานที่ทำเพื่อคุณ:
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 แต่ข้อผิดพลาดควรเกิดขึ้นในสภาพแวดล้อมการดีบักที่จะแสดงการติดตามสแต็ก