Tại sao một loại sẽ được kết hợp với trình xây dựng của nó?


20

Gần đây tôi đã xóa một câu trả lời của tôi trên Code Review , bắt đầu như thế này:

private Person(PersonBuilder builder) {

Dừng lại. Cờ đỏ. Một PersonBuilder sẽ xây dựng một Người; nó biết về một người. Lớp Person không nên biết bất cứ điều gì về PersonBuilder - đó chỉ là một loại bất biến. Bạn đã tạo ra một khớp nối tròn ở đây, trong đó A phụ thuộc vào B phụ thuộc vào A.

Người chỉ nên lấy thông số của nó; một khách hàng sẵn sàng tạo ra một Người mà không cần xây dựng nên sẽ có thể làm điều đó.

Tôi đã bị tát với một downvote, và nói rằng (trích dẫn) Cờ đỏ, tại sao? Việc triển khai ở đây có hình dạng tương tự như Joshua Bloch đã thể hiện trong cuốn sách "Java hiệu quả" của mình (mục số 2).

Vì vậy, có vẻ như One Right Way trong việc triển khai Mẫu xây dựng trong Java là làm cho trình xây dựng thành một kiểu lồng nhau (đây không phải là câu hỏi này về vấn đề gì), sau đó tạo ra sản phẩm (lớp của đối tượng được xây dựng ) phụ thuộc vào người xây dựng , như thế này:

private StreetMap(Builder builder) {
    // Required parameters
    origin      = builder.origin;
    destination = builder.destination;

    // Optional parameters
    waterColor         = builder.waterColor;
    landColor          = builder.landColor;
    highTrafficColor   = builder.highTrafficColor;
    mediumTrafficColor = builder.mediumTrafficColor;
    lowTrafficColor    = builder.lowTrafficColor;
}

https://en.wikipedia.org/wiki/Builder_potype#Java_example

Cùng một trang Wikipedia cho cùng một mẫu Builder có cách triển khai khác nhau (và linh hoạt hơn nhiều) cho C #:

//Represents a product created by the builder
public class Car
{
    public Car()
    {
    }

    public int Wheels { get; set; }

    public string Colour { get; set; }
}

Như bạn có thể thấy, sản phẩm ở đây không biết gì về một Builderlớp và với tất cả những gì nó quan tâm, nó có thể được khởi tạo bằng một cuộc gọi trực tiếp của nhà xây dựng, một nhà máy trừu tượng, ... hoặc một người xây dựng - theo như tôi hiểu, sản phẩm của một mô hình sáng tạo không bao giờ cần biết bất cứ điều gì về những gì tạo ra nó.

Tôi đã được phục vụ đối số phản đối (rõ ràng được bảo vệ rõ ràng trong cuốn sách của Bloch) rằng một mẫu xây dựng có thể được sử dụng để làm lại một loại có thể có một hàm tạo với hàng tá đối số tùy chọn. Vì vậy, thay vì bám vào những gì tôi nghĩ tôi biết tôi đã nghiên cứu một chút trên trang web này và thấy rằng như tôi nghi ngờ, lập luận này không giữ được nước .

Vậy thỏa thuận là gì? Tại sao lại đưa ra các giải pháp kỹ thuật quá mức cho một vấn đề thậm chí không tồn tại ngay từ đầu? Nếu chúng ta đưa Joshua Bloch ra khỏi bệ của mình trong một phút, chúng ta có thể đưa ra một lý do duy nhất, hợp lệ để ghép hai loại bê tông và gọi đó là cách thực hành tốt nhất không?

Đây là tất cả các chương trình đào tạo hàng hóa đối với tôi.


12
Và "câu hỏi" này chỉ là một câu nói ...
Philip Kendall

2
@PhilipKendall có lẽ một chút. Nhưng tôi thực sự quan tâm đến việc hiểu tại sao mọi triển khai của trình xây dựng Java có sự kết hợp chặt chẽ đó.
Mathieu Guindon

19
@PhilipKendall Nó gây tò mò cho tôi.
Cột

8
@PhilipKendall Nó đọc giống như một câu thần chú, vâng, nhưng cốt lõi của nó là một câu hỏi về chủ đề hợp lệ. Có lẽ các rant có thể được chỉnh sửa?
Andres F.

Tôi không bị ấn tượng bởi đối số "quá nhiều đối số của hàm tạo". Nhưng tôi thậm chí còn ít ấn tượng hơn bởi cả hai ví dụ xây dựng này. Có lẽ bởi vì các ví dụ này quá đơn giản để thể hiện hành vi không tầm thường, nhưng ví dụ Java đọc giống như một giao diện trôi chảy phức tạp và ví dụ C # chỉ là một cách làm tròn để thực hiện các thuộc tính. Không có ví dụ nào thực hiện bất kỳ hình thức xác nhận tham số nào; một hàm tạo ít nhất sẽ xác nhận loại và số lượng đối số được cung cấp, có nghĩa là hàm tạo có quá nhiều đối số thắng.
Robert Harvey

Câu trả lời:


22

Tôi không đồng ý với khẳng định của bạn rằng đó là một lá cờ đỏ. Tôi cũng không đồng ý với cách trình bày của bạn về đối trọng, rằng có một cách đúng để thể hiện một mẫu Builder.

Đối với tôi có một câu hỏi: Trình tạo có phải là một phần cần thiết của API không? Ở đây, PersonBuilder có phải là một phần cần thiết của API người không?

Hoàn toàn hợp lý khi lớp Người được kiểm tra tính hợp lệ, không thay đổi và / hoặc đóng gói chặt chẽ sẽ nhất thiết phải được tạo thông qua Trình tạo mà nó cung cấp (bất kể trình xây dựng đó được lồng hay liền kề). Khi làm như vậy, Người có thể giữ tất cả các trường của mình ở chế độ riêng tư hoặc gói riêng tư và cuối cùng, và cũng để lớp mở để sửa đổi nếu công việc đang được tiến hành. (Nó có thể kết thúc đóng cửa sửa đổi và mở cho phần mở rộng, nhưng đó là không đúng quan trọng bây giờ, và đặc biệt gây nhiều tranh cãi cho các đối tượng dữ liệu không thay đổi.)

Nếu đó là trường hợp một Người nhất thiết phải được tạo thông qua PersonBuilder được cung cấp như một phần của thiết kế API cấp gói duy nhất, thì khớp nối tròn sẽ ổn và cờ đỏ không được bảo hành ở đây. Đáng chú ý, bạn đã tuyên bố nó là một thực tế không thể thay đổi và không phải là ý kiến ​​hay lựa chọn thiết kế API, do đó có thể đã góp phần vào một phản hồi xấu. Tôi sẽ không có downvoted bạn, nhưng tôi không đổ lỗi cho người khác để làm như vậy, và tôi sẽ rời khỏi phần còn lại của cuộc thảo luận về "những gì bảo đảm một downvote" vào trung tâm trợ giúp Mã xétmeta . Downvote xảy ra; đó không phải là "cái tát".

Tất nhiên, nếu bạn muốn mở nhiều cách để xây dựng một đối tượng Person, thì PersonBuilder có thể trở thành người tiêu dùng hoặc tiện íchcủa lớp Người, mà Người không nên phụ thuộc trực tiếp. Sự lựa chọn này có vẻ linh hoạt hơn, những người không muốn có nhiều tùy chọn tạo đối tượng hơn? . Nếu Builder có nghĩa là đại diện hoặc thay thế một hàm tạo với nhiều trường tùy chọn hoặc hàm tạo mở để sửa đổi, thì hàm tạo dài của lớp có thể là một chi tiết triển khai được ẩn tốt hơn. (Cũng nên nhớ rằng một Builder chính thức và cần thiết không ngăn cản bạn viết tiện ích xây dựng của riêng bạn, chỉ là tiện ích xây dựng của bạn có thể sử dụng Builder như một API như trong câu hỏi ban đầu của tôi ở trên.)


ps: Tôi lưu ý rằng mẫu đánh giá mã mà bạn liệt kê có một Người không thay đổi, nhưng mẫu phản hồi từ Wikipedia liệt kê một Xe có thể thay đổi với getters và setters. Có thể dễ dàng hơn để xem các máy móc cần thiết được bỏ qua khỏi Xe nếu bạn giữ ngôn ngữ và bất biến nhất quán giữa chúng.


Tôi nghĩ rằng tôi thấy quan điểm của bạn về việc coi nhà xây dựng là một chi tiết triển khai. Có vẻ như Java hiệu quả truyền tải đúng thông điệp này sau đó (tôi không / không đọc cuốn sách đó), để lại hàng tấn các nhà phát triển Java (?) Giả sử "đó là cách nó được thực hiện" bất kể lẽ thường . Tôi đồng ý rằng các ví dụ Wikipedia là không tốt để so sánh .. nhưng hey đó là Wikipedia .. và FWIW tôi thực sự không quan tâm đến downvote; câu trả lời đã bị xóa đang ngồi với một điểm số dương tích cực (và cơ hội cuối cùng để tôi đạt được một [huy hiệu: áp lực ngang hàng]).
Mathieu Guindon

1
Tuy nhiên, tôi dường như không thể rũ bỏ ý tưởng rằng một loại có quá nhiều đối số của hàm tạo là vấn đề thiết kế (có mùi phá vỡ SRP), rằng một mẫu xây dựng sẽ nhanh chóng bị bong dưới thảm.
Mathieu Guindon

3
@ Mat'sMug Bài đăng của tôi ở trên lấy nó làm cho rằng lớp cần nhiều đối số của hàm tạo . Tôi cũng sẽ coi nó như một mùi thiết kế nếu chúng ta đang nói về một thành phần logic kinh doanh tùy ý, nhưng đối với một đối tượng dữ liệu, không phải là câu hỏi cho một loại có mười hoặc hai mươi thuộc tính trực tiếp. Chẳng hạn, không vi phạm SRP đối với một đối tượng để thể hiện một bản ghi được gõ mạnh trong nhật ký .
Jeff Bowman hỗ trợ Monica

1
Đủ công bằng. String(StringBuilder)Mặc dù vậy, tôi vẫn không nhận được hàm tạo, dường như tuân theo "nguyên tắc" đó khi mà một loại có sự phụ thuộc vào trình xây dựng của nó là bình thường. Tôi đã lúng túng khi thấy nhà xây dựng đó quá tải. Nó không giống như Stringcó 42 tham số hàm tạo ...
Mathieu Guindon

3
@Luaan lẻ. Tôi thực sự chưa bao giờ thấy nó được sử dụng và không biết nó tồn tại; toString()là phổ quát.
chrylis -on đình công-

7

Tôi hiểu mức độ khó chịu của nó khi 'kháng cáo lên chính quyền' được sử dụng trong một cuộc tranh luận. Các lập luận nên đứng trên IMO của chính họ và mặc dù không sai khi chỉ ra lời khuyên của một người được kính trọng như vậy, nhưng thực sự không thể coi đó là một cuộc tranh luận đầy đủ trong chính chúng ta, chúng ta biết Mặt trời đi vòng quanh trái đất vì Aristotle đã nói như vậy .

Có nói rằng, tôi nghĩ tiền đề của loại tranh luận của bạn là như nhau. Chúng ta không bao giờ nên ghép hai loại bê tông và gọi đó là cách thực hành tốt nhất bởi vì ... Ai đó đã nói như vậy?

Để lập luận rằng khớp nối có vấn đề là hợp lệ, cần phải có các vấn đề cụ thể mà nó tạo ra. Nếu phương pháp này không phải chịu những vấn đề đó, thì bạn không thể lập luận một cách hợp lý rằng hình thức khớp nối này có vấn đề. Vì vậy, nếu bạn muốn đưa ra quan điểm của mình ở đây, tôi nghĩ bạn cần chỉ ra cách tiếp cận này tuân theo những vấn đề đó và / hoặc cách thức tách rời nhà xây dựng sẽ cải thiện thiết kế.

Chính thức, tôi đang đấu tranh để xem cách tiếp cận kết hợp sẽ tạo ra vấn đề. Các lớp được ghép hoàn toàn, chắc chắn nhưng trình xây dựng chỉ tồn tại để tạo các thể hiện của lớp. Một cách khác để xem xét nó sẽ là một phần mở rộng của hàm tạo.


Vậy ... khớp nối cao hơn, sự gắn kết thấp hơn => kết thúc có hậu? hmm ... Tôi nhìn thấy điểm về xây dựng "mở rộng các nhà xây dựng", nhưng để đưa lên một ví dụ khác, tôi không nhìn thấy những gì Stringđang làm với một tình trạng quá tải constructor lấy StringBuildertham số (mặc dù nó gây tranh cãi liệu một StringBuilderlà một người thợ xây , nhưng đó của chuyện khác).
Mathieu Guindon

4
@ Mat'sMug Để rõ ràng, tôi thực sự không có cảm giác mạnh về điều này. Tôi không có xu hướng sử dụng mẫu trình xây dựng vì tôi thực sự không tạo các lớp có nhiều đầu vào trạng thái. Tôi thường sẽ phân tách một lớp như vậy vì lý do bạn ám chỉ đến nơi khác. Tất cả những gì tôi nói là nếu bạn có thể chỉ ra cách tiếp cận này dẫn đến một vấn đề, sẽ không có vấn đề gì nếu Chúa xuống và trao mẫu xây dựng đó cho Moses trên một phiến đá. Bạn thắng'. Mặt khác, nếu bạn không thể ...
JimmyJames

Một cách sử dụng hợp pháp mẫu xây dựng mà tôi có trong cơ sở mã của riêng mình, là để chế tạo API VBIDE; về cơ bản, bạn cung cấp nội dung chuỗi của mô-đun mã và trình xây dựng cung cấp cho bạn bản VBEgiả, hoàn chỉnh với các cửa sổ, khung mã hoạt động, dự án và thành phần có mô-đun chứa chuỗi bạn cung cấp. Không có nó, tôi không biết làm thế nào tôi có thể viết 80% bài kiểm tra trong Rubberduck , ít nhất là không tự đâm vào mắt mình mỗi khi tôi nhìn vào chúng.
Mathieu Guindon

@ Mat'sMug Nó có thể thực sự hữu ích cho việc sắp xếp dữ liệu vào các đối tượng không thay đổi. Những loại đối tượng này có xu hướng là túi thuộc tính tức là không thực sự OO. Toàn bộ không gian của vấn đề là một PITA đến nỗi mọi thứ giúp hoàn thành sẽ được sử dụng cho dù họ có tuân theo các thực hành tốt hay không.
JimmyJames

4

Để trả lời tại sao một loại sẽ được kết hợp với một trình xây dựng, cần phải hiểu tại sao mọi người sẽ sử dụng một trình xây dựng ở nơi đầu tiên. Cụ thể: Bloch khuyên bạn nên sử dụng trình xây dựng khi bạn có số lượng lớn các tham số của hàm tạo. Đó không phải là lý do duy nhất để sử dụng trình xây dựng, nhưng tôi đoán đó là lý do phổ biến nhất. Nói tóm lại, trình xây dựng là sự thay thế cho các tham số của hàm tạo đó và thường thì trình xây dựng được khai báo trong cùng một lớp mà nó đang xây dựng. Vì vậy, lớp kèm theo đã có kiến ​​thức về trình xây dựng và chuyển nó làm đối số của hàm tạo không thay đổi điều đó. Đó là lý do tại sao một loại sẽ được kết hợp với trình tạo của nó.

Tại sao có một số lượng lớn các tham số ngụ ý bạn nên sử dụng một trình xây dựng? Vâng, có một số lượng lớn các tham số không phải là hiếm trong thế giới thực, cũng không vi phạm nguyên tắc trách nhiệm duy nhất. Nó cũng hút khi bạn có mười tham số Chuỗi và đang cố gắng nhớ đó là tham số nào và đó có phải là Chuỗi thứ năm hoặc thứ sáu không có giá trị. Trình xây dựng giúp quản lý các tham số dễ dàng hơn và nó cũng có thể thực hiện những điều thú vị khác mà bạn nên đọc sách để tìm hiểu.

Khi nào bạn sử dụng một trình xây dựng không được kết hợp với loại của nó? Nói chung khi các thành phần của một đối tượng không có sẵn ngay lập tức và các đối tượng có các phần đó không cần biết về loại bạn đang cố gắng xây dựng hoặc bạn đang thêm một trình xây dựng cho một lớp mà bạn không có quyền kiểm soát .


Điều lạ lùng, lần duy nhất tôi cần một người xây dựng là khi tôi có một loại không có hình dạng dứt khoát, như MockVbeBuilder này , xây dựng một bản mô phỏng API của IDE; một thử nghiệm có thể chỉ cần một mô-đun mã đơn giản, một thử nghiệm khác có thể cần hai biểu mẫu và mô-đun lớp - IMO , đó là mô hình trình tạo của GoF dành cho. Điều đó nói rằng, tôi có thể bắt đầu sử dụng nó cho một lớp khác với một nhà xây dựng điên rồ ... nhưng khớp nối hoàn toàn không cảm thấy đúng.
Mathieu Guindon

1
@ Mat's Mug Lớp bạn đã trình bày có vẻ đại diện hơn cho mẫu thiết kế Builder (từ Mẫu thiết kế của Gamma và cộng sự), không nhất thiết là lớp xây dựng đơn giản. Cả hai đều xây dựng mọi thứ, nhưng chúng không giống nhau. Ngoài ra, bạn không bao giờ "cần" một người xây dựng, nó chỉ làm cho những thứ khác dễ dàng hơn.
Fat Fat Ned

1

sản phẩm của một mô hình sáng tạo không bao giờ cần biết bất cứ điều gì về những gì tạo ra nó.

Các Personlớp không biết những gì đang tạo ra nó. Nó chỉ có một constructor với một tham số, vậy thì sao? Tên của loại tham số kết thúc bằng ... Builder không thực sự ép buộc bất cứ điều gì. Rốt cuộc nó chỉ là một cái tên.

Trình xây dựng là một đối tượng cấu hình 1 được tôn vinh . Và một đối tượng cấu hình thực sự chỉ là nhóm các thuộc tính lại với nhau. Nếu chúng chỉ thuộc về nhau với mục đích được truyền cho người xây dựng của một lớp, thì hãy là như vậy! Cả origindestinationcác StreetMapví dụ là các loại Point. Có thể truyền tọa độ riêng của từng điểm vào bản đồ không? Chắc chắn, nhưng các thuộc tính của một Pointthuộc về nhau, cộng với bạn có thể có tất cả các loại phương thức giúp chúng xây dựng:

1 Sự khác biệt là trình xây dựng không chỉ là một đối tượng dữ liệu đơn thuần, mà còn cho phép các cuộc gọi setter chuỗi này. Phần Builderlớn quan tâm đến cách xây dựng chính nó.

Bây giờ, hãy thêm một chút mẫu lệnh vào hỗn hợp, bởi vì nếu bạn nhìn kỹ, trình xây dựng thực sự là một lệnh:

  1. Nó gói gọn một hành động: gọi một phương thức của lớp khác
  2. Nó chứa thông tin cần thiết để thực hiện hành động đó: các giá trị cho các tham số của phương thức

Phần đặc biệt cho người xây dựng là:

  1. Phương thức được gọi thực sự là hàm tạo của một lớp. Tốt hơn gọi nó là một mô hình sáng tạo bây giờ.
  2. Giá trị của tham số là chính trình xây dựng

Những gì đang được truyền cho nhà Personxây dựng có thể xây dựng nó, nhưng nó không nhất thiết phải làm. Nó có thể là một đối tượng cấu hình cũ đơn giản hoặc một trình xây dựng.


0

Không, họ hoàn toàn sai và bạn hoàn toàn đúng. Mô hình xây dựng đó chỉ là ngớ ngẩn. Đó không phải là việc của Người mà các đối số của nhà xây dựng xuất phát. Trình xây dựng chỉ là một tiện lợi cho người dùng và không có gì hơn.


4
Cảm ơn ... Tôi chắc chắn câu trả lời này sẽ thu thập nhiều phiếu hơn nếu được mở rộng thêm một chút, có vẻ như câu trả lời chưa hoàn thành =)
Mathieu Guindon

Có, tôi +1, một cách tiếp cận khác với nhà xây dựng cung cấp các biện pháp bảo vệ và bảo đảm tương tự mà không cần khớp nối
matt freake

3
Đối với vấn đề này, hãy xem câu trả lời được chấp nhận , trong đó đề cập đến các trường hợp PersonBuildertrên thực tế là một phần thiết yếu của PersonAPI. Như nó đứng, câu trả lời này không tranh luận trường hợp của nó.
Andres F.
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.