Lưu một đối tượng thông qua một phương thức của chính nó hoặc thông qua một lớp khác?


38

Nếu tôi muốn lưu và lấy một đối tượng, tôi có nên tạo một lớp khác để xử lý nó hay không, tốt hơn là nên làm điều đó trong chính lớp đó? Hoặc có thể trộn cả hai?

Đó là khuyến cáo theo mô hình OOD?

Ví dụ

Class Student
{
    public string Name {set; get;}
    ....
    public bool Save()
    {
        SqlConnection con = ...
        // Save the class in the db
    }
    public bool Retrieve()
    {
         // search the db for the student and fill the attributes
    }
    public List<Student> RetrieveAllStudents()
    {
         // this is such a method I have most problem with it 
         // that an object returns an array of objects of its own class!
    }
}

Đấu với. (Tôi biết những điều sau được khuyến nghị, tuy nhiên đối với tôi có vẻ hơi chống lại sự gắn kết của Studentlớp học)

Class Student { /* */ }
Class DB {
  public bool AddStudent(Student s)
  {

  }
  public Student RetrieveStudent(Criteria)
  {
  } 
  public List<Student> RetrieveAllStudents()
  {
  }
}

Làm thế nào về việc trộn chúng?

   Class Student
    {
        public string Name {set; get;}
        ....
        public bool Save()
        {
            /// do some business logic!
            db.AddStudent(this);
        }
        public bool Retrieve()
        {
             // build the criteria 
             db.RetrieveStudent(criteria);
             // fill the attributes
        }
    }

3
Bạn nên thực hiện một số nghiên cứu về mẫu Bản ghi hoạt động, chẳng hạn như câu hỏi này
Eric King

@gnat, tôi nghĩ rằng hỏi cái nào tốt hơn là dựa trên ý kiến ​​sau đó thêm "ưu và nhược điểm" và không có vấn đề gì để loại bỏ nó, nhưng thực tế tôi có nghĩa là về mô hình của bộ phận được đề xuất
Ahmad

3
Không có câu trả lời đúng. Nó phụ thuộc vào nhu cầu của bạn, sở thích của bạn, cách lớp của bạn sẽ được sử dụng và nơi bạn thấy dự án của bạn sẽ đi. Điều duy nhất đúng OO là bạn có thể lưu và truy xuất dưới dạng đối tượng.
Dunk

Câu trả lời:


34

Nguyên tắc trách nhiệm duy nhất , tách biệt mối quan tâmsự gắn kết chức năng . Nếu bạn đọc các khái niệm này, câu trả lời bạn nhận được là: Tách chúng ra .

Một lý do đơn giản để tách lớp Student"DB" (hoặc StudentRepository, theo các quy ước phổ biến hơn) là cho phép bạn thay đổi "quy tắc kinh doanh" của mình, hiện diện trong Studentlớp, mà không ảnh hưởng đến mã chịu trách nhiệm cho sự kiên trì và ngược lại ngược lại

Loại tách biệt này rất quan trọng, không chỉ giữa quy tắc kinh doanh và sự kiên trì, mà giữa nhiều mối quan tâm của hệ thống của bạn, cho phép bạn thực hiện các thay đổi với tác động tối thiểu trong các mô-đun không liên quan (tối thiểu vì đôi khi không thể tránh khỏi). Nó giúp xây dựng các hệ thống mạnh hơn, dễ bảo trì hơn và đáng tin cậy hơn khi có những thay đổi liên tục.

Bằng cách có các quy tắc kinh doanh và sự kiên trì trộn lẫn với nhau, hoặc là một lớp duy nhất như trong ví dụ đầu tiên của bạn, hoặc với DBtư cách là một phụ thuộc Student, bạn đang kết hợp hai mối quan tâm rất khác nhau. Có vẻ như họ thuộc về nhau; chúng dường như được gắn kết bởi vì chúng sử dụng cùng một dữ liệu. Nhưng đây là điều: sự gắn kết không thể được đo lường chỉ bằng dữ liệu được chia sẻ giữa các thủ tục, bạn cũng phải xem xét mức độ trừu tượng mà chúng tồn tại. Trong thực tế, loại kết dính lý tưởng được mô tả là:

Sự gắn kết chức năng là khi các bộ phận của một mô-đun được nhóm lại bởi vì tất cả chúng đều góp phần vào một nhiệm vụ được xác định rõ của mô-đun.

