Hợp pháp công việc thực tế trực tiếp trên một nhà xây dựng?


23

Tôi đang làm việc trên một thiết kế, nhưng tiếp tục đạt được một rào cản. Tôi có một lớp cụ thể (ModelDef) về cơ bản là chủ sở hữu của một cây nút phức được xây dựng bằng cách phân tích cú pháp lược đồ XML (nghĩ DOM). Tôi muốn tuân theo các nguyên tắc thiết kế tốt (RẮN) và đảm bảo rằng hệ thống kết quả có thể dễ dàng kiểm tra. Tôi có mọi ý định sử dụng DI để chuyển các phụ thuộc vào hàm tạo của ModelDef (để chúng có thể dễ dàng hoán đổi, nếu cần, trong khi thử nghiệm).

Tuy nhiên, điều tôi đang vật lộn với việc tạo ra cây nút. Cây này sẽ được tạo thành hoàn toàn từ các đối tượng "giá trị" đơn giản mà không cần phải được kiểm tra độc lập. (Tuy nhiên, tôi vẫn có thể chuyển một Nhà máy Trừu tượng vào ModelDef để hỗ trợ việc tạo các đối tượng này.)

Nhưng tôi tiếp tục đọc rằng một nhà xây dựng không nên làm bất kỳ công việc thực tế nào (ví dụ Flaw: Con constructor does Real Work ). Điều này có ý nghĩa hoàn hảo với tôi nếu "công việc thực tế" có nghĩa là xây dựng các đối tượng phụ thuộc nặng nề mà sau này người ta có thể muốn lấy ra để thử nghiệm. (Chúng nên được chuyển qua DI.)

Nhưng những gì về các đối tượng giá trị trọng lượng nhẹ như cây nút này? Cây phải được tạo ra ở đâu đó, phải không? Tại sao không thông qua hàm tạo của ModelDef (sử dụng phương thức buildNodeTree ())?

Tôi thực sự không muốn tạo cây nút bên ngoài ModelDef và sau đó chuyển nó vào (thông qua hàm tạo DI), bởi vì việc tạo cây nút bằng cách phân tích lược đồ đòi hỏi một lượng mã phức tạp đáng kể - mã cần được kiểm tra kỹ lưỡng . Tôi không muốn chuyển mã thành "keo" mã (tương đối tầm thường và có thể sẽ không được kiểm tra trực tiếp).

Tôi đã nghĩ đến việc đặt mã để tạo cây nút trong một đối tượng "trình xây dựng" riêng biệt, nhưng ngần ngại gọi nó là "trình xây dựng", vì nó không thực sự khớp với Mô hình Builder (dường như quan tâm nhiều hơn đến việc loại bỏ kính viễn vọng nhà xây dựng). Nhưng ngay cả khi tôi gọi nó là một cái gì đó khác (ví dụ: NodeTreeConstructor), nó vẫn cảm thấy giống như một chút hack chỉ để tránh việc nhà xây dựng ModelDef xây dựng cây nút. Nó phải được xây dựng ở đâu đó; Tại sao không phải là đối tượng sẽ sở hữu nó?


7
Mặc dù vậy, bạn nên luôn cảnh giác với những tuyên bố về chăn như thế. Nguyên tắc chung là mã theo cách rõ ràng, chức năng, dễ kiểm tra, tái sử dụng và bảo trì, bất kỳ cách nào có thể, thay đổi tùy thuộc vào tình huống của bạn. Nếu bạn không đạt được gì ngoài sự phức tạp về mã và sự nhầm lẫn khi cố gắng tuân theo một "quy tắc" như thế thì đó không phải là một quy tắc phù hợp cho tình huống của bạn. Tất cả các "mẫu" và tính năng ngôn ngữ này là các công cụ; sử dụng tốt nhất cho công việc cụ thể của bạn.
Jason C

Câu trả lời:


26

Và, bên cạnh những gì Ross Patterson đề xuất, hãy xem xét vị trí này hoàn toàn ngược lại:

  1. Lấy các câu châm ngôn như "Ngươi sẽ không làm bất kỳ công việc thực sự nào trong các nhà xây dựng của bạn" với một hạt muối.

  2. Một constructor thực sự không có gì ngoài một phương thức tĩnh. Vì vậy, về mặt cấu trúc, thực sự không có nhiều khác biệt giữa:

    a) một hàm tạo đơn giản và một loạt các phương thức tĩnh nhà máy phức tạp, và

    b) một hàm tạo đơn giản và một loạt các hàm tạo phức tạp hơn.

