Sử dụng tiêm phụ thuộc cho các đối tượng dữ liệu?


11

Tôi chỉ đang học về tiêm phụ thuộc, và bị mắc kẹt vào một cái gì đó. Dependency Injection khuyên bạn nên gửi các lớp phụ thuộc thông qua hàm tạo, nhưng tôi tự hỏi liệu điều này có cần thiết cho các đối tượng dữ liệu hay không. Vì Khả năng kiểm tra đơn vị là một trong những lợi ích chính của DI, nên một đối tượng dữ liệu, chỉ lưu trữ dữ liệu và không có bất kỳ quy trình nào từng được kiểm tra, làm cho DI trở thành một lớp phức tạp không cần thiết hoặc vẫn giúp hiển thị các phụ thuộc với đối tượng dữ liệu?

Class DO{
    DO(){
        DataObject2List = new List<DO2>();
    }

    public string Field1;
    public string Field2;
    public List<DO2> DataObject2List;
}

Class DO2{
    public DateTime Date;
    public double Value;
}

Chính xác thì bạn có ý nghĩa gì bởi "đối tượng dữ liệu"? Đó không phải là một thuật ngữ tiêu chuẩn. Bạn đang nói về một DTO hay bạn đang đề cập đến bất kỳ lớp nào không có phương thức (chẳng hạn như một phần đặc biệt nhàm chán của mô hình miền)? Có một sự khác biệt rất lớn giữa hai.
Aaronaught

Chắc chắn, ý tôi là chỉ một lớp không có phương thức, một lớp chỉ lưu trữ dữ liệu. Nếu đối tượng dữ liệu không phải là thuật ngữ chính xác cho điều này, thì có một hay chỉ được gọi là một lớp không có phương thức?
soopawn

@soopawn Đây là một câu hỏi hay. Suy nghĩ tốt.
Matthew Rodatus

@soopawn Có lẽ câu trả lời của tôi là không đúng. Bạn sẽ đặt các phương pháp CRUD ở đâu? Trong một lớp truy cập dữ liệu riêng biệt sẽ đưa Đối tượng dữ liệu và duy trì chúng vào bảng cơ sở dữ liệu? Tức là DataAccess.Create (<DataObject>)?
Matthew Rodatus

1
@Matthew, đây sẽ là Đối tượng truy cập dữ liệu - nếu thực tế đó là những gì OP đang nói, thì điều đó không rõ ràng chút nào. Các triển khai hiện đại có xu hướng di chuyển ra khỏi mô hình này dù sao, dựa vào các kho lưu trữ và / hoặc các đơn vị công việc.
Aaronaught

Câu trả lời:


7

Tôi muốn đề nghị làm rõ một số thuật ngữ bạn đang sử dụng ở đây, cụ thể là "phụ thuộc" và "tiêm phụ thuộc".

Phụ thuộc:

Một "phụ thuộc" thường là một đối tượng phức tạp thực hiện một số chức năng mà một lớp khác có thể cần phải phụ thuộc vào. Một số ví dụ cổ điển sẽ là một logger hoặc một bộ truy cập cơ sở dữ liệu hoặc một số thành phần xử lý một đoạn logic kinh doanh cụ thể.

Một đối tượng chỉ dữ liệu như DTO hoặc đối tượng giá trị thường không được gọi là "phụ thuộc", vì chúng không thực hiện một số chức năng cần thiết.

Khi bạn nhìn vào nó theo cách này, những gì bạn đang làm trong ví dụ của bạn ( sáng tác các DOđối tượng với một danh sách các D02đối tượng thông qua các nhà xây dựng) không nên được coi là "dependecy tiêm" chút nào. Nó chỉ là thiết lập một tài sản. Tùy thuộc vào việc bạn cung cấp nó trong hàm tạo hay theo một cách nào khác, nhưng chỉ cần truyền nó qua hàm tạo không làm cho nó phụ thuộc vào nội dung.

Phụ thuộc tiêm:

Nếu DO2lớp của bạn thực sự cung cấp một số chức năng bổ sung mà DOlớp cần, thì đó thực sự sẽ là một sự phụ thuộc. Trong trường hợp đó, lớp phụ thuộc DO, nên phụ thuộc vào một giao diện (như ILogger hoặc IDataAccessor), và lần lượt dựa vào mã gọi để cung cấp giao diện đó (nói cách khác là 'tiêm' nó vào DOví dụ).

Việc tiêm phụ thuộc theo cách như vậy làm cho DOđối tượng linh hoạt hơn, vì mỗi bối cảnh khác nhau có thể cung cấp việc thực hiện giao diện riêng cho DOđối tượng. (Hãy thử nghiệm đơn vị.)