Và rõ ràng, thực hiện xác nhận Studenttrong một thời gian cũng tồn tại nó không hình thành "một nhiệm vụ được xác định rõ". Vì vậy, một lần nữa, các quy tắc kinh doanh và cơ chế kiên trì là hai khía cạnh rất khác nhau của hệ thống, theo nhiều nguyên tắc thiết kế hướng đối tượng tốt, nên được tách biệt.

Tôi khuyên bạn nên đọc về Kiến trúc sạch , xem bài nói này về Nguyên tắc trách nhiệm duy nhất (nơi sử dụng một ví dụ rất giống nhau) và cũng xem bài nói về Kiến trúc sạch này . Những khái niệm này phác thảo những lý do đằng sau sự tách biệt như vậy.


11
À, nhưng Studentlớp học nên được gói gọn, đúng không? Làm thế nào sau đó một lớp bên ngoài có thể quản lý sự tồn tại của nhà nước tư nhân? Đóng gói phải được cân bằng với SoC và SRP, nhưng chỉ cần chọn cái khác mà không cân nhắc cẩn thận sự đánh đổi có lẽ là sai. Một giải pháp khả thi cho câu hỏi hóc búa này là sử dụng các bộ truy cập gói riêng cho mã kiên trì sử dụng.
amon

1
@amon Tôi đồng ý rằng nó không đơn giản như vậy, nhưng tôi tin rằng đó là vấn đề khớp nối chứ không phải đóng gói. Chẳng hạn, bạn có thể làm cho lớp Sinh viên trừu tượng, với các phương thức trừu tượng cho bất kỳ dữ liệu nào mà doanh nghiệp cần, sau đó được kho lưu trữ triển khai. Theo cách này, cấu trúc dữ liệu của DB hoàn toàn bị ẩn đằng sau việc triển khai kho lưu trữ, do đó, nó thậm chí còn được gói gọn từ lớp Sinh viên. Nhưng, đồng thời, kho lưu trữ được kết hợp sâu sắc với lớp Sinh viên - nếu bất kỳ phương thức trừu tượng nào được thay đổi, thì việc thực hiện cũng phải thay đổi. Đó là tất cả về sự đánh đổi. :)
MichelHenrich

4
Khiếu nại rằng SRP làm cho các chương trình dễ bảo trì hơn là không rõ ràng. Vấn đề SRP tạo ra là thay vì có một tập hợp các lớp được đặt tên tốt, trong đó tên cho biết mọi thứ mà lớp có thể và không thể làm được (nói cách khác là dễ hiểu dẫn đến dễ duy trì) bạn kết thúc với hàng trăm / hàng ngàn lớp học mà mọi người cần sử dụng từ điển đồng nghĩa để chọn một tên duy nhất, nhiều tên tương tự nhưng không hoàn toàn và sắp xếp qua tất cả những điều đó là một việc vặt. IOW, Không duy trì được chút nào, IMO.
Dunk

3
@ Bạn nói như thể các lớp là đơn vị mô đun hóa duy nhất có sẵn. Tôi đã xem và viết nhiều dự án theo SRP và tôi đồng ý rằng ví dụ của bạn đã xảy ra, nhưng chỉ khi bạn không sử dụng đúng gói và thư viện. Nếu bạn tách các trách nhiệm thành các gói, vì bối cảnh được ngụ ý bởi nó, bạn có thể đặt tên lại các lớp một cách tự do. Sau đó, để duy trì một hệ thống như thế này, tất cả chỉ cần khoan sâu vào đúng gói trước khi tìm kiếm lớp mà bạn cần thay đổi. :)
MichelHenrich

5
Nguyên tắc @Dunk OOD có thể được nêu là: "Về nguyên tắc, làm điều này tốt hơn vì X, Y và Z". Điều đó không có nghĩa là nó phải luôn được áp dụng; mọi người phải thực dụng và cân nhắc sự đánh đổi. Trong các dự án lớn, những lợi ích thường vượt trội so với đường cong học tập mà bạn nói đến - nhưng tất nhiên, đó không phải là thực tế đối với hầu hết mọi người, và vì vậy nó không nên được áp dụng nhiều. Tuy nhiên, ngay cả trong các ứng dụng nhẹ hơn SRP, tôi nghĩ cả hai chúng ta đều có thể đồng ý rằng tách biệt sự kiên trì khỏi các quy tắc kinh doanh luôn mang lại lợi ích vượt quá chi phí của nó (ngoại trừ có thể cho mã vứt bỏ).
MichelHenrich

