Inversion of Control (IoC) คืออะไร
IoC เป็นทฤษฏีที่ว่าด้วย การลดความผูกมัด (dependency) กันในระหว่าง module เพื่อให้ application ของเราแก้ไข (maintain) ต่อเติม (extensible) หรือทดสอบ (test) ได้ง่ายขึ้น
ซึ่งเอาจริงๆ IoC เป็นอะไรที่ทำให้เราสับสนและงุนงงมากๆ ว่ามันคืออะไร หลายๆ คนจึงยกให้ว่า IoC คือ Dependency Injection (DI) ซึ่งจริงๆ มันก็ไม่ถูกซะทีเดียว
Dependency คืออะไร
Dependency คือการผูกมัดที่เกิดขึ้นในระบบ เมื่อ module นึงมีการเรียกใช้อีก module นึงด้วยการอ้างอิง (reference) ตรงๆ
แล้วอะไรที่เรียกว่าการ อ้างอิง (Reference) แบบตรงๆ
อย่างภาพ diagram ด้านบน class LogEngine มีการเรียกใช้ ConsoleLog โดยตรง ซึ่งมองผ่าน diagram อาจจะไม่เห็นภาพลองดู code กัน
จาก code จะเห็นว่าเรามี class LogEngine เป็นตัวกลางในการพ่น log ลง Console (ConsoleLog) เชื่อว่าหลายคนก็คงงงว่ามันมีการผูกมัดอะไรตรงไหน ข้อเสียมันคืออะไร ทำไมถึงต้องเป็นเรื่องที่ต้องกังวล
จุดที่มีการผูกมัด อยู่ที่ class LogEngine ที่มีการเรียกใช้ class ConsoleLog โดยตรง ทำให้เมื่อมีการสร้าง object ของ class LogEngine จะต้องสร้าง object ConsoleLog ด้วยเสมอ ตรงนี้คือส่วนที่ทำให้ 2 class ผูกติดกันอย่างแน่นหนา (Tightly Coupling)
ข้อเสียการผูกมัดแบบนี้ จะทำให้การแก้ไขหรือต่อขยายในภายภาคหน้านั้นเป็นไปได้ยาก ยกตัวอย่างเช่นเราต้องการขยายระบบ log ให้สามารถบันทึกลงไฟล์ (FileLog) หรือ ฐานข้อมูล (DatabaseLog) เชื่อว่าหลายคนยังไม่เห็นภาพ เรามีตัวอย่างให้ดู
วิธีแก้ปัญหา
ตามหลักการของ D (Dependency Inversion) ในหลักการพัฒนา application ในรูปแบบ SOLID ของ Robert C. Martin ได้กล่าวว่า
แปลเป็นไทยแบบง่อยๆ ก็ได้ใจความประมาณว่า
จากหลักการด้านบนนั้นหมายความว่า การจะลดการผูกมัดระหว่าง module ต้องใช้ abstraction เข้ามาเป็นตัวกลางสำหรับการเรียกใช้ระหว่าง module
เชื่อว่าหลายคนยังคงงงอยู่ ทำไมต้องเอา abstraction มาคั่นกลาง ให้เราลองคิดตามกันดู สมมุติว่าเรามี class 2 ตัว X และ Y โดยที่ X นั้นเรียกใช้ Y ซึ่งมันก็เป็นไปตามกลไกปกติ ถ้าหาก X จะใช้อะไรบางสิ่งใน Y ไม่ว่าจะเป็น method หรือ properties ยังไงก็ต้องเรียก Y แต่มีคำถามว่า X จำเป็นรู้ถึงรายละเอียดในการทำงานของ Y สำหรับการเรียกใช้ Y หรือไม่
จริงๆ แล้ว X ไม่มีความจำเป็นต้องรู้ลงลึกไปถึงรายละเอียดของ Y ไม่ต้องรู้เลยว่า Y ทำอะไรยังไงถึงได้ผลลัพธ์ที่ X ต้องการออกมา ดังนั้นจึงมีแนวคิดที่ให้ abstraction เป็นตัวกลางระหว่าง X และ Y โดย abstraction นั้นจะมีรายละเอียดว่า ถ้า X ต้องการใช้ Y ต้องทำยังไง ต้องส่ง argument อะไร
ในรูปด้านบน Y นั้นได้ implement interface มาจาก I และ X ได้เรียกใช้ Y ผ่าน I โดยที่ X นั้นไม่รู้เลยว่ากำลังเรียกใช้ Y อยู่ แค่รู้ว่ากระทำผ่านตัวกลางอย่าง I
จากที่กล่าวไว้ด้านบนเราลองมาดูในตัวอย่างจริงๆ กันว่าจะมีหน้าตาเป็นยังไงดัง diagram ด้านล่างนี้
คำว่า abstractions ในความหมายข้างบนหมายถึง interface ในภาษา Java โดย class LogEngine จะไม่ได้เรียกใช้ class ConsoleLog หรือ FileLog ตรงๆ แต่จะเรียกใช้ผ่านตัวกลางอย่าง interface ILog แทน และนี่คือหน้าตาของ code เมื่อเราได้มีการปรับปรุง
Dependency Injection (DI)
DI เป็นเทคนิคที่ว่าด้วยการจัดหาหรือการเตรียม object ที่มีการผูกมัด (dependency object) ให้กับ object ที่มีการเรียกใช้ โดย object ที่เรียกใช้ไม่จำเป็นต้องสร้าง dependency object ขึ้นมาเอง เพราะ dependency object จะถูกฉีดเข้าไปให้เอง
DI ก็เป็นเพียง 1 ในวิธีที่ใช้แก้ปัญหาการผูกมัดกันภายในระบบตามหลักการของ IoC ซึ่งวิธีการแก้ปัญหานั้นมีหลายวิธี (ดังรูปด้านบน) แต่เราหยิบยก DI ขึ้นมากล่าวถึงเพียงอย่างเดียว เพราะหลักการของ DI นั้นเป็นที่นิยมและถูกนำไปสร้างเป็น library หรือ framework ในหลายๆ ภาษา โดยวิธีการของ DI นั้นยังแตกแยกย่อยออกไปอีก 3 วิธี
Constructor Injection
เป็นวิธีการฉีด dependency object ผ่านทาง constructor ของ class ที่จะเรียกใช้ dependency object โดย constructor จะรับ argument ที่เป็น interface ของ class ที่เป็น dependency object
ตามข้อกำหนดด้านบน ในกรณีนี้เราจะต้องแก้ class LogEngine ให้มี constructor ที่มี argument เป็น interface ของ ILog ตาม code ด้านล่าง
ในภายภาคหน้าไม่ว่าเราจะมีการเพิ่มประเภท log อีกสักกี่ตัว class LogEngine เราก็แทบไม่ต้องแก้ไขเลย ยกเว้นจะมีการเปลี่ยนแปลงทางโครงสร้างของ interface ILog
ข้อควรระวังของ Constructor Injection
Setter Injection
เป็นวิธีที่ฉีด dependency object ผ่านทาง setter method วิธีนี้ดีกว่าแบบแรกตรงที่ยืดหยุ่นกว่า constructor injection เพราะสามารถเปลี่ยน dependency object ได้ตลอดเวลาผ่านทาง setter method โดยที่ไม่ต้องสร้าง object ขึ้นมาใหม่
เป็นวิธีที่ฉีด dependency object ไปยัง method ที่มีการเรียกใช้โดยตรงเลย วิธีนี้เป็นวิธีที่ยืดหยุ่นที่สุด แต่มัันจะเป็นเรื่องน่ารำคาญเพราะต้องฉีด object เข้าไปทุกครั้งที่จะมีการเรียกใช้ method ที่ถูกทำการฉีดเข้าไป
ใครเป็นคนสร้าง dependency object?
หลายคนคงสงสัยว่าแล้วใครจะเป็นสร้าง dependency object แล้วใครเป็นผู้ฉีด object เข้าไป ซึ่งถ้าเราไม่ได้ใช้ library หรือ framework อย่าง Spring Framework, Salta หรือ Google Guice นักพัฒนาจะต้องเป็นคนสร้าง dependency object และเป็นคนฉีดเข้าไปเอง
ถามว่าควรจะสร้าง dependency object ที่ไหน เพราะจากที่กล่าวไปทั้งหมด ถ้าหากเราสร้าง object ตรงๆ เท่ากับเป็นการสร้างการผูกมัด คำตอบคือควรจะสร้างที่ส่วนที่อยู่บนที่สุดที่เกี่ยวกับ module เป็นจุดที่สร้าง object เพราะว่ายังไงการสร้าง application ใดๆ ขึ้นมาก็ต้องมีส่วนที่จะร้อยเรียง module ย่อยๆ ขึ้นมาประกอบให้เป็น application อยู่แล้ว อย่างในตัวอย่างเราก็จะสร้าง dependency object ใน main method
IoC คือหลักการที่ไม่ให้ object สร้าง dependency object ที่จะเรียกใช้เองโดยตรง เพื่อหลีกเลี่ยงการผูกมัดกันระหว่างทั้ง 2 object โดย dependency object จะถูกขึ้นมาจากภายนอก แล้วนำมาฉีดลงไปใน object ที่ต้องการจะใช้จากภายนอก
DI คือวิธีการที่ทำให้หลักการของ IoC นั้นเกิดขึ้นจริง
จะเห็นได้ว่าหลักการของ IoC เป็นตัวช่วยให้การพัฒนา application ของเรานั้นยืดหยุ่นขึ้นกว่าเดิม สามารถขยับขยายได้ง่ายขึ้น แต่ใช่ว่าหลักการของ IoC นั้นจะไม่มีข้อเสีย
นั่นแหละจึงเป็นเหตุผลว่าทำไม Spring ถึง learning curve สูงงงงงงงง
อ้างอิง
IoC เป็นทฤษฏีที่ว่าด้วย การลดความผูกมัด (dependency) กันในระหว่าง module เพื่อให้ application ของเราแก้ไข (maintain) ต่อเติม (extensible) หรือทดสอบ (test) ได้ง่ายขึ้น
ซึ่งเอาจริงๆ IoC เป็นอะไรที่ทำให้เราสับสนและงุนงงมากๆ ว่ามันคืออะไร หลายๆ คนจึงยกให้ว่า IoC คือ Dependency Injection (DI) ซึ่งจริงๆ มันก็ไม่ถูกซะทีเดียว
Dependency คืออะไร
Dependency คือการผูกมัดที่เกิดขึ้นในระบบ เมื่อ module นึงมีการเรียกใช้อีก module นึงด้วยการอ้างอิง (reference) ตรงๆ
แล้วอะไรที่เรียกว่าการ อ้างอิง (Reference) แบบตรงๆ
อย่างภาพ diagram ด้านบน class LogEngine มีการเรียกใช้ ConsoleLog โดยตรง ซึ่งมองผ่าน diagram อาจจะไม่เห็นภาพลองดู code กัน
public class ConsoleLog { public void openLog(){ //do something to open log } public void log(String message){ //do something to log } public void closeLog(){ //do something to close log } }
public class LogEngine { private ConsoleLog log; public LogEngine(){ log = new ConsoleLog(); //this is dependency } public void log(String message){ log.openLog(); log.log(message); log.closeLog(); } }
จาก code จะเห็นว่าเรามี class LogEngine เป็นตัวกลางในการพ่น log ลง Console (ConsoleLog) เชื่อว่าหลายคนก็คงงงว่ามันมีการผูกมัดอะไรตรงไหน ข้อเสียมันคืออะไร ทำไมถึงต้องเป็นเรื่องที่ต้องกังวล
จุดที่มีการผูกมัด อยู่ที่ class LogEngine ที่มีการเรียกใช้ class ConsoleLog โดยตรง ทำให้เมื่อมีการสร้าง object ของ class LogEngine จะต้องสร้าง object ConsoleLog ด้วยเสมอ ตรงนี้คือส่วนที่ทำให้ 2 class ผูกติดกันอย่างแน่นหนา (Tightly Coupling)
ข้อเสียการผูกมัดแบบนี้ จะทำให้การแก้ไขหรือต่อขยายในภายภาคหน้านั้นเป็นไปได้ยาก ยกตัวอย่างเช่นเราต้องการขยายระบบ log ให้สามารถบันทึกลงไฟล์ (FileLog) หรือ ฐานข้อมูล (DatabaseLog) เชื่อว่าหลายคนยังไม่เห็นภาพ เรามีตัวอย่างให้ดู
import java.io.*; public class FileLog { private BufferedWriter logWriter; private String logPath; public FileLog(String logPath){ this.logPath = logPath; } public void openLog() throws IOException{ try { logWriter = new BufferedWriter(new FileWriter(logPath)); }catch (IOException ex){ System.err.printf("Can't initial log file in this path [%s] \n", logPath); throw ex; } } public void log(String message)throws IOException{ logWriter.write(message); logWriter.newLine(); } public void closeLog() throws IOException{ logWriter.flush(); logWriter.close(); } }class FileLog ดูเหมือนจะไม่มีอะไรแปลกประหลาด แต่จะเกิดปัญหาเมื่ือนำไปใช้กับ class LogEngine ซึ่งเป็นตัวกลางในการจัดการ log อาจจะยังเห็นภาพไม่ชัด ลองไปดูในตัวอย่าง code ด้านล่าง
import java.io.IOException; public class LogEngine { private ConsoleLog consoleLog; private FileLog fileLog; public LogEngine(){ consoleLog = new ConsoleLog(); } public LogEngine(String logPath){ fileLog = new FileLog(logPath); } public void log(String message) throws IOException{ if(consoleLog != null) { consoleLog.openLog(); consoleLog.log(message); consoleLog.closeLog(); }else if(fileLog != null){ fileLog.openLog(); fileLog.log(message); fileLog.closeLog(); } } }จาก code ด้านบนจะเห็นว่าทุกครั้งที่มีการเพิ่มประเภทของ log เราต้องแก้ไข code ของ class LogEngine ที่เป็นเช่นนั้นเพราะว่าโครงสร้างของ log ที่เราสร้างมาตั้งแต่แรกนั้นมีการผูกมัด (dependency) ที่แน่นหนา ทำให้การแก้ไขหรือการขยับขยายเป็นไปได้ยาก
วิธีแก้ปัญหา
ตามหลักการของ D (Dependency Inversion) ในหลักการพัฒนา application ในรูปแบบ SOLID ของ Robert C. Martin ได้กล่าวว่า
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
แปลเป็นไทยแบบง่อยๆ ก็ได้ใจความประมาณว่า
module ในระดับสูงไม่ควรที่จะผูกมัดกับ module ในระดับล่าง ทั้งสอง module ควรจะมีความสัมพันธ์กันผ่าน abstractions abstractions ไม่ควรจะมีความสัมพันธ์ที่ลงลึกไปถึงรายละเอียด แต่รายละเอียดจะไปผูกกับ abstractions เอง
จากหลักการด้านบนนั้นหมายความว่า การจะลดการผูกมัดระหว่าง module ต้องใช้ abstraction เข้ามาเป็นตัวกลางสำหรับการเรียกใช้ระหว่าง module
เชื่อว่าหลายคนยังคงงงอยู่ ทำไมต้องเอา abstraction มาคั่นกลาง ให้เราลองคิดตามกันดู สมมุติว่าเรามี class 2 ตัว X และ Y โดยที่ X นั้นเรียกใช้ Y ซึ่งมันก็เป็นไปตามกลไกปกติ ถ้าหาก X จะใช้อะไรบางสิ่งใน Y ไม่ว่าจะเป็น method หรือ properties ยังไงก็ต้องเรียก Y แต่มีคำถามว่า X จำเป็นรู้ถึงรายละเอียดในการทำงานของ Y สำหรับการเรียกใช้ Y หรือไม่
จริงๆ แล้ว X ไม่มีความจำเป็นต้องรู้ลงลึกไปถึงรายละเอียดของ Y ไม่ต้องรู้เลยว่า Y ทำอะไรยังไงถึงได้ผลลัพธ์ที่ X ต้องการออกมา ดังนั้นจึงมีแนวคิดที่ให้ abstraction เป็นตัวกลางระหว่าง X และ Y โดย abstraction นั้นจะมีรายละเอียดว่า ถ้า X ต้องการใช้ Y ต้องทำยังไง ต้องส่ง argument อะไร
ในรูปด้านบน Y นั้นได้ implement interface มาจาก I และ X ได้เรียกใช้ Y ผ่าน I โดยที่ X นั้นไม่รู้เลยว่ากำลังเรียกใช้ Y อยู่ แค่รู้ว่ากระทำผ่านตัวกลางอย่าง I
จากที่กล่าวไว้ด้านบนเราลองมาดูในตัวอย่างจริงๆ กันว่าจะมีหน้าตาเป็นยังไงดัง diagram ด้านล่างนี้
คำว่า abstractions ในความหมายข้างบนหมายถึง interface ในภาษา Java โดย class LogEngine จะไม่ได้เรียกใช้ class ConsoleLog หรือ FileLog ตรงๆ แต่จะเรียกใช้ผ่านตัวกลางอย่าง interface ILog แทน และนี่คือหน้าตาของ code เมื่อเราได้มีการปรับปรุง
public interface ILog { void openLog() throws Exception; void log(String message) throws Exception; void closeLog() throws Exception; }
public class LogEngine { private ILog log; public LogEngine(){ log = new ConsoleLog(); //dependency } public LogEngine(String logPath){ log = new FileLog(logPath); //dependency } public void log(String message) throws Exception{ log.openLog(); log.log(message); log.closeLog(); } }แต่ถึงเราจะออกแบบ class ของเราให้มีโครงสร้างที่ตามหลักการที่จะลดการผูกมัดในระบบแล้ว และทำให้ method log() ใน class LogEngine นั้นดูสะอาดขึ้น แต่ว่าเมื่อเรามาลองดู code กลับพบว่ายังมีการผูกมัดกันระหว่าง LogEngine และ ConsoleLog, FileLog ยังคงอยู่เหมือนเดิม ที่ยังเป็นเช่นนั้นเพราะเราเรียกใช้ผิดวิธีอยู่ เพราะการสร้าง object ConsoleLog และ FileLog ภายใน class ของ LogEngine เป็นจุดที่ทำให้เกิดการผูกมัดกัันในระบบ ดังนั้นการแก้ไขก็คือห้ามมีการสร้าง object ที่ต้องการใช้ขึ้นมาตรงๆ แต่ให้ทำการฉีด (inject) object ที่จะใช้เข้ามาแทน และวิธีนี้นี่เองที่เค้าเรียกว่า Dependency Injection (DI)
Dependency Injection (DI)
DI เป็นเทคนิคที่ว่าด้วยการจัดหาหรือการเตรียม object ที่มีการผูกมัด (dependency object) ให้กับ object ที่มีการเรียกใช้ โดย object ที่เรียกใช้ไม่จำเป็นต้องสร้าง dependency object ขึ้นมาเอง เพราะ dependency object จะถูกฉีดเข้าไปให้เอง
DI ก็เป็นเพียง 1 ในวิธีที่ใช้แก้ปัญหาการผูกมัดกันภายในระบบตามหลักการของ IoC ซึ่งวิธีการแก้ปัญหานั้นมีหลายวิธี (ดังรูปด้านบน) แต่เราหยิบยก DI ขึ้นมากล่าวถึงเพียงอย่างเดียว เพราะหลักการของ DI นั้นเป็นที่นิยมและถูกนำไปสร้างเป็น library หรือ framework ในหลายๆ ภาษา โดยวิธีการของ DI นั้นยังแตกแยกย่อยออกไปอีก 3 วิธี
- Constructor Injection
- Setter Injection
- Method Injection
Constructor Injection
เป็นวิธีการฉีด dependency object ผ่านทาง constructor ของ class ที่จะเรียกใช้ dependency object โดย constructor จะรับ argument ที่เป็น interface ของ class ที่เป็น dependency object
ตามข้อกำหนดด้านบน ในกรณีนี้เราจะต้องแก้ class LogEngine ให้มี constructor ที่มี argument เป็น interface ของ ILog ตาม code ด้านล่าง
public class LogEngine { private ILog log; public LogEngine(ILog log){ this.log = log; } public void log(String message) throws Exception{ log.openLog(); log.log(message); log.closeLog(); } }จาก code ด้านบนจะทำให้เรา class ของ LogEngine จะไม่ได้มีการสร้าง object ของ ILog โดยตรงแล้ว แต่จะรับมาจากภายนอกแทน ซึ่งวิธีนี้เป็นการฉีด object ผ่าน constructor (constructor injection)
ในภายภาคหน้าไม่ว่าเราจะมีการเพิ่มประเภท log อีกสักกี่ตัว class LogEngine เราก็แทบไม่ต้องแก้ไขเลย ยกเว้นจะมีการเปลี่ยนแปลงทางโครงสร้างของ interface ILog
ข้อควรระวังของ Constructor Injection
- ถ้าหากจะใช้ Constructor Injection ต้องระวังอย่าสร้าง empty constructor ใน class นั้น
- วิธีนี้จะทำให้ dependency object จะไม่มีทางเปลี่ยนได้แล้ว เช่นถ้าเราฉีด object ของ ConsoleLog เข้ามา ก็จะไม่สามารถเปลี่ยนเป็น object ของ FileLog ได้อีกแล้ว เพราะถูกกกำหนดมาให้ตั้งแต่ตอนสร้าง object
Setter Injection
เป็นวิธีที่ฉีด dependency object ผ่านทาง setter method วิธีนี้ดีกว่าแบบแรกตรงที่ยืดหยุ่นกว่า constructor injection เพราะสามารถเปลี่ยน dependency object ได้ตลอดเวลาผ่านทาง setter method โดยที่ไม่ต้องสร้าง object ขึ้นมาใหม่
public class LogEngine { private ILog log; public void setLog(ILog log) { this.log = log; } public void log(String message) throws Exception{ log.openLog(); log.log(message); log.closeLog(); } }Method Injection
เป็นวิธีที่ฉีด dependency object ไปยัง method ที่มีการเรียกใช้โดยตรงเลย วิธีนี้เป็นวิธีที่ยืดหยุ่นที่สุด แต่มัันจะเป็นเรื่องน่ารำคาญเพราะต้องฉีด object เข้าไปทุกครั้งที่จะมีการเรียกใช้ method ที่ถูกทำการฉีดเข้าไป
public class LogEngine { public void log(String message, ILog log) throws Exception{ log.openLog(); log.log(message); log.closeLog(); } }
ใครเป็นคนสร้าง dependency object?
หลายคนคงสงสัยว่าแล้วใครจะเป็นสร้าง dependency object แล้วใครเป็นผู้ฉีด object เข้าไป ซึ่งถ้าเราไม่ได้ใช้ library หรือ framework อย่าง Spring Framework, Salta หรือ Google Guice นักพัฒนาจะต้องเป็นคนสร้าง dependency object และเป็นคนฉีดเข้าไปเอง
ถามว่าควรจะสร้าง dependency object ที่ไหน เพราะจากที่กล่าวไปทั้งหมด ถ้าหากเราสร้าง object ตรงๆ เท่ากับเป็นการสร้างการผูกมัด คำตอบคือควรจะสร้างที่ส่วนที่อยู่บนที่สุดที่เกี่ยวกับ module เป็นจุดที่สร้าง object เพราะว่ายังไงการสร้าง application ใดๆ ขึ้นมาก็ต้องมีส่วนที่จะร้อยเรียง module ย่อยๆ ขึ้นมาประกอบให้เป็น application อยู่แล้ว อย่างในตัวอย่างเราก็จะสร้าง dependency object ใน main method
... public static void main(String args[]) throws Exception { //prepare dependency object ILog consoleLog = new ConsoleLog(); LogEngine log = new LogEngine(); log.setLog(consoleLog); //inject object log.log("TestLog"); } ...สรุป
IoC คือหลักการที่ไม่ให้ object สร้าง dependency object ที่จะเรียกใช้เองโดยตรง เพื่อหลีกเลี่ยงการผูกมัดกันระหว่างทั้ง 2 object โดย dependency object จะถูกขึ้นมาจากภายนอก แล้วนำมาฉีดลงไปใน object ที่ต้องการจะใช้จากภายนอก
DI คือวิธีการที่ทำให้หลักการของ IoC นั้นเกิดขึ้นจริง
จะเห็นได้ว่าหลักการของ IoC เป็นตัวช่วยให้การพัฒนา application ของเรานั้นยืดหยุ่นขึ้นกว่าเดิม สามารถขยับขยายได้ง่ายขึ้น แต่ใช่ว่าหลักการของ IoC นั้นจะไม่มีข้อเสีย
- ทำให้การอ่าน code นั้นยากขึ้น เพราะแทนที่เราจะเรียกใช้ตรงๆ แต่เรากลับเรียกใช้ผ่าน abstraction ยกตัวอย่างในกรณีของ class LogEngine หลังจากที่เปลี่ยนมาใช้ interface ตามหลักของ IoC จะพบว่าเวลาอ่าน code เราจะไม่รู้ว่า dependency object ที่เข้ามานั้นจะเป็นอะไรระหว่าง ConsoleLog หรือ FileLog
- เพิ่มภาระให้กับนักพัฒนา สำหรับการเตรียม dependency object สำหรับการฉีดเข้าไปยัง object ที่เรียกใช้ เพราะในการพัฒนา application ในระบบใหญ่ๆ นั้นไม่ได้มีเพียงแค่ module ย่อยๆ เพียง module เดียว อาจจะมีเป็นร้อยเป็นพัน module ซึ่งถ้านักพัฒนาเอา IoC มาใช้ นักพัฒนาก็ต้องเตรียมสิ่งเหล่านี้ด้วยตัวเอง
นั่นแหละจึงเป็นเหตุผลว่าทำไม Spring ถึง learning curve สูงงงงงงงง
อ้างอิง
Comments
Post a Comment