ฉันเรียนรู้วิธีที่จะทำให้ OOP ด้วย JavaScript มันมีแนวคิดของส่วนต่อประสาน (เช่นของ Java interface
) หรือไม่?
ดังนั้นฉันจะสามารถสร้างผู้ฟัง ...
ฉันเรียนรู้วิธีที่จะทำให้ OOP ด้วย JavaScript มันมีแนวคิดของส่วนต่อประสาน (เช่นของ Java interface
) หรือไม่?
ดังนั้นฉันจะสามารถสร้างผู้ฟัง ...
คำตอบ:
ไม่มีความคิดของ "คลาสนี้จะต้องมีฟังก์ชั่นเหล่านี้" (นั่นคือไม่มีส่วนต่อประสาน se) เนื่องจาก:
แต่ JavaScript ใช้สิ่งที่เรียกว่าเป็ดพิมพ์ (ถ้ามันเดินเหมือนเป็ดและ quacks เหมือนเป็ดเท่าที่ใส่ใจ JS ก็เป็นเป็ด) หากวัตถุของคุณมีการต้มตุ๋น (), เดิน () และวิธีการบิน () รหัสสามารถใช้มันได้ทุกที่ที่คาดหวัง วัตถุที่สามารถเดินเล่นต้มตุ๋นและบินได้โดยไม่ต้องใช้ส่วนต่อประสาน "Duckable" อินเทอร์เฟซคือชุดของฟังก์ชันที่รหัสใช้ (และค่าส่งคืนจากฟังก์ชันเหล่านั้น) และด้วยการพิมพ์เป็ดคุณจะได้รับสิ่งนั้นฟรี
ตอนนี้ที่ไม่ได้บอกรหัสของคุณจะไม่ล้มเหลวผ่านไปครึ่งทางถ้าคุณพยายามที่จะโทรsome_dog.quack()
; คุณจะได้รับ TypeError ตรงไปตรงมาถ้าคุณบอกให้สุนัขต้มตุ๋นคุณมีปัญหาใหญ่กว่าเล็กน้อย การพิมพ์เป็ดนั้นทำได้ดีที่สุดเมื่อคุณเก็บเป็ดทั้งหมดไว้ในแถวเพื่อพูดและไม่ปล่อยให้สุนัขและเป็ดปะปนกันเว้นแต่คุณจะปฏิบัติต่อพวกมันเหมือนสัตว์ทั่วไป กล่าวอีกนัยหนึ่งแม้ว่าอินเทอร์เฟซเป็นของเหลว แต่ก็ยังคงมีอยู่ มันมักจะเป็นข้อผิดพลาดในการส่งสุนัขไปยังรหัสที่คาดว่าจะต้มตุ๋นและบินในสถานที่แรก
แต่ถ้าคุณแน่ใจว่าคุณกำลังทำสิ่งที่ถูกต้องคุณสามารถแก้ไขปัญหาสุนัขหลอกด้วยการทดสอบการมีอยู่ของวิธีการเฉพาะก่อนที่จะพยายามที่จะใช้มัน สิ่งที่ต้องการ
if (typeof(someObject.quack) == "function")
{
// This thing can quack
}
ดังนั้นคุณสามารถตรวจสอบวิธีการทั้งหมดที่คุณสามารถใช้ได้ก่อนที่จะใช้ แม้ว่าไวยากรณ์จะเป็นเรื่องที่น่าเกลียด มีวิธีที่สวยกว่าเล็กน้อย:
Object.prototype.can = function(methodName)
{
return ((typeof this[methodName]) == "function");
};
if (someObject.can("quack"))
{
someObject.quack();
}
นี่คือ JavaScript มาตรฐานดังนั้นจึงควรใช้งานล่าม JS ที่ควรใช้ มันมีประโยชน์เพิ่มเติมของการอ่านเช่นภาษาอังกฤษ
สำหรับเบราว์เซอร์สมัยใหม่ (นั่นคือเบราว์เซอร์อื่น ๆ ที่ไม่ใช่ IE 6-8) ค่อนข้างมีวิธีที่จะป้องกันไม่ให้ทรัพย์สินปรากฏในfor...in
:
Object.defineProperty(Object.prototype, 'can', {
enumerable: false,
value: function(method) {
return (typeof this[method] === 'function');
}
}
ปัญหาคือวัตถุ IE7 ไม่มี.defineProperty
เลยและใน IE8 นั้นถูกกล่าวหาว่าใช้งานได้เฉพาะบนวัตถุโฮสต์ (นั่นคือองค์ประกอบ DOM และเช่นนั้น) หากความเข้ากันได้เป็นปัญหาคุณจะไม่สามารถใช้งาน.defineProperty
ได้ (ฉันจะไม่พูดถึง IE6 เพราะมันค่อนข้างไม่เกี่ยวข้องอีกแล้วนอกประเทศจีน)
ปัญหาก็คือว่าบางรูปแบบการเข้ารหัสชอบที่จะถือว่าทุกคนที่เขียนรหัสที่ไม่ดีและห้ามแก้ไขในกรณีที่มีคนต้องการที่จะใช้สุ่มสี่สุ่มห้าObject.prototype
for...in
หากคุณสนใจหรือใช้รหัส (IMO ใช้งานไม่ได้ ) ให้ลองใช้รุ่นที่แตกต่างกันเล็กน้อย:
function can(obj, methodName)
{
return ((typeof obj[methodName]) == "function");
}
if (can(someObject, "quack"))
{
someObject.quack();
}
for...in
คือ - และถูกเสมอ - เต็มไปด้วยอันตรายดังกล่าวและใครก็ตามที่ทำอย่างนั้นโดยไม่พิจารณาว่ามีคนเพิ่มเข้ามาObject.prototype
(ไม่ใช่เทคนิคที่ผิดปกติโดยการยอมรับของบทความนั้น) จะเห็นรหัสของพวกเขาอยู่ในมือของคนอื่น
for...in
ปัญหานี้ได้ developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
for...in
ปัญหา" จะยังคงอยู่ในระดับหนึ่งสาเหตุที่มีจะเป็นรหัสเลอะเทอะ ... ดีนั้นและเป็นงานค่อนข้างน้อยกว่าเพียงแค่Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});
obj.a = 3;
ฉันสามารถเข้าใจคนที่ไม่พยายามทำบ่อยขึ้น : P
รับสำเนาของ ' รูปแบบการออกแบบ JavaScriptโดยดัสตินดิแอซ มีบทไม่กี่บทที่ทุ่มเทให้กับการใช้งานอินเตอร์เฟส JavaScript ผ่าน Duck Typing มันเป็นการอ่านที่ดีเช่นกัน แต่ไม่มีไม่มีการดำเนินพื้นเมืองภาษาของอินเตอร์เฟซที่คุณต้องเป็ดประเภท
// example duck typing method
var hasMethods = function(obj /*, method list as strings */){
var i = 1, methodName;
while((methodName = arguments[i++])){
if(typeof obj[methodName] != 'function') {
return false;
}
}
return true;
}
// in your code
if(hasMethods(obj, 'quak', 'flapWings','waggle')) {
// IT'S A DUCK, do your duck thang
}
JavaScript (ECMAScript edition 3) ได้บันทึกimplements
คำสงวนไว้เพื่อใช้ในอนาคตเก็บไว้สำหรับใช้ในอนาคตฉันคิดว่าสิ่งนี้มีจุดประสงค์เพื่อจุดประสงค์นี้อย่างเร่งด่วนอย่างไรก็ตามเพื่อให้ได้สเปคออกไปนอกประตูพวกเขาไม่มีเวลากำหนดสิ่งที่ต้องทำกับมันดังนั้นในปัจจุบันเบราว์เซอร์ไม่ได้ทำอะไรนอกจาก ปล่อยให้มันนั่งอยู่ตรงนั้นและบางครั้งก็บ่นถ้าคุณพยายามใช้มันเพื่ออะไรบางอย่าง
เป็นไปได้และง่ายพอที่จะสร้างObject.implement(Interface)
วิธีการของคุณเองด้วยตรรกะที่หยุดชะงักเมื่อใดก็ตามที่คุณสมบัติ / ฟังก์ชั่นชุดหนึ่งไม่ถูกนำไปใช้ในวัตถุที่กำหนด
ผมเขียนบทความเกี่ยวกับวัตถุปฐมนิเทศ ที่ใช้รูปแบบของตัวเองดังต่อไปนี้ :
// Create a 'Dog' class that inherits from 'Animal'
// and implements the 'Mammal' interface
var Dog = Object.extend(Animal, {
constructor: function(name) {
Dog.superClass.call(this, name);
},
bark: function() {
alert('woof');
}
}).implement(Mammal);
มีหลายวิธีในการสกินแมวตัวนี้โดยเฉพาะ แต่นี่เป็นตรรกะที่ฉันใช้สำหรับการปรับใช้อินเตอร์เฟสของตัวเอง ฉันคิดว่าฉันชอบวิธีนี้และง่ายต่อการอ่านและใช้ (ตามที่คุณเห็นด้านบน) มันหมายถึงการเพิ่มวิธีการ 'นำไปใช้' Function.prototype
ซึ่งบางคนอาจมีปัญหา แต่ฉันคิดว่ามันใช้งานได้อย่างสวยงาม
Function.prototype.implement = function() {
// Loop through each interface passed in and then check
// that its members are implemented in the context object (this).
for(var i = 0; i < arguments.length; i++) {
// .. Check member's logic ..
}
// Remember to return the class being tested
return this;
}
var interf = arguments[i]; for (prop in interf) { if (this.prototype[prop] === undefined) { throw 'Member [' + prop + '] missing from class definition.'; }}
บางสิ่งบางอย่างตามสายของ ดูที่ด้านล่างของลิงค์บทความสำหรับตัวอย่างที่ซับซ้อนมากขึ้น
แม้ว่า JavaScript ไม่ได้มีinterface
ชนิดก็มักจะเป็นเวลาที่จำเป็น สำหรับเหตุผลที่เกี่ยวข้องกับลักษณะไดนามิกของ JavaScript และการใช้ Prototypical-Inheritance มันเป็นเรื่องยากที่จะมั่นใจได้ว่าอินเตอร์เฟซที่สอดคล้องกันในชั้นเรียน - อย่างไรก็ตามมันเป็นไปได้ที่จะทำเช่นนั้น และลอกเลียนแบบบ่อยครั้ง
ณ จุดนี้มีหลายวิธีที่จะเลียนแบบส่วนต่อประสานใน JavaScript ได้ ความแปรปรวนของวิธีการมักจะตอบสนองความต้องการบางอย่างในขณะที่คนอื่นไม่ได้รับภาระ บ่อยครั้งที่แนวทางที่แข็งแกร่งที่สุดนั้นยุ่งยากและมีอิทธิพลมากต่อผู้ดำเนินการ
นี่คือวิธีการในการเรียนอินเตอร์เฟซ / นามธรรมที่ไม่ยุ่งยากมากเป็นที่ชัดเจนทำให้การใช้งานภายใน Abstractions ให้น้อยที่สุดและออกจากห้องเพียงพอสำหรับวิธีการแบบไดนามิกหรือแบบกำหนดเอง:
function resolvePrecept(interfaceName) {
var interfaceName = interfaceName;
return function curry(value) {
/* throw new Error(interfaceName + ' requires an implementation for ...'); */
console.warn('%s requires an implementation for ...', interfaceName);
return value;
};
}
var iAbstractClass = function AbstractClass() {
var defaultTo = resolvePrecept('iAbstractClass');
this.datum1 = this.datum1 || defaultTo(new Number());
this.datum2 = this.datum2 || defaultTo(new String());
this.method1 = this.method1 || defaultTo(new Function('return new Boolean();'));
this.method2 = this.method2 || defaultTo(new Function('return new Object();'));
};
var ConcreteImplementation = function ConcreteImplementation() {
this.datum1 = 1;
this.datum2 = 'str';
this.method1 = function method1() {
return true;
};
this.method2 = function method2() {
return {};
};
//Applies Interface (Implement iAbstractClass Interface)
iAbstractClass.apply(this); // .call / .apply after precept definitions
};
ศีล Resolver
resolvePrecept
ฟังก์ชั่นเป็นสาธารณูปโภคและผู้ช่วยฟังก์ชั่นการใช้งานภายในของคุณบทคัดย่อชั้น งานของมันคือการอนุญาตให้มีการปรับแต่งการใช้งานการจัดการของห่อหุ้มศีล (ข้อมูลและพฤติกรรม) มันสามารถโยนข้อผิดพลาดหรือคำเตือน - และ - กำหนดค่าเริ่มต้นให้กับคลาส Implementor
iAbstractClass
iAbstractClass
กำหนดอินเตอร์เฟซที่จะใช้ วิธีการของมันสร้างข้อตกลงโดยนัยกับชั้น Implementor ของมัน อินเตอร์เฟซนี้ได้มอบหมายให้แต่ละศีลเดียวกัน namespace กฎเกณฑ์ที่แน่นอน - หรือ - เพื่ออะไรก็ตามศีล Resolverกลับมาทำงาน อย่างไรก็ตามข้อตกลงโดยปริยายแก้ไขเป็นบริบท - บทบัญญัติของ Implementor
implementor
Implementor เพียง 'ตกลง' กับอินเตอร์เฟซ ( iAbstractClassในกรณีนี้) และนำไปใช้โดยใช้สร้าง-หักหลังiAbstractClass.apply(this)
: โดยการกำหนดข้อมูลและพฤติกรรมด้านบนแล้วทำการจี้คอนสตรัคเตอร์ของอินเทอร์เฟซ - ผ่านบริบทของแอพพลิเคชั่นไปยังอินเทอร์เฟซคอนสตรัคเตอร์ - เราสามารถตรวจสอบให้แน่ใจว่า
นี่เป็นวิธีที่ไม่ยุ่งยากซึ่งให้บริการกับทีมของฉัน & ฉันทำได้ดีมากสำหรับเวลาและโครงการต่าง ๆ อย่างไรก็ตามมีข้อ จำกัด บางประการ
ข้อเสีย
แม้ว่าสิ่งนี้จะช่วยให้การใช้งานซอฟต์แวร์ของคุณมีความสม่ำเสมอ แต่ก็ไม่ได้ใช้อินเทอร์เฟซที่แท้จริง - แต่เลียนแบบพวกเขา แม้ว่าคำจำกัดความค่าเริ่มต้นและคำเตือนหรือข้อผิดพลาดจะได้รับการอธิบายการอธิบายการใช้งานนั้นได้รับการบังคับใช้และยืนยันโดยนักพัฒนา (เช่นเดียวกับการพัฒนา JavaScript จำนวนมาก)
นี่เป็นวิธีที่ดีที่สุดสำหรับ"การเชื่อมต่อใน JavaScript"แต่ฉันชอบที่จะเห็นการแก้ไขต่อไปนี้:
delete
การกระทำที่กล่าวว่าฉันหวังว่าสิ่งนี้จะช่วยคุณได้มากเท่ากับที่มีทีมของฉันและฉัน
คุณต้องการอินเตอร์เฟสใน Java เนื่องจากถูกพิมพ์แบบสแตติกและควรรู้จักสัญญาระหว่างคลาสในระหว่างการคอมไพล์ ใน JavaScript มันแตกต่างกัน JavaScript ถูกพิมพ์แบบไดนามิก หมายความว่าเมื่อคุณได้รับวัตถุคุณสามารถตรวจสอบว่ามันมีวิธีการเฉพาะและเรียกมันว่า
yourMethod
ที่ # 5 รายการในSuperclass
vtable ของและสำหรับแต่ละคลาสย่อยที่มีของตัวเองyourMethod
เพียงชี้รายการของคลาสย่อยที่ 5 ในการดำเนินการที่เหมาะสม
Implementation
ที่ใช้SomeInterface
ไม่เพียง แต่กล่าวว่ามันใช้อินเทอร์เฟซทั้งหมด มันมีข้อมูลที่ระบุว่า "ผมใช้SomeInterface.yourMethod
" Implementation.yourMethod
และจุดที่นิยามวิธีการ เมื่อโทร JVM จะมีลักษณะในชั้นเรียนสำหรับข้อมูลเกี่ยวกับการใช้งานของวิธีการอินเตอร์เฟซที่และพบว่าจะต้องมีการโทรSomeInterface.yourMethod
Implementation.yourMethod
หวังว่าทุกคนที่ยังคงมองหาคำตอบจะเป็นประโยชน์
คุณสามารถลองใช้พร็อกซี (เป็นมาตรฐานตั้งแต่ ECMAScript 2015): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
latLngLiteral = new Proxy({},{
set: function(obj, prop, val) {
//only these two properties can be set
if(['lng','lat'].indexOf(prop) == -1) {
throw new ReferenceError('Key must be "lat" or "lng"!');
}
//the dec format only accepts numbers
if(typeof val !== 'number') {
throw new TypeError('Value must be numeric');
}
//latitude is in range between 0 and 90
if(prop == 'lat' && !(0 < val && val < 90)) {
throw new RangeError('Position is out of range!');
}
//longitude is in range between 0 and 180
else if(prop == 'lng' && !(0 < val && val < 180)) {
throw new RangeError('Position is out of range!');
}
obj[prop] = val;
return true;
}
});
จากนั้นคุณสามารถพูดได้อย่างง่ายดาย:
myMap = {}
myMap.position = latLngLiteral;
เมื่อคุณต้องการใช้ transcompiler คุณสามารถลองใช้ TypeScript ได้ สนับสนุนคุณลักษณะ ECMA ฉบับร่าง (ในข้อเสนออินเทอร์เฟซที่เรียกว่า " โปรโตคอล ") คล้ายกับภาษาที่ชอบ coffeescript หรือ babel
ใน TypeScript อินเทอร์เฟซของคุณสามารถมีลักษณะดังนี้:
interface IMyInterface {
id: number; // TypeScript types are lowercase
name: string;
callback: (key: string; value: any; array: string[]) => void;
type: "test" | "notATest"; // so called "union type"
}
คุณทำอะไรไม่ได้:
ไม่มีอินเตอร์เฟสดั้งเดิมใน JavaScript มีหลายวิธีในการจำลองอินเตอร์เฟส ฉันได้เขียนแพ็คเกจที่ทำ
คุณสามารถดูการฝังที่นี่
Javascript ไม่มีส่วนต่อประสาน แต่มันสามารถพิมพ์เป็ด, ตัวอย่างสามารถพบได้ที่นี่:
http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html
ฉันรู้ว่านี่เป็นรุ่นเก่า แต่เมื่อเร็ว ๆ นี้ฉันพบว่าตัวเองต้องการมากขึ้นเพื่อให้มี API ที่ใช้งานง่ายสำหรับการตรวจสอบวัตถุกับส่วนต่อประสาน ดังนั้นฉันจึงเขียนสิ่งนี้: https://github.com/tomhicks/methodical
มันยังมีให้ผ่าน NPM: npm install methodical
โดยพื้นฐานแล้วมันทำทุกอย่างที่แนะนำข้างต้นโดยมีตัวเลือกบางอย่างสำหรับการเข้มงวดมากขึ้นเล็กน้อยและทั้งหมดนี้โดยไม่ต้องทำif (typeof x.method === 'function')
หม้อไอน้ำจำนวนมาก
หวังว่าบางคนจะพบว่ามีประโยชน์
นี่เป็นคำถามเก่า แต่หัวข้อนี้ไม่หยุดที่จะบักฉัน
จากคำตอบมากมายที่นี่และทั่วทั้งเว็บให้ความสำคัญกับ "การบังคับใช้" อินเทอร์เฟซฉันต้องการแนะนำมุมมองทางเลือก:
ฉันรู้สึกว่าไม่มีอินเทอร์เฟซมากที่สุดเมื่อฉันใช้หลายคลาสที่ทำงานคล้ายกัน (เช่นใช้อินเทอร์เฟซ )
ตัวอย่างเช่นฉันมีเครื่องมือสร้างอีเมลที่คาดว่าจะได้รับส่วนโรงงานอีเมลซึ่ง "รู้" ถึงวิธีสร้างเนื้อหาและ HTML ของส่วน ดังนั้นพวกเขาทุกคนต้องมีการจัดเรียงของบางส่วนgetContent(id)
และgetHtml(content)
วิธีการ
รูปแบบที่ใกล้เคียงที่สุดกับอินเตอร์เฟส (แม้ว่ามันจะยังคงเป็นวิธีแก้ปัญหา) ฉันคิดว่าใช้คลาสที่จะได้รับ 2 อาร์กิวเมนต์ซึ่งจะกำหนดวิธีการอินเทอร์เฟซ 2
ความท้าทายหลักของรูปแบบนี้ก็คือวิธีการนั้นจะต้องมีstatic
หรือเพื่อให้เป็นอาร์กิวเมนต์ตัวอินสแตนซ์ของตัวเองเพื่อที่จะเข้าถึงคุณสมบัติของมัน อย่างไรก็ตามมีหลายกรณีที่ฉันพบว่าการแลกเปลี่ยนนี้มีความยุ่งยาก
class Filterable {
constructor(data, { filter, toString }) {
this.data = data;
this.filter = filter;
this.toString = toString;
// You can also enforce here an Iterable interface, for example,
// which feels much more natural than having an external check
}
}
const evenNumbersList = new Filterable(
[1, 2, 3, 4, 5, 6], {
filter: (lst) => {
const evenElements = lst.data.filter(x => x % 2 === 0);
lst.data = evenElements;
},
toString: lst => `< ${lst.data.toString()} >`,
}
);
console.log('The whole list: ', evenNumbersList.toString(evenNumbersList));
evenNumbersList.filter(evenNumbersList);
console.log('The filtered list: ', evenNumbersList.toString(evenNumbersList));
ส่วนต่อประสานที่เป็นนามธรรมเช่นนี้
const MyInterface = {
serialize: () => {throw "must implement serialize for MyInterface types"},
print: () => console.log(this.serialize())
}
สร้างตัวอย่าง:
function MyType() {
this.serialize = () => "serialized "
}
MyType.prototype = MyInterface
และใช้มัน
let x = new MyType()
x.print()