10

Cả hai cách tiếp cận đều vi phạm Nguyên tắc Trách nhiệm duy nhất. Phiên bản đầu tiên của bạn cung cấp cho Studentlớp nhiều trách nhiệm và liên kết nó với một công nghệ truy cập DB cụ thể. Thứ hai dẫn đến một DBlớp học lớn sẽ chịu trách nhiệm không chỉ đối với sinh viên mà còn đối với bất kỳ loại đối tượng dữ liệu nào khác trong chương trình của bạn. EDIT: cách tiếp cận thứ ba của bạn là tồi tệ nhất, vì nó tạo ra sự phụ thuộc theo chu kỳ giữa lớp DB và Studentlớp.

Vì vậy, nếu bạn sẽ không viết một chương trình đồ chơi, không sử dụng chúng. Thay vào đó, hãy sử dụng một lớp khác như StudentRepositorycung cấp API để tải và lưu, giả sử bạn sẽ tự mình thực hiện mã CRUD. Bạn cũng có thể cân nhắc sử dụng khung ORM , có thể thực hiện công việc khó khăn cho bạn (và khung thường sẽ thi hành một số quyết định trong đó các hoạt động Tải và Lưu phải được đặt).


Cảm ơn bạn, tôi đã sửa đổi câu trả lời của tôi, những gì về cách tiếp cận thứ ba? dù sao tôi nghĩ ở đây vấn đề là tạo ra các lớp riêng biệt
Ahmad

Một cái gì đó cũng gợi ý cho tôi sử dụng StudentRep repository thay vì DB (không biết tại sao! Có thể một lần nữa chịu trách nhiệm) nhưng tôi vẫn không thể hiểu tại sao kho lưu trữ khác nhau cho mỗi lớp? Nếu nó chỉ là một lớp truy cập dữ liệu thì có nhiều hàm tiện ích tĩnh hơn và có thể kết hợp với nhau không? chỉ có thể sau đó nó sẽ trở nên lớp học bẩn thỉu và đông đúc (xin lỗi vì tiếng Anh của tôi)
Ahmad

1
@Ahmad: có một vài lý do, và một vài ưu và nhược điểm cần xem xét. Điều này có thể lấp đầy cả một cuốn sách. Lý do đằng sau điều này giúp giảm khả năng kiểm tra, khả năng duy trì và khả năng phát triển lâu dài của các chương trình của bạn, đặc biệt là khi chúng có vòng đời dài.
Doc Brown

Có ai từng làm việc trong một dự án nơi có sự thay đổi đáng kể trong công nghệ DB không? (không có một bản viết lại lớn?) Tôi không có. Nếu vậy, đã làm theo SRP, như được đề xuất ở đây để tránh bị ràng buộc với DB đó, giúp đỡ đáng kể?
dùng949300

@ user949300: Tôi nghĩ rằng tôi đã không thể hiện bản thân rõ ràng. Lớp sinh viên không bị ràng buộc với một công nghệ DB cụ thể, nó trở nên gắn liền với một công nghệ truy cập DB cụ thể, vì vậy nó mong đợi một cái gì đó giống như SQL DB để tồn tại. Giữ cho lớp Sinh viên hoàn toàn không biết về cơ chế kiên trì sẽ cho phép tái sử dụng lớp dễ dàng hơn nhiều trong các bối cảnh khác nhau. Ví dụ, trong một bối cảnh thử nghiệm hoặc trong một bối cảnh nơi các đối tượng được lưu trữ trong một tệp. Và đó là điều tôi đã làm thực sự trong quá khứ rất thường xuyên. Không cần thay đổi toàn bộ ngăn xếp DB của hệ thống của bạn để đạt được lợi ích từ việc này.
Doc Brown

3

Có khá nhiều mẫu có thể được sử dụng để duy trì dữ liệu. Có mẫu Đơn vị công việc , có mẫu Kho lưu trữ , có một số mẫu bổ sung có thể được sử dụng như Mặt tiền từ xa, v.v.

