Phương pháp tiếp cận dần dần để tiêm phụ thuộc


12

Tôi đang làm việc để làm cho các lớp học của tôi có thể kiểm tra được đơn vị, sử dụng phép tiêm phụ thuộc. Nhưng một số trong các lớp này có rất nhiều khách hàng và tôi chưa sẵn sàng cấu trúc lại tất cả chúng để bắt đầu chuyển qua các phụ thuộc. Vì vậy, tôi đang cố gắng làm điều đó dần dần; giữ các phụ thuộc mặc định cho đến bây giờ, nhưng cho phép chúng bị ghi đè để thử nghiệm.

Một cách tiếp cận mà tôi quan tâm là chỉ chuyển tất cả các cuộc gọi "mới" sang các phương thức của riêng họ, ví dụ:

public MyObject createMyObject(args) {
  return new MyObject(args);
}

Sau đó, trong các bài kiểm tra đơn vị của mình, tôi chỉ có thể phân lớp lớp này và ghi đè các hàm tạo, để chúng tạo các đối tượng giả thay thế.

Đây có phải là một cách tiếp cận tốt? Có bất kỳ nhược điểm?

Tổng quát hơn, có ổn không khi có các phụ thuộc được mã hóa cứng, miễn là bạn có thể thay thế chúng để thử nghiệm? Tôi biết cách tiếp cận ưa thích là yêu cầu rõ ràng chúng trong hàm tạo và cuối cùng tôi muốn đến đó. Nhưng tôi tự hỏi nếu đây là một bước đầu tốt.

Một nhược điểm vừa xảy ra với tôi: nếu bạn có các lớp con thực sự mà bạn cần kiểm tra, bạn không thể sử dụng lại lớp con kiểm tra mà bạn đã viết cho lớp cha. Bạn sẽ phải tạo một lớp con thử nghiệm cho mỗi lớp con thực sự và nó sẽ phải ghi đè cùng các hàm tạo.


Bạn có thể giải thích lý do tại sao bây giờ họ không thể kiểm tra được đơn vị không?
Austin Salonen

Có rất nhiều "X mới" nằm rải rác trong mã, cũng như nhiều cách sử dụng các lớp và singletons tĩnh. Vì vậy, khi tôi thử kiểm tra một lớp, tôi thực sự đang kiểm tra cả đống chúng. Nếu tôi có thể cô lập các phụ thuộc và thay thế chúng bằng các đối tượng giả, tôi sẽ kiểm soát nhiều hơn đối với thử nghiệm.
JW01

Câu trả lời:


13

Đây là một cách tiếp cận tốt để giúp bạn bắt đầu. Lưu ý rằng điều quan trọng nhất là bao gồm mã hiện tại của bạn bằng các bài kiểm tra đơn vị; một khi bạn có các bài kiểm tra, bạn có thể tái cấu trúc một cách tự do hơn để cải thiện thiết kế của bạn hơn nữa.

Vì vậy, điểm ban đầu không phải là làm cho thiết kế thanh lịch và sáng bóng - chỉ để làm cho đơn vị mã có thể kiểm tra được với những thay đổi ít rủi ro nhất . Nếu không có bài kiểm tra đơn vị, bạn phải thận trọng và thận trọng hơn để tránh phá vỡ mã của mình. Những thay đổi ban đầu này thậm chí có thể làm cho mã trông vụng về hoặc xấu xí hơn trong một số trường hợp - nhưng nếu chúng cho phép bạn viết các bài kiểm tra đơn vị đầu tiên, cuối cùng bạn sẽ có thể cấu trúc lại theo thiết kế lý tưởng mà bạn có trong đầu.

Công việc cơ bản để đọc về chủ đề này là Làm việc hiệu quả với Bộ luật kế thừa . Nó mô tả thủ thuật bạn thể hiện ở trên và nhiều, nhiều hơn nữa, bằng cách sử dụng một số ngôn ngữ.


Chỉ cần một nhận xét về điều này "một khi bạn có các bài kiểm tra, bạn có thể tái cấu trúc một cách tự do hơn" - Nếu bạn không có bài kiểm tra, bạn sẽ không tái cấu trúc, bạn chỉ cần thay đổi mã.
ocodo

@Slomojo Tôi thấy quan điểm của bạn, nhưng bạn vẫn có thể thực hiện nhiều phép tái cấu trúc an toàn mà không cần kiểm tra, đặc biệt là các công cụ IDE tự động như, ví dụ (trong nhật thực cho Java) trích xuất mã trùng lặp thành phương thức, đổi tên mọi thứ, di chuyển các lớp và trích xuất các trường, hằng v.v.
Alb

Tôi thực sự chỉ trích dẫn nguồn gốc của thuật ngữ Tái cấu trúc, đó là sửa đổi mã thành các mẫu được cải tiến, được bảo đảm khỏi các lỗi hồi quy bằng khung kiểm tra. Tất nhiên, tái cấu trúc dựa trên IDE đã làm cho phần mềm 'tái cấu trúc' trở nên tầm thường hơn nhiều, nhưng tôi có xu hướng thích tránh tự ảo tưởng, nếu phần mềm của tôi không có kiểm tra và tôi "tái cấu trúc" tôi chỉ thay đổi mã. Tất nhiên, đó có thể không phải là những gì tôi nói với người khác;)
ocodo