Một phần đáng kể của tâm lý tiêu cực đối với việc thực hiện bất kỳ công việc thực tế nào trong các nhà xây dựng xuất phát từ một giai đoạn nhất định của lịch sử C ++ khi có tranh luận về việc chính xác trạng thái nào sẽ để lại nếu một ngoại lệ được ném vào bên trong nhà xây dựng, và liệu các hàm hủy phải được gọi trong một sự kiện như vậy. Đó là một phần của lịch sử C ++ đã kết thúc và vấn đề đã được giải quyết, trong khi trong các ngôn ngữ như Java, không bao giờ có bất kỳ vấn đề nào thuộc loại này bắt đầu.

Ý kiến ​​của tôi là nếu bạn đơn giản tránh sử dụng newtrong hàm tạo, (như ý định của bạn sử dụng Dependency Injection chỉ ra,) bạn sẽ ổn. Tôi cười vào những câu như "logic có điều kiện hoặc lặp trong một hàm tạo là một dấu hiệu cảnh báo về một lỗ hổng".

Ngoài ra, cá nhân tôi, tôi sẽ đưa logic phân tích cú pháp XML ra khỏi hàm tạo, không phải vì nó có logic phức tạp trong một hàm tạo, mà bởi vì nó là tốt để tuân theo nguyên tắc "phân tách các mối quan tâm". Vì vậy, tôi sẽ chuyển logic phân tích cú pháp XML vào một số lớp riêng biệt, chứ không phải vào một số phương thức tĩnh thuộc về ModelDeflớp của bạn .

Sửa đổi

Tôi giả sử rằng nếu bạn có một phương thức bên ngoài ModelDeftạo ra ModelDeftừ XML, bạn sẽ cần khởi tạo một số cấu trúc dữ liệu cây tạm thời động, điền vào nó bằng cách phân tích cú pháp XML của bạn và sau đó tạo ModelDefcấu trúc mới chuyển qua cấu trúc đó làm tham số của hàm tạo. Vì vậy, đó có lẽ có thể được coi là một ứng dụng của mẫu "Builder". Có một sự tương đồng rất gần giữa những gì bạn muốn làm và cặp String& StringBuilder. Tuy nhiên, tôi đã tìm thấy Câu hỏi và trả lời này có vẻ không đồng ý, vì những lý do không rõ ràng đối với tôi: Stackoverflow - StringBuilder và Builder Pattern . Vì vậy, để tránh một cuộc tranh luận kéo dài ở đây về việc liệu StringBuildercó hay không thực hiện mô hình "người xây dựng", tôi sẽ nói rằng hãy thoải mái để được truyền cảm hứng bằng cáchStrungBuilder làm việc với một giải pháp phù hợp với nhu cầu của bạn và hoãn gọi nó là một ứng dụng của mẫu "Builder" cho đến khi chi tiết nhỏ đó được giải quyết.

Xem câu hỏi hoàn toàn mới này: Lập trình viên SE: Có phải String Stringilder là một ứng dụng của Mẫu thiết kế Builder?


3
@RichardLevasseur Tôi chỉ nhớ nó là một chủ đề quan tâm và tranh luận giữa các lập trình viên C ++ vào đầu những năm 1990. Nếu bạn nhìn vào bài đăng này: gotw.ca/gotw/066.htm bạn sẽ thấy rằng nó khá phức tạp và những thứ khá phức tạp có xu hướng gây tranh cãi. Tôi không biết chắc chắn, nhưng tôi nghĩ rằng vào đầu những năm chín mươi, những thứ đó thậm chí còn chưa được chuẩn hóa. Nhưng xin lỗi, tôi không thể cung cấp một tài liệu tham khảo tốt.
Mike Nakis

1
@Gurtz Tôi sẽ nghĩ về một lớp như "tiện ích xml" dành riêng cho ứng dụng, vì định dạng của tệp xml (hoặc cấu trúc của tài liệu) có thể được gắn với ứng dụng cụ thể mà bạn đang phát triển, bất kể mọi khả năng đối với sử dụng lại "ModelDef" của bạn.
Mike Nakis

1
@Gurtz vì vậy, tôi có thể biến chúng thành các phương thức cá thể của lớp "Ứng dụng" chính hoặc nếu điều đó quá rắc rối, thì các phương thức tĩnh của một số lớp trợ giúp, theo cách rất giống với những gì Ross Patterson đề xuất.
Mike Nakis

