Áp dụng các nguyên tắc RẮN


13

Tôi còn khá mới với các nguyên tắc thiết kế RẮN . Tôi hiểu nguyên nhân và lợi ích của chúng, nhưng tôi không áp dụng chúng cho một dự án nhỏ hơn mà tôi muốn tái cấu trúc như một bài tập thực tế để sử dụng các nguyên tắc RẮN. Tôi biết không cần phải thay đổi một ứng dụng hoạt động hoàn hảo, nhưng dù sao tôi cũng muốn cấu trúc lại nó để tôi có được kinh nghiệm thiết kế cho các dự án trong tương lai.

Ứng dụng này có nhiệm vụ sau (thực tế nhiều hơn thế nhưng hãy đơn giản hóa): Nó phải đọc một tệp XML chứa các định nghĩa Bảng / Cột / Xem cơ sở dữ liệu và tạo một tệp SQL có thể được sử dụng để tạo một lược đồ cơ sở dữ liệu ORACLE.

(Lưu ý: Vui lòng không thảo luận về lý do tại sao tôi cần nó hoặc tại sao tôi không sử dụng XSLT, v.v., có những lý do, nhưng chúng không có chủ đề.)

Khi bắt đầu, tôi chọn chỉ nhìn vào Bảng và ràng buộc. Nếu bạn bỏ qua các cột, bạn có thể nêu nó theo cách sau:

Ràng buộc là một phần của bảng (hay chính xác hơn là một phần của câu lệnh CREATE TABLE) và một ràng buộc cũng có thể tham chiếu một bảng khác.

Đầu tiên, tôi sẽ giải thích ứng dụng trông như thế nào ngay bây giờ (không áp dụng RẮN):

Hiện tại, ứng dụng này có một lớp "Bảng" chứa danh sách các con trỏ tới các ràng buộc thuộc sở hữu của bảng và một danh sách các con trỏ tới các ràng buộc tham chiếu bảng này. Bất cứ khi nào một kết nối được thiết lập, kết nối ngược cũng sẽ được thiết lập. Bảng này có một phương thức createStatement () mà lần lượt gọi hàm createdStatement () của mỗi ràng buộc. Phương thức đã nói sẽ tự sử dụng các kết nối đến bảng chủ sở hữu và bảng được tham chiếu để truy xuất tên của chúng.

Rõ ràng, điều này hoàn toàn không áp dụng cho RẮN. Ví dụ, có các phụ thuộc vòng tròn, làm phồng mã theo các phương thức "thêm" / "loại bỏ" cần thiết và một số hàm hủy đối tượng lớn.

Vì vậy, có một vài câu hỏi:

  1. Tôi có nên giải quyết các phụ thuộc vòng tròn bằng cách sử dụng Dependency Injection? Nếu vậy, tôi cho rằng ràng buộc sẽ nhận được bảng của chủ sở hữu (và tùy chọn là tham chiếu) trong hàm tạo của nó. Nhưng làm thế nào tôi có thể chạy qua danh sách các ràng buộc cho một bảng sau đó?
  2. Nếu lớp Bảng vừa lưu trữ trạng thái của chính nó (ví dụ như tên bảng, nhận xét bảng, v.v.) và các liên kết đến các ràng buộc, thì đây có phải là một hoặc hai "trách nhiệm", nghĩ về Nguyên tắc Trách nhiệm duy nhất không?
  3. Trong trường hợp 2. đúng, tôi có nên tạo một lớp mới trong lớp nghiệp vụ logic quản lý các liên kết không? Nếu vậy, 1. rõ ràng sẽ không còn phù hợp.
  4. Các phương thức "createStatement" có nên là một phần của các lớp Bảng / Ràng buộc hay tôi cũng nên chuyển chúng ra? Nếu vậy thì ở đâu? Một lớp Manager cho mỗi lớp lưu trữ dữ liệu (ví dụ: Bảng, ràng buộc, ...)? Hay đúng hơn là tạo một lớp người quản lý cho mỗi liên kết (tương tự như 3.)?