7

Tôi sẽ làm hết sức mình để vượt qua sự nhầm lẫn trong câu hỏi.

Trước hết, "Đối tượng dữ liệu" không phải là một thuật ngữ có ý nghĩa. Nếu đặc điểm xác định duy nhất của đối tượng này là nó không có phương thức, thì nó hoàn toàn không tồn tại . Một đối tượng không có hành vi hữu ích phải phù hợp với ít nhất một trong các loại con sau:

  • Đối tượng giá trị hoặc "hồ sơ" hoàn toàn không có danh tính. Chúng phải là các loại giá trị , với ngữ nghĩa sao chép trên tham chiếu, giả sử môi trường hỗ trợ nó. Vì đây là các cấu trúc cố định, VO chỉ nên là một kiểu nguyên thủy hoặc một chuỗi nguyên thủy cố định. Do đó, VO không nên có bất kỳ sự phụ thuộc hoặc liên kết nào; bất kỳ hàm tạo không mặc định nào sẽ tồn tại chỉ với mục đích khởi tạo giá trị, nghĩa là bởi vì nó không thể được biểu thị bằng nghĩa đen.

  • Đối tượng truyền dữ liệu thường bị nhầm lẫn với các đối tượng giá trị. DTOs làm có bản sắc, hay ít nhất là họ có thể . Mục đích duy nhất của DTO là tạo điều kiện cho luồng thông tin từ miền này sang miền khác. Họ không bao giờ có "sự phụ thuộc". Họ có thể có các hiệp hội (tức là một mảng hoặc bộ sưu tập) nhưng hầu hết mọi người thích làm cho chúng phẳng. Về cơ bản, chúng tương tự như các hàng trong đầu ra của truy vấn cơ sở dữ liệu; chúng là những đối tượng nhất thời thường cần được duy trì hoặc tuần tự hóa, và do đó không thể tham chiếu bất kỳ loại trừu tượng nào, vì điều này sẽ khiến chúng không thể sử dụng được.

  • Cuối cùng, Đối tượng truy cập dữ liệu cung cấp trình bao bọc hoặc mặt tiền cho cơ sở dữ liệu thuộc loại nào đó. Chúng rõ ràng có sự phụ thuộc - chúng phụ thuộc vào kết nối cơ sở dữ liệu và / hoặc các thành phần bền vững. Tuy nhiên, sự phụ thuộc của họ hầu như luôn được quản lý bên ngoài và hoàn toàn vô hình đối với người gọi. Trong mẫu Bản ghi hoạt động, đó là khung quản lý mọi thứ thông qua cấu hình; trong các mô hình DAO cũ hơn (cổ theo tiêu chuẩn ngày nay), bạn chỉ có thể xây dựng các mô hình này thông qua container. Nếu tôi thấy một trong những thứ này với tiêm constructor, tôi sẽ rất, rất lo lắng.

Bạn cũng có thể nghĩ đến việc một đối tượng thực thể hoặc "đối tượng kinh doanh" , và trong trường hợp này bạn làm muốn tiêm hỗ trợ phụ thuộc, nhưng không phải theo cách mà bạn nghĩ hay vì những lý do bạn nghĩ. Nó không phải vì lợi ích của mã người dùng , vì lợi ích của trình quản lý thực thể hoặc ORM, nó sẽ âm thầm tiêm một proxy mà nó chặn để thực hiện những việc ưa thích như hiểu truy vấn hoặc tải chậm.

Trong những điều này, bạn thường không cung cấp một hàm tạo để tiêm; thay vào đó, bạn chỉ cần làm cho thuộc tính ảo và sử dụng một loại trừu tượng (ví dụ IList<T>thay vì List<T>). Phần còn lại xảy ra đằng sau hậu trường, và không ai là người khôn ngoan hơn.

Vì vậy, tất cả trong tất cả tôi sẽ nói rằng một mẫu DI có thể nhìn thấy được áp dụng cho một "đối tượng dữ liệu" là không cần thiết và thậm chí có thể là một lá cờ đỏ; nhưng phần lớn, đó là bởi vì sự tồn tại của đối tượng là một lá cờ đỏ, ngoại trừ trong trường hợp khi nó được sử dụng đặc biệt để biểu diễn dữ liệu từ cơ sở dữ liệu. Trong hầu hết các trường hợp khác, đó là mùi mã, điển hình là sự khởi đầu của Mô hình miền thiếu máu hoặc ít nhất là Poltergeist .

