ฉันรู้ว่ากระทู้นี้ค่อนข้างเก่าในตอนนี้ แต่ฉันคิดว่าฉันจะพูดสอดกับความคิดของฉันในเรื่องนี้ TL; DR นั้นเป็นเพราะธรรมชาติของ JavaScript ที่ไม่มีการพิมพ์คุณสามารถทำได้ค่อนข้างมากโดยไม่ต้องพึ่งพารูปแบบการพึ่งพา (DI) หรือใช้เฟรมเวิร์ก DI อย่างไรก็ตามเนื่องจากแอปพลิเคชันมีขนาดใหญ่ขึ้นและมีความซับซ้อนมากขึ้น DI สามารถช่วยบำรุงรักษาโค้ดของคุณได้อย่างแน่นอน
DI ใน C #
เพื่อให้เข้าใจว่าทำไม DI จึงไม่ใช่ความต้องการที่ยิ่งใหญ่ใน JavaScript มันมีประโยชน์ที่จะดูภาษาที่พิมพ์ได้ดีเช่น C # (ขออภัยสำหรับผู้ที่ไม่รู้จัก C # แต่ควรจะง่ายพอที่จะติดตาม) สมมติว่าเรามีแอพที่อธิบายรถยนต์และแตรของมัน คุณจะกำหนดสองคลาส:
class Horn
{
public void Honk()
{
Console.WriteLine("beep!");
}
}
class Car
{
private Horn horn;
public Car()
{
this.horn = new Horn();
}
public void HonkHorn()
{
this.horn.Honk();
}
}
class Program
{
static void Main()
{
var car = new Car();
car.HonkHorn();
}
}
มีปัญหาเล็กน้อยในการเขียนโค้ดด้วยวิธีนี้
Car
ชั้นคู่แน่นในการดำเนินงานโดยเฉพาะอย่างยิ่งของฮอร์นในHorn
ชั้นเรียน หากเราต้องการเปลี่ยนประเภทของฮอร์นที่ใช้โดยรถยนต์เราต้องแก้ไขCar
คลาสแม้ว่าการใช้ฮอร์นจะไม่เปลี่ยนแปลง สิ่งนี้ทำให้การทดสอบทำได้ยากเพราะเราไม่สามารถทดสอบCar
ชั้นเรียนแยกออกจากHorn
ชั้นเรียนได้
Car
ระดับเป็นผู้รับผิดชอบสำหรับการดำเนินงานของHorn
ระดับ ในตัวอย่างง่ายๆเช่นนี้มันไม่ใช่ปัญหาใหญ่ แต่ในการพึ่งพาแอปพลิเคชันจริงจะมีการพึ่งพาซึ่งจะมีการพึ่งพา ฯลฯCar
ชั้นจะต้องรับผิดชอบในการสร้างต้นไม้ทั้งหมดของการพึ่งพา สิ่งนี้ไม่เพียง แต่ซับซ้อนและซ้ำซาก แต่เป็นการละเมิด "ความรับผิดชอบเดี่ยว" ของชั้นเรียน ควรเน้นที่การเป็นรถยนต์ไม่ใช่การสร้างอินสแตนซ์
- ไม่มีวิธีที่จะใช้อินสแตนซ์การพึ่งพาเดียวกันซ้ำได้ สิ่งนี้ไม่สำคัญในแอปพลิเคชันของเล่นนี้ แต่ให้พิจารณาการเชื่อมต่อฐานข้อมูล โดยทั่วไปแล้วคุณจะมีอินสแตนซ์เดียวที่แชร์ผ่านแอปพลิเคชันของคุณ
ทีนี้มาลองปรับเปลี่ยนสิ่งนี้เพื่อใช้รูปแบบการฉีดพึ่งพา
interface IHorn
{
void Honk();
}
class Horn : IHorn
{
public void Honk()
{
Console.WriteLine("beep!");
}
}
class Car
{
private IHorn horn;
public Car(IHorn horn)
{
this.horn = horn;
}
public void HonkHorn()
{
this.horn.Honk();
}
}
class Program
{
static void Main()
{
var horn = new Horn();
var car = new Car(horn);
car.HonkHorn();
}
}
เราได้ทำสองสิ่งสำคัญที่นี่ อันดับแรกเราได้แนะนำอินเทอร์เฟซที่Horn
คลาสของเราใช้ สิ่งนี้ช่วยให้เราเขียนรหัสCar
คลาสไปยังอินเทอร์เฟซแทนการใช้งานเฉพาะ IHorn
ตอนนี้รหัสอาจจะใช้สิ่งที่ดำเนินการ ประการที่สองเราได้ดึงอินสแตนซ์ของฮอร์นออกCar
แล้วผ่านเข้าไปแทน วิธีนี้จะช่วยแก้ไขปัญหาข้างต้นและนำไปไว้ที่ฟังก์ชั่นหลักของแอปพลิเคชันเพื่อจัดการอินสแตนซ์และวงจรชีวิตของพวกเขา
สิ่งนี้หมายความว่าอะไรที่จะสามารถแนะนำฮอร์นชนิดใหม่สำหรับให้รถใช้โดยไม่ต้องสัมผัสCar
ชั้นเรียน:
class FrenchHorn : IHorn
{
public void Honk()
{
Console.WriteLine("le beep!");
}
}
หลักสามารถแทรกตัวอย่างของFrenchHorn
คลาสแทน นอกจากนี้ยังช่วยลดความยุ่งยากในการทดสอบ คุณสามารถสร้างMockHorn
ชั้นเรียนเพื่อแทรกเข้าไปในตัวCar
สร้างเพื่อให้แน่ใจว่าคุณกำลังทดสอบเพียงCar
ชั้นแยก
ตัวอย่างข้างต้นแสดงการฉีดขึ้นต่อกันแบบแมนนวล โดยทั่วไปแล้ว DI จะทำกับเฟรมเวิร์ก (เช่นUnityหรือNinjectใน C # world) เฟรมเวิร์กเหล่านี้จะทำการเดินสายอ้างอิงทั้งหมดให้คุณโดยการเดินกราฟอ้างอิงและสร้างอินสแตนซ์ตามต้องการ
วิธีมาตรฐาน Node.js
ตอนนี้เรามาดูตัวอย่างเดียวกันใน Node.js เราอาจแบ่งรหัสของเราออกเป็น 3 โมดูล:
// horn.js
module.exports = {
honk: function () {
console.log("beep!");
}
};
// car.js
var horn = require("./horn");
module.exports = {
honkHorn: function () {
horn.honk();
}
};
// index.js
var car = require("./car");
car.honkHorn();
เนื่องจาก JavaScript ถูกพิมพ์ออกมาเราจึงไม่มีข้อต่อแน่นเหมือนที่เราเคยทำมาก่อน ไม่จำเป็นต้องมีอินเตอร์เฟส (หรือไม่มีอยู่) เนื่องจากcar
โมดูลจะพยายามเรียกhonk
เมธอดบนสิ่งที่horn
โมดูลส่งออก
นอกจากนี้เนื่องจากโหนดrequire
เก็บทุกอย่างโมดูลจะถูกเก็บไว้ในคอนเทนเนอร์เป็นหลัก โมดูลอื่น ๆ ที่ทำงานrequire
บนhorn
โมดูลจะได้รับอินสแตนซ์เดียวกันแน่นอน ทำให้การแชร์วัตถุแบบซิงเกิลเช่นการเชื่อมต่อฐานข้อมูลทำได้ง่ายมาก
ตอนนี้ยังมีปัญหาที่ว่าโมดูลเป็นผู้รับผิดชอบสำหรับการเรียกพึ่งพาของตัวเองcar
horn
หากคุณต้องการให้รถใช้โมดูลที่แตกต่างกันสำหรับแตรของมันคุณจะต้องเปลี่ยนrequire
คำสั่งในcar
โมดูล สิ่งนี้ไม่ใช่สิ่งที่ทำกันมากนัก แต่มันทำให้เกิดปัญหากับการทดสอบ
วิธีที่ผู้คนปกติจัดการกับปัญหาที่เกิดขึ้นกับการทดสอบเป็นproxyquire เนื่องจากลักษณะของ JavaScript แบบไดนามิก proxyquire จะสกัดกั้นการโทรที่ต้องการและส่งคืน stubs / mocks ใด ๆ ที่คุณให้ไว้แทน
var proxyquire = require('proxyquire');
var hornStub = {
honk: function () {
console.log("test beep!");
}
};
var car = proxyquire('./car', { './horn': hornStub });
// Now make test assertions on car...
นี่เกินพอสำหรับแอปพลิเคชั่นส่วนใหญ่ ถ้ามันใช้ได้กับแอปของคุณให้ไปกับมัน อย่างไรก็ตามในประสบการณ์ของฉันเมื่อแอปพลิเคชันขยายใหญ่ขึ้นและมีความซับซ้อนมากขึ้นการบำรุงรักษารหัสเช่นนี้จะกลายเป็นเรื่องยาก
DI ใน JavaScript
Node.js ยืดหยุ่นได้มาก หากคุณไม่พอใจกับวิธีการข้างต้นคุณสามารถเขียนโมดูลของคุณโดยใช้รูปแบบการฉีดพึ่งพา ในรูปแบบนี้ทุกโมดูลส่งออกฟังก์ชั่นโรงงาน (หรือตัวสร้างคลาส)
// horn.js
module.exports = function () {
return {
honk: function () {
console.log("beep!");
}
};
};
// car.js
module.exports = function (horn) {
return {
honkHorn: function () {
horn.honk();
}
};
};
// index.js
var horn = require("./horn")();
var car = require("./car")(horn);
car.honkHorn();
นี่คล้ายกับวิธี C # มากก่อนหน้านี้ในindex.js
โมดูลที่รับผิดชอบวงจรชีวิตและการเดินสายเช่น การทดสอบหน่วยนั้นค่อนข้างง่ายเนื่องจากคุณสามารถส่งผ่าน mocks / stubs ไปยังฟังก์ชั่นได้ หากนี่เป็นสิ่งที่ดีพอสำหรับแอปพลิเคชันของคุณ
Bolus DI Framework
แตกต่างจาก C # ไม่มีกรอบ DI มาตรฐานที่จัดตั้งขึ้นเพื่อช่วยในการจัดการการพึ่งพาของคุณ มีเฟรมเวิร์กจำนวนมากในรีจิสทรี npm แต่ไม่มีการยอมรับอย่างกว้างขวาง ตัวเลือกเหล่านี้จำนวนมากได้ถูกอ้างถึงแล้วในคำตอบอื่น ๆ
ผมไม่ได้มีความสุขโดยเฉพาะอย่างยิ่งกับใด ๆ ของตัวเลือกที่ใช้ได้ดังนั้นฉันเขียนของตัวเองที่เรียกว่ายาลูกกลอน ยาลูกกลอนถูกออกแบบมาเพื่อทำงานกับรหัสที่เขียนในรูปแบบ DI ข้างต้นและพยายามที่จะแห้งมากและง่ายมาก ด้วยการใช้โมดูลcar.js
และhorn.js
โมดูลข้างต้นที่แน่นอนคุณสามารถเขียนindex.js
โมดูลใหม่ได้ด้วย bolus ดังนี้
// index.js
var Injector = require("bolus");
var injector = new Injector();
injector.registerPath("**/*.js");
var car = injector.resolve("car");
car.honkHorn();
แนวคิดพื้นฐานคือคุณสร้างหัวฉีด คุณลงทะเบียนโมดูลทั้งหมดของคุณในหัวฉีด จากนั้นคุณก็แก้ไขสิ่งที่คุณต้องการ ยาลูกกลอนจะเดินกราฟการพึ่งพาและสร้างและฉีดการพึ่งพาตามความจำเป็น คุณไม่ได้ประหยัดมากนักในตัวอย่างของเล่นเช่นนี้ แต่ในแอปพลิเคชันขนาดใหญ่ที่มีต้นไม้ที่พึ่งพาได้ซับซ้อนการออมมีขนาดใหญ่
Bolus รองรับฟีเจอร์มากมายเช่นการอ้างอิงเสริมและการทดสอบรอบตัว แต่มีประโยชน์หลักสองประการที่ฉันได้เห็นเมื่อเทียบกับวิธี Node.js มาตรฐาน ก่อนอื่นถ้าคุณมีแอปพลิเคชั่นที่คล้ายกันจำนวนมากคุณสามารถสร้างโมดูล npm ส่วนตัวสำหรับฐานของคุณที่สร้างหัวฉีดและลงทะเบียนวัตถุที่มีประโยชน์บนมัน จากนั้นแอปเฉพาะของคุณสามารถเพิ่มแทนที่และแก้ไขได้ตามต้องการเช่นเดียวกับวิธีการใช้AngularJSหัวฉีดทำงาน ประการที่สองคุณสามารถใช้ยาลูกกลอนเพื่อจัดการบริบทต่างๆของการพึ่งพา ตัวอย่างเช่นคุณสามารถใช้มิดเดิลแวร์เพื่อสร้าง child injector ต่อการร้องขอลงทะเบียน user id, session id, logger และอื่น ๆ บน injector พร้อมกับโมดูลใด ๆ จากนั้นแก้ไขสิ่งที่คุณต้องการเพื่อให้บริการตามคำขอ สิ่งนี้จะให้อินสแตนซ์ของโมดูลของคุณต่อการร้องขอและป้องกันไม่ให้ส่งผ่านตัวบันทึก ฯลฯ พร้อมกับการเรียกฟังก์ชันโมดูลทุกครั้ง