Bạn nên sử dụng các kỹ thuật để giải quyết vấn đề mà họ giỏi trong việc giải quyết khi bạn gặp phải những vấn đề đó. Phụ thuộc đảo ngược và tiêm không khác nhau.
Nghịch đảo phụ thuộc hoặc tiêm là một kỹ thuật cho phép mã của bạn quyết định việc triển khai phương thức nào được gọi trong thời gian chạy. Điều này tối đa hóa lợi ích của ràng buộc muộn. Kỹ thuật này là cần thiết khi ngôn ngữ không hỗ trợ thay thế thời gian chạy của các chức năng phi thể hiện. Ví dụ, Java thiếu một cơ chế để thay thế các cuộc gọi đến một phương thức tĩnh bằng các cuộc gọi đến một triển khai khác; tương phản với Python, trong đó tất cả những gì cần thiết để thay thế lời gọi hàm là liên kết tên với một hàm khác (gán lại biến giữ chức năng).
Tại sao chúng ta muốn thay đổi việc thực hiện chức năng? Có hai lý do chính:
- Chúng tôi muốn sử dụng hàng giả cho mục đích thử nghiệm. Điều này cho phép chúng tôi kiểm tra một lớp phụ thuộc vào tìm nạp cơ sở dữ liệu mà không thực sự kết nối với cơ sở dữ liệu.
- Chúng tôi cần hỗ trợ nhiều triển khai. Ví dụ: chúng ta có thể cần thiết lập một hệ thống hỗ trợ cả cơ sở dữ liệu MySQL và PostgreQuery.
Bạn cũng có thể muốn lưu ý đảo ngược các container điều khiển. Đây là một kỹ thuật nhằm giúp bạn tránh những cây xây dựng khổng lồ, rối rắm trông giống như mã giả này:
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)
);
Nó cho phép bạn đăng ký các lớp học và sau đó thực hiện việc xây dựng cho bạn:
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.
Lưu ý rằng nó đơn giản nhất nếu các lớp được đăng ký có thể là các singletons không trạng thái .
Lời cảnh báo
Lưu ý rằng đảo ngược phụ thuộc không nên là câu trả lời của bạn cho logic tách rời. Tìm kiếm cơ hội để sử dụng tham số hóa thay thế. Hãy xem xét phương pháp mã giả này chẳng hạn:
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;
}
Chúng ta có thể sử dụng nghịch đảo phụ thuộc cho một số phần của phương pháp này:
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;
}
Nhưng chúng ta không nên, ít nhất là không hoàn toàn. Lưu ý rằng chúng tôi đã tạo ra một lớp trạng thái với Querier
. Bây giờ nó giữ một tham chiếu đến một số đối tượng kết nối toàn cầu về cơ bản. Điều này tạo ra các vấn đề như khó khăn trong việc hiểu trạng thái chung của chương trình và cách các lớp khác nhau phối hợp với nhau. Cũng lưu ý rằng chúng tôi buộc phải giả mạo bộ kiểm tra hoặc kết nối nếu chúng tôi muốn kiểm tra logic trung bình. Hơn nữa Một cách tiếp cận tốt hơn sẽ là tăng tham số hóa :
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);
}
}
Và kết nối sẽ được quản lý ở cấp độ cao hơn, chịu trách nhiệm cho toàn bộ hoạt động và biết phải làm gì với đầu ra này.
Bây giờ chúng ta có thể kiểm tra logic trung bình hoàn toàn độc lập với truy vấn và chúng ta có thể sử dụng nó nhiều hơn trong nhiều tình huống khác nhau. Chúng tôi có thể đặt câu hỏi liệu chúng tôi thậm chí có cần MyQuerier
và Averager
các đối tượng hay không, và có thể câu trả lời là chúng tôi không nếu chúng tôi không có ý định kiểm tra đơn vị StuffDoer
, và không kiểm tra đơn vị StuffDoer
sẽ hoàn toàn hợp lý vì nó được kết hợp chặt chẽ với cơ sở dữ liệu. Nó có thể có ý nghĩa hơn khi chỉ để cho các bài kiểm tra tích hợp bao gồm nó. Trong trường hợp đó, chúng ta có thể thực hiện tốt fetchAboveMin
và averageData
thành các phương thức tĩnh.