Bất cứ khi nào tôi cố gắng trả lời một trong những câu hỏi này, tôi thấy mình đang chạy trong vòng tròn ở đâu đó.

Vấn đề rõ ràng trở nên phức tạp hơn rất nhiều nếu bạn bao gồm các cột, chỉ mục, v.v., nhưng nếu các bạn giúp tôi làm điều đơn giản với Bảng / Ràng buộc, tôi có thể tự mình giải quyết phần còn lại.


3
Bạn đang sử dụng ngôn ngữ nào? Bạn có thể gửi ít nhất một số mã bộ xương? Rất khó để thảo luận về chất lượng mã và các phép tái cấu trúc có thể mà không thấy mã thực tế.
Péter Török

Tôi đang sử dụng C ++ nhưng tôi đã cố gắng tránh xa cuộc thảo luận vì bạn có thể gặp vấn đề này bằng bất kỳ ngôn ngữ nào
Tim Meyer

Có, nhưng việc áp dụng các mẫu và tái cấu trúc phụ thuộc vào ngôn ngữ. Ví dụ: @ back2dos đã đề xuất AOP trong câu trả lời của anh ấy bên dưới, điều này rõ ràng không áp dụng cho C ++.
Péter Török

Câu trả lời:


8

Bạn có thể bắt đầu từ một quan điểm khác để áp dụng "Nguyên tắc trách nhiệm duy nhất" tại đây. Những gì bạn đã chỉ ra cho chúng tôi là (ít nhiều) chỉ là mô hình dữ liệu của ứng dụng của bạn. SRP ở đây có nghĩa là: đảm bảo mô hình dữ liệu của bạn chỉ chịu trách nhiệm giữ dữ liệu - không hơn không kém.

Vì vậy, khi bạn định đọc tệp XML của mình, hãy tạo một mô hình dữ liệu từ nó và viết SQL, điều bạn không nên làm là triển khai bất cứ điều gì vào Tablelớp của bạn , cụ thể là XML hoặc SQL. Bạn muốn luồng dữ liệu của bạn trông như thế này:

[XML] -> ("Read XML") -> [Data model of DB definition] -> ("Write SQL") -> [SQL]

Vì vậy, nơi duy nhất đặt mã cụ thể XML là một lớp có tên, chẳng hạn , Read_XML. Nơi duy nhất cho mã cụ thể SQL phải là một lớp như thế nào Write_SQL. Tất nhiên, có thể bạn sẽ chia 2 nhiệm vụ đó thành nhiều nhiệm vụ phụ hơn (và chia các lớp của bạn thành nhiều lớp người quản lý), nhưng "mô hình dữ liệu" của bạn không chịu bất kỳ trách nhiệm nào từ lớp đó. Vì vậy, đừng thêm createStatementvào bất kỳ lớp mô hình dữ liệu nào của bạn, vì điều này mang lại trách nhiệm cho mô hình dữ liệu của bạn đối với SQL.

Tôi không thấy bất kỳ vấn đề nào khi bạn mô tả rằng Bảng chịu trách nhiệm giữ tất cả các phần của nó, (tên, cột, nhận xét, ràng buộc ...), đó là ý tưởng đằng sau mô hình dữ liệu. Nhưng bạn đã mô tả "Bảng" cũng chịu trách nhiệm quản lý bộ nhớ của một số bộ phận của nó. Đó là một vấn đề cụ thể của C ++, mà bạn sẽ không gặp phải dễ dàng như vậy trong các ngôn ngữ như Java hoặc C #. Cách C ++ để loại bỏ trách nhiệm đó là sử dụng các con trỏ thông minh, ủy quyền sở hữu cho một lớp khác (ví dụ: thư viện boost hoặc lớp con trỏ "thông minh" của riêng bạn). Nhưng hãy cẩn thận, các phụ thuộc theo chu kỳ của bạn có thể "kích thích" một số triển khai con trỏ thông minh.

Đôi điều nữa về RẮN: đây là bài viết hay