1
@Alb, tái cấu trúc mà không cần kiểm tra luôn có nhiều rủi ro. Tái cấu trúc tự động chắc chắn giảm rủi ro đó, nhưng không đến không. Luôn có những trường hợp khó khăn mà các công cụ có thể không thể xử lý chính xác. Trong trường hợp tốt hơn, kết quả là một số thông báo lỗi từ công cụ, nhưng trong trường hợp xấu nhất, nó có thể âm thầm đưa ra các lỗi tinh vi. Vì vậy, nên giữ những thay đổi đơn giản và an toàn nhất có thể ngay cả với các công cụ tự động.
Péter Török

3

Guice-folks khuyên bạn nên sử dụng

@Inject
public void setX(.... X x) {
   this.x = x;
}

cho tất cả các thuộc tính thay vì chỉ thêm @Inject vào định nghĩa của chúng. Điều này sẽ cho phép bạn coi chúng như những hạt đậu bình thường, điều mà bạn có thể newkhi kiểm tra (và thiết lập các thuộc tính theo cách thủ công) và @Inject trong sản xuất.


3

Khi tôi gặp phải loại vấn đề này, tôi sẽ xác định từng phụ thuộc của bài kiểm tra lớp của mình và tạo một hàm tạo được bảo vệ cho phép tôi vượt qua chúng. Điều này thực sự giống như tạo một setter cho mỗi, nhưng tôi thích tuyên bố sự phụ thuộc trong các nhà xây dựng của tôi để tôi không quên khởi tạo chúng trong tương lai.

Vì vậy, lớp thử nghiệm đơn vị mới của tôi sẽ có 2 hàm tạo:

public MyClass() { 
  this(new Client1(), new Client2()); 
}

public MyClass(Client1 client1, Client2 client2) { 
  this.client1 = client1; 
  this.client2 = client2; 
}

2

Bạn có thể muốn xem xét thành ngữ "getter created" cho đến khi bạn có thể sử dụng phần phụ thuộc được thêm vào.

Ví dụ,

public class Example {
  private MyObject myObject=null;

  public MyObject getMyObject() {
    if (myObject==null) {
      myObject=new MyObject();
    } 
    return myObject;
  }

  public void setMyObject(MyObject myObject) {
    this.myObject=myObject;
  }

  private void myMethod() {
    if (getMyObject().doSomething()) {
      // Instance automatically created
    }
  }
}

Điều này sẽ cho phép bạn cấu trúc lại nội bộ để bạn có thể tham chiếu myObject của mình thông qua getter. Bạn không bao giờ phải gọi new. Trong tương lai, khi tất cả các máy khách được cấu hình để cho phép tiêm phụ thuộc, thì tất cả những gì bạn phải làm là xóa mã tạo ở một nơi - getter. Trình thiết lập sẽ cho phép bạn tiêm các đối tượng giả theo yêu cầu.

Đoạn mã trên chỉ là một ví dụ và nó trực tiếp phơi bày trạng thái bên trong. Bạn nên cân nhắc cẩn thận nếu cách tiếp cận này phù hợp với cơ sở mã của bạn.

Tôi cũng khuyên bạn nên đọc " Làm việc hiệu quả với Mã kế thừa " trong đó có vô số lời khuyên hữu ích để thành công trong việc tái cấu trúc chính.


Cảm ơn. Tôi đang xem xét phương pháp này cho một số trường hợp. Nhưng bạn sẽ làm gì khi hàm tạo của MyObject yêu cầu các tham số chỉ có sẵn từ bên trong lớp của bạn? Hoặc khi bạn cần tạo nhiều hơn một MyObject trong suốt thời gian của lớp học? Bạn có phải tiêm MyObjectFactory không?
JW01

@ JW01 Bạn có thể định cấu hình mã trình tạo getter để đọc trạng thái nội bộ từ lớp của bạn và chỉ cần điều chỉnh nó. Tạo nhiều hơn một thể hiện trong suốt cuộc đời sẽ rất khó để phối hợp. Nếu bạn gặp phải tình huống đó, tôi sẽ đề xuất thiết kế lại, thay vì làm việc xung quanh. Đừng làm cho getters của bạn quá phức tạp hoặc chúng sẽ trở thành một cối xay quanh cổ bạn. Mục tiêu của bạn là để giảm bớt quá trình tái cấu trúc. Nếu nó có vẻ quá khó khăn, di chuyển đến một khu vực khác để sửa nó ở đó.
Gary Rowe

đó là một người độc thân. Chỉ không sử dụng singletons O_O
Nhà phát triển Game

@DarioOO Thật ra đó là một người độc thân rất nghèo. Một triển khai tốt hơn sẽ sử dụng một enum theo Josh Bloch. Singletons là một mẫu thiết kế hợp lệ và có công dụng của chúng (sáng tạo một lần đắt tiền, v.v.).
Gary Rowe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.