คุณควรใช้เทคนิคในการแก้ปัญหาที่พวกเขาทำได้ดีเมื่อคุณมีปัญหาเหล่านั้น การผกผันของการพึ่งพาและการฉีดไม่แตกต่างกัน
การผกผันของการพึ่งพาหรือการฉีดเป็นเทคนิคที่ช่วยให้โค้ดของคุณตัดสินใจได้ว่าการใช้งานวิธีใดที่เรียกในเวลาทำงาน สิ่งนี้จะเพิ่มประโยชน์ของการผูกปลาย เทคนิคจำเป็นเมื่อภาษาไม่รองรับการแทนที่เวลาทำงานของฟังก์ชันที่ไม่ใช่อินสแตนซ์ ตัวอย่างเช่น Java ขาดกลไกเพื่อแทนที่การเรียกไปยังเมธอดแบบสแตติกด้วยการเรียกไปยังการปรับใช้อื่น ตรงกันข้ามกับ Python ซึ่งทั้งหมดที่จำเป็นในการแทนที่การเรียกใช้ฟังก์ชันคือการผูกชื่อกับฟังก์ชันอื่น (มอบหมายตัวแปรที่ถือฟังก์ชันใหม่)
เหตุใดเราต้องการเปลี่ยนแปลงการใช้งานฟังก์ชั่นต่าง ๆ ? มีสองเหตุผลหลัก:
- เราต้องการใช้ของปลอมในการทดสอบ สิ่งนี้ทำให้เราสามารถทดสอบคลาสที่ขึ้นอยู่กับการดึงฐานข้อมูลโดยไม่ต้องเชื่อมต่อกับฐานข้อมูลจริง
- เราจำเป็นต้องสนับสนุนการใช้งานหลายอย่าง ตัวอย่างเช่นเราอาจต้องตั้งค่าระบบที่รองรับฐานข้อมูล MySQL และ PostgreSQL
คุณอาจต้องการทราบการผกผันของคอนเทนเนอร์ควบคุม นี่คือเทคนิคที่มีจุดประสงค์เพื่อช่วยคุณหลีกเลี่ยงต้นไม้ก่อสร้างขนาดใหญ่ที่ยุ่งเหยิงที่มีลักษณะคล้ายปลอมนี้:
thing5 = new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());
myApp = new MyApp(
new MyAppDependency1(thing5, thing3),
new MyAppDependency2(
new Thing1(),
new Thing2(new Thing3(thing5, new Thing4(thing5)))
),
...
new MyAppDependency15(thing5)
);
ช่วยให้คุณสามารถลงทะเบียนชั้นเรียนของคุณแล้วสร้างสิ่งต่อไปนี้ให้คุณ:
injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);
myApp = injector.create(MyApp); // The injector fills in all the construction parameters.
โปรดทราบว่ามันง่ายที่สุดถ้าคลาสที่ลงทะเบียนสามารถเป็นซิงเกิลไร้สัญชาติได้
คำเตือน
โปรดทราบว่าการผกผันของการพึ่งพาไม่ควรเป็นคำตอบแบบไปสู่ตรรกะลอจิกของคุณ มองหาโอกาสในการใช้การกำหนดพารามิเตอร์แทน พิจารณาเมธอด pseudocode นี้เช่น:
myAverageAboveMin()
{
dbConn = new DbConnection("my connection string");
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", 5);
dbQuery.Execute();
myData = dbQuery.getAll();
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
เราสามารถใช้การผกผันของการพึ่งพาสำหรับบางส่วนของวิธีนี้:
class MyQuerier
{
private _dbConn;
MyQueries(dbConn) { this._dbConn = dbConn; }
fetchAboveMin(min)
{
dbQuery = this._dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
private _querier;
Averager(querier) { this._querier = querier; }
myAverageAboveMin(min)
{
myData = this._querier.fetchAboveMin(min);
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
แต่เราไม่ควรอย่างน้อยก็ไม่สมบูรณ์ แจ้งให้ทราบว่าเราได้สร้างstatefulQuerier
ชั้นเรียนกับ ตอนนี้มันมีการอ้างอิงถึงวัตถุเชื่อมต่อทั่วโลกเป็นหลัก สิ่งนี้จะสร้างปัญหาเช่นความยากลำบากในการทำความเข้าใจสถานะโดยรวมของโปรแกรมและวิธีการที่ชั้นเรียนที่แตกต่างประสานงานกัน โปรดสังเกตว่าเราถูกบังคับให้ปลอมออกจากการสอบถามหรือการเชื่อมต่อหากเราต้องการทดสอบตรรกะเฉลี่ย วิธีที่ดีกว่าคือการเพิ่มการกำหนดพารามิเตอร์ :
class MyQuerier
{
fetchAboveMin(dbConn, min)
{
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
averageData(myData)
{
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
class StuffDoer
{
private _querier;
private _averager;
StuffDoer(querier, averager)
{
this._querier = querier;
this._averager = averager;
}
myAverageAboveMin(dbConn, min)
{
myData = this._querier.fetchAboveMin(dbConn, min);
return this._averager.averageData(myData);
}
}
และการเชื่อมต่อจะได้รับการจัดการในระดับที่สูงกว่าซึ่งรับผิดชอบการดำเนินงานโดยรวมและรู้ว่าต้องทำอะไรกับผลลัพธ์นี้
ตอนนี้เราสามารถทดสอบตรรกะการหาค่าเฉลี่ยได้อย่างอิสระโดยสมบูรณ์จากการสอบถามและยิ่งไปกว่านั้นเราสามารถใช้มันในสถานการณ์ที่หลากหลาย เราอาจถามว่าเราต้องการMyQuerier
และAverager
วัตถุหรือไม่และบางทีคำตอบก็คือเราไม่ได้ถ้าเราไม่ต้องการทดสอบหน่วยStuffDoer
และไม่ทดสอบหน่วยStuffDoer
จะสมเหตุสมผลอย่างสมบูรณ์เพราะมันเชื่อมโยงกับฐานข้อมูลอย่างแน่นหนา มันอาจสมเหตุสมผลมากกว่าที่จะเพียงแค่ให้การทดสอบการรวมระบบครอบคลุม ในกรณีนั้นเราอาจทำอย่างดีfetchAboveMin
และaverageData
เป็นวิธีคงที่