http://cre8iveth think.com/blog/2011/08/23/software-development-is-not-a-jenga-game

giải thích RẮN bằng một ví dụ nhỏ. Hãy thử áp dụng điều đó cho trường hợp của bạn:

  • bạn sẽ không chỉ cần các lớp Read_XMLWrite_SQL, mà còn cần một lớp thứ ba quản lý sự tương tác của 2 lớp đó. Hãy gọi nó là a ConversionManager.

  • Áp dụng nguyên tắc DI có thể có nghĩa ở đây: ConversionManager không nên tạo ra các trường hợp Read_XMLWrite_SQLcủa chính nó. Thay vào đó, những đối tượng đó có thể được tiêm thông qua hàm tạo. Và các nhà xây dựng nên có một chữ ký như thế này

    ConversionManager(IDataModelReader reader, IDataModelWriter writer)

nơi IDataModelReadermột giao diện mà từ đó Read_XMLkế thừa, và IDataModelWritertương tự cho Write_SQL. Điều này làm cho một ConversionManagerphần mở rộng (bạn rất dễ dàng cung cấp các trình đọc hoặc tác giả khác nhau) mà không phải thay đổi nó - vì vậy chúng tôi có một ví dụ cho nguyên tắc Mở / Đóng. Hãy suy nghĩ về những gì bạn sẽ phải thay đổi khi bạn muốn hỗ trợ nhà cung cấp cơ sở dữ liệu khác - thực sự, bạn không phải thay đổi bất cứ điều gì trong mô hình dữ liệu của mình, chỉ cần cung cấp SQL-Writer khác.


Mặc dù đây là một bài tập rất hợp lý của RẮN, (lưu ý) lưu ý rằng nó vi phạm "trường học cũ Kay / Holub OOP" bằng cách yêu cầu getters và setters cho Mô hình dữ liệu khá thiếu máu. Nó cũng làm tôi nhớ đến câu nói nổi tiếng của Steve Yegge .
dùng949300

2

Chà, bạn nên áp dụng S của RẮN trong trường hợp này.

Một bảng chứa tất cả các ràng buộc được xác định trên nó. Một ràng buộc giữ tất cả các bảng mà nó tham chiếu. Mô hình đơn giản và đơn giản.

Những gì bạn dính vào đó, là khả năng thực hiện tra cứu ngược, nghĩa là tìm ra các ràng buộc mà một số bảng được tham chiếu.
Vì vậy, những gì bạn thực sự muốn là một dịch vụ lập chỉ mục. Đó là một nhiệm vụ hoàn toàn khác và do đó nên được thực hiện bởi một đối tượng khác.

Để chia nó thành một phiên bản rất đơn giản:

class Table {
      void addConstraint(Constraint constraint) { ... }
      bool removeConstraint(Constraint constraint) { ... }
      Iterator<Constraint> getConstraints() { ... }
}
class Constraint {
      //actually I am not so sure these two should be exposed directly at all
      void addReference(Table to) { ... }
      bool removeReference(Table to) { ... }
      Iterator<Table> getReferencedTables() { ... }
}
class Database {
      void addTable(Table table) { ... }
      bool removeTable(Table table) { ... }
      Iterator<Table> getTables() { ... }
}
class Index {
      Iterator<Constraint> getConstraintsReferencing(Table target) { ... }
}

Đối với việc thực hiện chỉ mục, có 3 cách để đi:

  • các getContraintsReferencingphương pháp có thể thực sự chỉ là thu thập dữ liệu toàn bộ Databasecho Tabletrường hợp và bò của họ Constraints để có được kết quả. Tùy thuộc vào mức độ tốn kém này và mức độ thường xuyên bạn cần nó, nó có thể là một lựa chọn.
  • nó cũng có thể sử dụng bộ đệm. Nếu mô hình cơ sở dữ liệu của bạn có thể thay đổi một khi được xác định, bạn có thể duy trì bộ đệm bằng cách bắn các tín hiệu từ các trường hợp TableConstrainttrường hợp tương ứng , khi chúng thay đổi. Một giải pháp đơn giản hơn một chút là Indexxây dựng một "chỉ số ảnh chụp nhanh" của toàn bộ Databaseđể làm việc, sau đó bạn sẽ loại bỏ. Tất nhiên điều đó chỉ có thể, nếu ứng dụng của bạn tạo ra sự khác biệt lớn giữa "thời gian mô hình hóa" và "thời gian truy vấn". Nếu nó có khả năng thực hiện hai việc đó cùng một lúc, thì điều này là không khả thi.
  • Một tùy chọn khác là sử dụng AOP để chặn toàn bộ các cuộc gọi tạo và duy trì chỉ mục tương ứng.