Hầu hết những người có người hâm mộ và các nhà phê bình của họ. Thông thường phải chọn những gì có vẻ phù hợp nhất với ứng dụng và gắn bó với nó (với tất cả các nhược điểm của nó, tức là không sử dụng cả hai mẫu cùng một lúc ... trừ khi bạn thực sự chắc chắn về điều đó).

Như một lưu ý phụ: trong ví dụ của bạn là RetrieveStudent, AddStudent phải là các phương thức tĩnh (vì chúng không phụ thuộc vào thể hiện).

Một cách khác để có các phương thức lưu / tải trong lớp là:

class Animal{
    public string Name {get; set;}
    public void Save(){...}
    public static Animal Load(string name){....}
    public static string[] GetAllNames(){...} // if you just want to list them
    public static Animal[] GetAllAnimals(){...} // if you actually need to retrieve them all
}

Cá nhân tôi chỉ sử dụng cách tiếp cận như vậy trong một ứng dụng khá nhỏ, có thể là các công cụ để sử dụng cá nhân hoặc nơi tôi có thể dự đoán một cách đáng tin cậy rằng tôi sẽ không gặp trường hợp sử dụng phức tạp hơn là chỉ lưu hoặc tải đối tượng.

Còn cá nhân, xem mẫu Đơn vị công việc. Khi bạn làm quen với nó, nó thực sự tốt trong cả trường hợp nhỏ và lớn. Và nó được hỗ trợ bởi rất nhiều khung / apis, để đặt tên EntityFramework hoặc RavenDB chẳng hạn.


2
Về lưu ý phụ của bạn: Mặc dù, về mặt khái niệm, chỉ có một DB trong khi ứng dụng đang chạy, điều đó không có nghĩa là bạn nên làm cho các phương thức RetriveStudent và AddStudent tĩnh. Các kho lưu trữ thường được thực hiện với các giao diện để cho phép các nhà phát triển chuyển đổi triển khai mà không ảnh hưởng đến phần còn lại của ứng dụng. Điều này thậm chí có thể cho phép bạn phân phối ứng dụng của mình với sự hỗ trợ cho các cơ sở dữ liệu khác nhau. Tất nhiên, logic tương tự này, áp dụng cho nhiều lĩnh vực khác ngoài sự kiên trì, ví dụ như UI.
MichelHenrich

Bạn chắc chắn đúng, tôi đã đưa ra rất nhiều giả định dựa trên câu hỏi ban đầu.
Gerino

cảm ơn bạn, tôi nghĩ cách tiếp cận tốt nhất là sử dụng các lớp như được đề cập trong mẫu Kho lưu trữ
Ahmad

1

Nếu đó là một ứng dụng rất đơn giản trong đó đối tượng ít nhiều bị ràng buộc với kho dữ liệu và ngược lại (nghĩa là có thể được coi là một thuộc tính của kho dữ liệu), thì có một phương thức .save () cho lớp đó có thể có ý nghĩa.

Nhưng tôi nghĩ rằng nó sẽ khá đặc biệt.

Thay vào đó, tốt hơn là để cho lớp quản lý dữ liệu và chức năng của nó (như một công dân OO tốt) và đưa cơ chế bền bỉ sang một lớp khác hoặc tập hợp các lớp.

Tùy chọn khác là sử dụng một khung kiên trì xác định khai báo bền bỉ (như với các chú thích), nhưng điều đó vẫn xuất hiện bên ngoài sự tồn tại.


-1
  • Xác định một phương thức trên lớp đó tuần tự hóa đối tượng và trả về một chuỗi byte mà bạn có thể đặt trong cơ sở dữ liệu hoặc tệp hoặc bất cứ thứ gì
  • Có một hàm tạo cho đối tượng đó lấy một chuỗi byte như vậy làm đầu vào

Về RetrieveAllStudents()phương pháp, cảm giác của bạn là đúng, nó thực sự có thể bị đặt sai chỗ, bởi vì bạn có thể có nhiều danh sách sinh viên khác nhau. Tại sao không chỉ đơn giản là giữ (các) danh sách bên ngoài Studentlớp học?


Bạn biết tôi đã thấy xu hướng như vậy trong một số khung công tác PHP, ví dụ như Laravel nếu bạn biết. Mô hình được gắn với cơ sở dữ liệu và thậm chí có thể trả về một loạt các đối tượng.
Ahmad
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.