1
@Gurtz xin lỗi vì đã không giải quyết cụ thể cách tiếp cận "người xây dựng" trước đó. Tôi đã sửa đổi câu trả lời của tôi.
Mike Nakis

3
@Gurtz Có thể nhưng ngoài sự tò mò về học thuật, nó không thành vấn đề. Đừng để bị hút vào "mô hình chống mẫu". Các mẫu thực sự chỉ là tên để mô tả các kỹ thuật mã hóa phổ biến / hữu ích cho người khác một cách thuận tiện. Làm những gì bạn cần làm, tát một nhãn vào nó sau nếu bạn cần mô tả nó. Theo cách nào đó, hoàn toàn ổn khi thực hiện một cái gì đó "giống như mẫu xây dựng, theo một cách nào đó, có thể", miễn là mã của bạn có ý nghĩa. Thật hợp lý khi tập trung vào các mẫu khi học các kỹ thuật mới, chỉ cần không rơi vào một cái bẫy nghĩ rằng mọi thứ bạn làm phải là một số mẫu được đặt tên.
Jason C

9

Bạn đã đưa ra những lý do tốt nhất để không thực hiện công việc này trong hàm ModelDeftạo:

  1. Không có gì "nhẹ" về việc phân tích tài liệu XML vào cây nút.
  2. Không có gì rõ ràng về ModelDefviệc nói rằng nó chỉ có thể được tạo từ một tài liệu XML.

Nghe có vẻ như lớp học của bạn nên có một loạt các phương pháp tĩnh như ModelDef.FromXmlString(string xmlDocument), ModelDef.FromXmlDoc(XmlDoc parsedNodeTree), , vv


Cảm ơn vi đa trả lơi! Về đề xuất phương pháp tĩnh. Đây có phải là các nhà máy tĩnh tạo ra một thể hiện của ModelDef (từ các nguồn xml khác nhau) không? Hoặc họ sẽ chịu trách nhiệm tải một đối tượng ModelDef đã được tạo? Nếu sau này, tôi lo ngại về việc đối tượng chỉ được khởi tạo một phần (vì ModelDef cần một cây nút để được khởi tạo hoàn toàn). Suy nghĩ?
Gurtz

3
Xin lỗi vì đã bỏ qua, nhưng đúng vậy, ý nghĩa của Ross là các phương thức tĩnh của nhà máy trả về các thể hiện được xây dựng đầy đủ. Nguyên mẫu đầy đủ sẽ là một cái gì đó như public static ModelDef createFromXmlString( string xmlDocument ). Đây là thực tế khá phổ biến. Đôi khi tôi cũng làm điều đó. Đề nghị của tôi rằng bạn cũng có thể thực hiện chỉ các nhà xây dựng là một loại phản ứng tiêu chuẩn của tôi trong các tình huống mà tôi nghi ngờ rằng các phương pháp thay thế bị loại bỏ là "không kosher" mà không có lý do chính đáng.
Mike Nakis

1
@ Mike-Nakis, cảm ơn đã làm rõ. Vì vậy, trong trường hợp này, phương thức nhà máy tĩnh sẽ xây dựng cây nút và sau đó chuyển nó vào hàm tạo (có thể là riêng tư) của ModelDef. Có lý. Cảm ơn.
Gurtz

@Gurtz Chính xác.
Ross Patterson

5

Tôi đã nghe nói rằng "quy tắc" trước đây. Theo kinh nghiệm của tôi, nó là cả đúng và sai.

Trong định hướng đối tượng "cổ điển" hơn, chúng ta nói về các đối tượng gói gọn trạng thái và hành vi. Do đó, một hàm tạo đối tượng cần đảm bảo rằng đối tượng được khởi tạo ở trạng thái hợp lệ (và báo hiệu lỗi nếu các đối số được cung cấp không làm cho đối tượng hợp lệ). Đảm bảo rằng một đối tượng được khởi tạo ở trạng thái hợp lệ chắc chắn nghe giống như công việc thực sự đối với tôi. Và ý tưởng này có giá trị, nếu bạn có một đối tượng chỉ cho phép khởi tạo thành trạng thái hợp lệ thông qua hàm tạo đối tượng đóng gói đúng trạng thái của nó để mỗi phương thức thay đổi trạng thái cũng kiểm tra rằng nó không thay đổi trạng thái thành trạng thái xấu ... thì đối tượng đó về bản chất đảm bảo rằng nó "luôn hợp lệ". Đó là một tài sản thực sự tốt đẹp!