Câu trả lời rất chi tiết, tôi thích giải pháp của bạn cho đến nay! Bạn sẽ nghĩ gì nếu tôi thực hiện DI cho lớp Bảng, đưa ra danh sách các ràng buộc trong quá trình xây dựng? Dù sao tôi cũng có một lớp TableParser, có thể hoạt động như một nhà máy hoặc làm việc cùng với một nhà máy cho trường hợp đó.
Tim Meyer

@Tim Meyer: DI không nhất thiết phải là constructor. DI cũng có thể được thực hiện bởi các chức năng thành viên. Nếu Bảng sẽ nhận được tất cả các phần của nó thông qua hàm tạo tùy thuộc vào việc bạn muốn các phần đó chỉ được thêm vào lúc xây dựng và không bao giờ thay đổi sau đó hay nếu bạn muốn tạo bảng từng bước. Đó nên là cơ sở của quyết định thiết kế của bạn.
Doc Brown

1

Cách chữa trị cho các phụ thuộc vòng tròn là thề rằng bạn sẽ không bao giờ, không bao giờ tạo ra chúng. Tôi thấy rằng thử nghiệm mã hóa đầu tiên là một yếu tố ngăn chặn mạnh mẽ.

Dù sao, phụ thuộc vòng tròn luôn có thể bị phá vỡ bằng cách giới thiệu một lớp cơ sở trừu tượng. Đây là điển hình cho biểu diễn đồ thị. Ở đây các bảng là các nút và các ràng buộc khóa ngoài là các cạnh. Vì vậy, tạo một lớp Bảng trừu tượng và một lớp ràng buộc trừu tượng và có thể là một lớp Cột trừu tượng. Sau đó, tất cả các triển khai có thể phụ thuộc vào các lớp trừu tượng. Đây có thể không phải là đại diện tốt nhất có thể, nhưng nó là một sự cải tiến so với các lớp kết hợp lẫn nhau.

Nhưng, như bạn nghi ngờ, giải pháp tốt nhất cho vấn đề này có thể không yêu cầu bất kỳ sự theo dõi nào về mối quan hệ đối tượng. Nếu bạn chỉ muốn dịch XML sang SQL, thì bạn không cần biểu diễn trong bộ nhớ của biểu đồ ràng buộc. Biểu đồ ràng buộc sẽ rất tuyệt nếu bạn muốn chạy các thuật toán đồ thị, nhưng bạn đã không đề cập đến điều đó vì vậy tôi sẽ cho rằng đó không phải là một yêu cầu. Bạn chỉ cần một danh sách các bảng và một danh sách các ràng buộc và một khách truy cập cho mỗi phương ngữ SQL mà bạn muốn hỗ trợ. Tạo các bảng, sau đó tạo các ràng buộc bên ngoài cho các bảng. Cho đến khi các yêu cầu thay đổi, tôi sẽ không gặp vấn đề gì với việc ghép trình tạo SQL với XML DOM. Tiết kiệm vào ngày mai cho ngày mai.


Đây là nơi "(thực tế là nhiều hơn thế nhưng hãy giữ cho nó đơn giản)" đi vào hoạt động. Ví dụ, có những trường hợp tôi cần xóa một bảng, vì vậy tôi cần kiểm tra xem có bất kỳ ràng buộc nào tham chiếu bảng này không.
Tim Meyer
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.