Để nhắc lại:

  1. Đừng tạo "đối tượng dữ liệu".
  2. Nếu bạn phải tạo một "đối tượng dữ liệu", thì hãy chắc chắn rằng nó có mục đích được xác định rõ ràng . Mục đích đó sẽ cho bạn biết liệu DI có phù hợp hay không. Không thể đưa ra bất kỳ quyết định thiết kế có ý nghĩa nào về một đối tượng không nên tồn tại ở nơi đầu tiên.

Vây.


0

Trong ví dụ của bạn, DOkhông có bất kỳ phụ thuộc chức năng nào (về cơ bản vì nó không làm gì cả). Nó có một sự phụ thuộc vào loại cụ thể DO2, vì vậy bạn có thể muốn giới thiệu một giao diện trừu tượng DO2, để người tiêu dùng có thể thực hiện việc triển khai cụ thể của riêng họ đối với lớp con.

Thực sự, những gì phụ thuộc bạn sẽ tiêm ở đây?


Mỗi câu hỏi khác của tôi mà tôi đã trả lời, tôi nghĩ rằng câu hỏi đề cập đến một Đối tượng dữ liệu với các hoạt động CRUD được kết hợp. Làm thế nào / nơi phụ thuộc cơ sở dữ liệu được đưa vào Đối tượng dữ liệu? Trong các phương pháp? Trong các nhà xây dựng? Một số cách khác? Giả định là không nên ẩn phụ thuộc vào phần thân của các phương thức - việc vô hiệu hóa tách biệt phụ thuộc cơ sở dữ liệu khỏi Đối tượng dữ liệu để Đối tượng dữ liệu có thể được kiểm tra đơn vị.
Matthew Rodatus

@Matthew Rodatus - Tôi hiểu rồi. Có, trong trường hợp đó, bạn có hai lựa chọn: tiêm dịch vụ kiên trì hoặc tạo một lớp khác gọi là DOPersisterbiết cách duy trì DOvà để nó như một đối tượng chỉ có dữ liệu nghiêm ngặt (theo ý kiến ​​của tôi tốt hơn). Trong trường hợp sau, DOPersistersẽ được thêm vào phụ thuộc cơ sở dữ liệu.
Scott Whitlock

Sau khi đọc lại câu hỏi của anh ấy, tôi không chắc lắm. Phân tích của tôi có thể sai. Anh ấy đã nói trong câu hỏi của mình rằng DO của anh ấy sẽ không có bất kỳ thủ tục nào. Điều đó có nghĩa là sự kiên trì KHÔNG xảy ra trong DO. Trong trường hợp đó, câu trả lời của bạn là đúng - không có sự phụ thuộc để tiêm.
Matthew Rodatus

0

Vì đây là Đối tượng dữ liệu trong lớp truy cập dữ liệu, nên nó phụ thuộc trực tiếp vào dịch vụ cơ sở dữ liệu. Bạn có thể chỉ định một dịch vụ cơ sở dữ liệu cho nhà xây dựng:

DataObject dataObject = new DataObject(new DatabaseService());
dataObject.Update();

Nhưng, mũi tiêm không phải ở trong hàm tạo. Ngoài ra, bạn có thể cung cấp sự phụ thuộc thông qua mỗi phương pháp CRUD. Tôi thích phương pháp này hơn trước vì Đối tượng dữ liệu của bạn không cần biết nó sẽ tồn tại ở đâu cho đến khi bạn thực sự cần phải duy trì nó.

DataObject dataObject = new DataObject();
dataObject.Update(new DatabaseService());

Bạn chắc chắn không muốn che giấu việc xây dựng trong các phương pháp CRUD!

public void Update()
{
    // DON'T DO THIS!
    using (DatabaseService dbService = new DatabaseService())
    {
        ...
    }
}

Một tùy chọn thay thế sẽ là xây dựng DatabaseService thông qua một phương thức lớp có thể ghi đè.

public void Update()
{
    // GetDatabaseService() is protected virtual, so in unit testing
    // you can subclass the Data Object and return your own
    // MockDatabaseService.
    using (DatabaseService dbService = GetDatabaseService())
    {
        ...
    }
}

Một lựa chọn cuối cùng là sử dụng ServiceLocator kiểu đơn. Mặc dù tôi không thích tùy chọn này, nó là đơn vị thử nghiệm.

public void Update()
{
    // The ServiceLocator would not be a real singleton. It would have a setter
    // property so that unit tests can swap it out with a mock implementation
    // for unit tests.
    using (DatabaseService dbService = ServiceLocator.GetDatabaseService())
    {
        ...
    }
}
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.