Vì vậy, vấn đề thường xuất hiện khi chúng ta cố gắng chia mọi thứ thành các phần nhỏ để kiểm tra và chế nhạo công cụ. Bởi vì nếu một đối tượng thực sự được đóng gói đúng cách thì bạn không thể thực sự vào đó và thay thế FooBarService bằng FooBarService bị chế giễu của bạn và bạn (có thể) không thể thay đổi giá trị willy-nilly cho phù hợp với các thử nghiệm của bạn (hoặc, nó có thể mất một nhiều mã hơn một bài tập đơn giản).

Do đó, chúng ta có được "trường phái tư tưởng" khác, đó là RẮN. Và trong trường phái tư tưởng này rất có thể đúng hơn rất nhiều rằng chúng ta không nên làm công việc thực sự trong nhà xây dựng. Mã RẮN thường (nhưng không phải luôn luôn) dễ kiểm tra hơn. Nhưng cũng có thể khó hơn để lý do về. Chúng tôi chia mã của chúng tôi thành các đối tượng nhỏ với một trách nhiệm duy nhất và do đó, hầu hết các đối tượng của chúng tôi không còn gói gọn trạng thái của chúng (và thường chứa trạng thái hoặc hành vi). Mã xác nhận thường được trích xuất thành một lớp trình xác nhận và được tách biệt khỏi trạng thái. Nhưng bây giờ chúng tôi đã mất sự gắn kết, chúng tôi không còn có thể chắc chắn rằng các đối tượng của chúng tôi có giá trị khi chúng tôi có được chúng và hoàn toànchắc chắn rằng chúng ta phải luôn xác nhận rằng các điều kiện tiên quyết mà chúng ta nghĩ rằng chúng ta có về đối tượng là đúng trước khi chúng ta cố gắng làm điều gì đó với đối tượng. (Tất nhiên, nói chung, bạn thực hiện xác nhận trong một lớp và sau đó giả sử rằng đối tượng hợp lệ ở các lớp thấp hơn.) Nhưng việc kiểm tra sẽ dễ dàng hơn!

Vậy ai đúng?

Không một ai sẵn sàng. Cả hai trường phái tư tưởng đều có công. Hiện tại RẮN là tất cả cơn thịnh nộ và mọi người đang nói về SRP và Mở / Đóng và tất cả nhạc jazz đó. Nhưng chỉ vì một cái gì đó phổ biến không có nghĩa là nó là lựa chọn thiết kế chính xác cho mọi ứng dụng. Vì vậy, nó phụ thuộc. Nếu bạn đang làm việc trong một cơ sở mã tuân theo nhiều nguyên tắc RẮN thì có, công việc thực sự trong hàm tạo có lẽ là một ý tưởng tồi. Nhưng nếu không, hãy nhìn vào tình huống và cố gắng sử dụng phán đoán của bạn. Những thuộc tính nào mà đối tượng của bạn có được khi thực hiện công việc trong hàm tạo, nó mất những thuộc tính nào ? Làm thế nào nó phù hợp với kiến ​​trúc tổng thể của ứng dụng của bạn?

Công việc thực sự trong hàm tạo không phải là một antipotype, nó có thể hoàn toàn ngược lại khi được sử dụng ở những vị trí chính xác. Nhưng nó phải được ghi lại rõ ràng (cùng với trường hợp ngoại lệ nào có thể được ném, nếu có) và như với bất kỳ quyết định thiết kế nào - nó phải phù hợp với phong cách chung được sử dụng trong cơ sở mã hiện tại.


Đây là một câu trả lời tuyệt vời.
jrahhali

0

Có một vấn đề cơ bản với quy tắc này và đó là, cái gì tạo thành "công việc thực sự"?

Bạn có thể thấy từ bài viết gốc được đăng trong câu hỏi mà tác giả cố gắng xác định "công việc thực sự" là gì, nhưng nó rất thiếu sót. Để một thực hành được tốt, nó cần phải là một nguyên tắc được xác định rõ. Điều đó có nghĩa là vì tôn trọng công nghệ phần mềm, ý tưởng nên có thể mang theo (bất khả tri đối với bất kỳ ngôn ngữ nào), đã được thử nghiệm và chứng minh. Hầu hết những gì được tranh luận trong bài viết đó không phù hợp với tiêu chí đầu tiên đó. Dưới đây là một số chỉ số mà tác giả đề cập trong bài viết đó về những gì cấu thành "công việc thực sự" và tại sao chúng không phải là định nghĩa xấu.

Sử dụng newtừ khóa . Định nghĩa đó về cơ bản là thiếu sót vì đó là miền cụ thể. Một số ngôn ngữ không sử dụng newtừ khóa. Cuối cùng, điều anh ta ám chỉ là không nên xây dựng các vật thể khác. Tuy nhiên, trong nhiều ngôn ngữ, ngay cả các giá trị cơ bản nhất cũng là đối tượng. Vì vậy, bất kỳ giá trị nào được gán trong hàm tạo cũng đang xây dựng một đối tượng mới. Điều đó làm cho ý tưởng này bị giới hạn trong một số ngôn ngữ nhất định và là một chỉ báo tồi cho những gì cấu thành "công việc thực sự".

Đối tượng không được khởi tạo đầy đủ sau khi hàm tạo kết thúc . Đây là một quy tắc tốt, nhưng nó cũng mâu thuẫn với một số quy tắc khác được đề cập trong bài viết đó. Một ví dụ điển hình về việc làm thế nào điều đó có thể mâu thuẫn với những người khác được đề cập đến câu hỏi đưa tôi đến đây. Trong câu hỏi đó, ai đó quan tâm đến việc sử dụng sortphương thức trong một hàm tạo trong cái có vẻ là JavaScript vì nguyên tắc này. Trong ví dụ này, người đó đã tạo ra một đối tượng đại diện cho một danh sách sắp xếp các đối tượng khác. Với mục đích thảo luận, hãy tưởng tượng rằng chúng ta có một danh sách các đối tượng chưa được sắp xếp và chúng ta cần một đối tượng mới để thể hiện một danh sách được sắp xếp. Chúng tôi cần đối tượng mới này vì một số phần mềm của chúng tôi đang mong đợi một danh sách được sắp xếp và cho phép gọi đối tượng nàySortedList. Đối tượng mới này chấp nhận một danh sách chưa sắp xếp và đối tượng kết quả sẽ đại diện cho một danh sách các đối tượng được sắp xếp. Nếu chúng ta tuân theo các quy tắc khác được đề cập trong tài liệu đó, cụ thể là không gọi phương thức tĩnh, không có cấu trúc luồng điều khiển, không có gì hơn là gán, thì đối tượng kết quả sẽ không được xây dựng trong trạng thái hợp lệ phá vỡ quy tắc khác của nó được khởi tạo hoàn toàn sau khi các nhà xây dựng kết thúc. Để khắc phục điều này, chúng ta sẽ cần thực hiện một số công việc cơ bản để sắp xếp danh sách chưa sắp xếp trong hàm tạo. Làm điều này sẽ phá vỡ 3 quy tắc khác, làm cho các quy tắc khác không liên quan.

Cuối cùng, quy tắc không thực hiện "công việc thực tế" này trong một nhà xây dựng là không xác định và thiếu sót. Cố gắng xác định "công việc thực sự" là một bài tập vô ích. Quy tắc tốt nhất trong bài viết đó là khi một hàm tạo hoàn thành, nó sẽ được khởi tạo hoàn toàn. Có rất nhiều thực tiễn tốt nhất khác sẽ giới hạn số lượng công việc được thực hiện trong một nhà xây dựng. Hầu hết những nguyên tắc này có thể được tóm tắt trong các nguyên tắc RẮN và những nguyên tắc tương tự sẽ không ngăn bạn thực hiện công việc trong hàm tạo.

Tái bút Tôi cảm thấy bắt buộc phải nói rằng trong khi tôi khẳng định ở đây rằng không có gì sai khi thực hiện một số công việc trong nhà xây dựng, thì đó cũng không phải là nơi để thực hiện một loạt công việc. SRP sẽ đề xuất rằng một nhà xây dựng nên làm công việc vừa đủ để làm cho nó hợp lệ. Nếu hàm tạo của bạn có quá nhiều dòng mã (tôi biết rất chủ quan) thì có lẽ nó vi phạm nguyên tắc này và có thể được chia thành các phương thức và đối tượng được xác định tốt hơn nhỏ hơn.

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.