một nhà xây dựng nên phức tạp như thế nào


18

Tôi đang thảo luận với đồng nghiệp về việc một nhà xây dựng có thể làm được bao nhiêu công việc. Tôi có một lớp, B mà bên trong yêu cầu một đối tượng khác A. Đối tượng A là một trong số ít thành viên mà lớp B cần để thực hiện công việc của mình. Tất cả các phương thức công khai của nó phụ thuộc vào đối tượng bên trong A. Thông tin về đối tượng A được lưu trữ trong DB vì vậy tôi cố gắng xác thực và lấy nó bằng cách tìm kiếm nó trên DB trong hàm tạo. Đồng nghiệp của tôi đã chỉ ra rằng hàm tạo không nên thực hiện nhiều công việc khác ngoài việc nắm bắt các tham số của hàm tạo. Vì tất cả các phương thức công khai sẽ thất bại nếu không tìm thấy đối tượng A bằng cách sử dụng các đầu vào cho hàm tạo, tôi lập luận rằng thay vì cho phép một cá thể được tạo và sau đó thất bại, tốt hơn là nên ném sớm vào hàm tạo.

Người khác nghĩ gì? Tôi đang sử dụng C # nếu điều đó làm cho bất kỳ sự khác biệt.

Đọc Có bao giờ có lý do để thực hiện tất cả công việc của một đối tượng trong một hàm tạo không? Tôi tự hỏi tìm nạp đối tượng A bằng cách truy cập DB là một phần của "bất kỳ khởi tạo nào khác cần thiết để làm cho đối tượng sẵn sàng sử dụng" bởi vì nếu người dùng chuyển sai giá trị cho hàm tạo, tôi sẽ không thể sử dụng bất kỳ phương thức công khai nào của nó.

Các nhà xây dựng nên khởi tạo các trường của một đối tượng và thực hiện bất kỳ khởi tạo nào khác cần thiết để làm cho đối tượng sẵn sàng sử dụng. Điều này thường có nghĩa là các nhà xây dựng là nhỏ, nhưng có những kịch bản trong đó đây sẽ là một khối lượng công việc đáng kể.


8
Tôi không đồng ý. Hãy xem nguyên tắc đảo ngược phụ thuộc, sẽ tốt hơn nếu đối tượng A được trao cho đối tượng B ở trạng thái hợp lệ trong hàm tạo của nó.
Jimmy Hoffa

5
Bạn đang hỏi thể làm được bao nhiêu? Bao nhiêu nên được thực hiện? Hoặc bao nhiêu tình huống cụ thể của bạn nên được thực hiện? Đó là ba câu hỏi rất khác nhau.
Bobson

1
Tôi đồng ý với đề nghị của Jimmy Hoffa để xem xét tiêm phụ thuộc (hoặc "đảo ngược phụ thuộc"). Đó là suy nghĩ đầu tiên của tôi khi đọc mô tả, bởi vì có vẻ như bạn đã đi được nửa đường rồi; bạn đã có chức năng tách ra thành lớp A, lớp B đang sử dụng. Tôi không thể nói với trường hợp của bạn, nhưng tôi thường thấy rằng mã tái cấu trúc để sử dụng mẫu tiêm phụ thuộc làm cho các lớp đơn giản hơn / ít đan xen hơn.
Học viên của Tiến sĩ Wily

1
Bobson, tôi đã thay đổi tiêu đề thành 'nên'. Tôi đang yêu cầu thực hành tốt nhất ở đây.
Taein Kim

1
@JimmyHoffa: có lẽ bạn nên mở rộng nhận xét của mình thành câu trả lời.
kevin cline

Câu trả lời:


22

Câu hỏi của bạn bao gồm hai phần hoàn toàn riêng biệt:

Tôi nên ném ngoại lệ từ constructor, hay tôi nên có phương thức thất bại?

Đây rõ ràng là áp dụng nguyên tắc Fail-fast . Làm cho hàm tạo không thành công dễ gỡ lỗi hơn nhiều so với việc phải tìm ra lý do tại sao phương thức bị lỗi. Ví dụ, bạn có thể lấy ví dụ đã được tạo từ một số phần khác của mã và bạn gặp lỗi khi gọi phương thức. Rõ ràng là đối tượng được tạo ra sai? Không.

Đối với vấn đề "bọc cuộc gọi trong thử / bắt". Các ngoại lệ có nghĩa là đặc biệt . Nếu bạn biết một số mã sẽ đưa ra một ngoại lệ, bạn không bọc mã trong thử / bắt, nhưng bạn xác thực các tham số của mã đó trước khi bạn thực thi mã có thể ném ngoại lệ. Các ngoại lệ chỉ có nghĩa là cách để đảm bảo hệ thống không rơi vào trạng thái không hợp lệ. Nếu bạn biết một số tham số đầu vào có thể dẫn đến trạng thái không hợp lệ, bạn chắc chắn rằng các tham số đó không bao giờ xảy ra. Theo cách này, bạn chỉ phải thực hiện thử / bắt ở những nơi mà bạn có thể xử lý ngoại lệ một cách hợp lý, thường nằm trên ranh giới của hệ thống.

Tôi có thể truy cập "các phần khác của hệ thống, như DB" từ hàm tạo không.

Tôi nghĩ rằng điều này đi ngược lại với nguyên tắc ít ngạc nhiên nhất . Không nhiều người mong đợi constructor truy cập DB. Vì vậy, không, bạn không nên làm điều đó.


2
Giải pháp thích hợp hơn cho vấn đề này thường là thêm phương thức kiểu "mở" trong đó việc xây dựng là miễn phí, nhưng không thể sử dụng trước khi bạn "mở" / "kết nối" / etc, đó là nơi xảy ra tất cả các khởi tạo thực tế. Các nhà xây dựng thất bại có thể gây ra tất cả các loại vấn đề khi bạn trong tương lai muốn thực hiện xây dựng một phần các đối tượng là một khả năng hữu ích.
Jimmy Hoffa

@JimmyHoffa Tôi thấy không có sự khác biệt giữa phương thức xây dựng và phương pháp cụ thể để xây dựng.
Euphoric

4
Bạn có thể không, nhưng nhiều người làm. Hãy suy nghĩ về khi bạn tạo một đối tượng kết nối, hàm tạo có kết nối nó không? Là đối tượng có thể sử dụng nếu nó không được kết nối? Trong cả hai câu hỏi, câu trả lời là không, bởi vì thật hữu ích khi có các đối tượng kết nối được xây dựng một phần để bạn có thể điều chỉnh cài đặt và mọi thứ trước khi mở kết nối. Đây là một cách tiếp cận phổ biến cho kịch bản này. Tôi vẫn nghĩ rằng tiêm constructor là cách tiếp cận phù hợp cho các tình huống trong đó đối tượng A có sự phụ thuộc tài nguyên nhưng một phương thức mở vẫn tốt hơn so với việc nhà xây dựng thực hiện công việc nguy hiểm.
Jimmy Hoffa

@JimmyHoffa Nhưng đó là yêu cầu cụ thể của lớp kết nối, không phải là quy tắc chung của thiết kế OOP. Tôi thực sự sẽ gọi nó là một trường hợp đặc biệt, hơn là một quy tắc. Về cơ bản bạn đang nói về mô hình xây dựng.
Euphoric

2
Đó không phải là một yêu cầu, đó là một quyết định thiết kế. Họ có thể đã làm cho hàm tạo lấy một chuỗi kết nối và kết nối ngay lập tức khi được xây dựng. Họ quyết định không quá vì nó hữu ích để có thể tạo ra đối tượng, sau đó tinh chỉnh tìm ra chuỗi kết nối hoặc các bit và bobble khác và kết nối nó sau ,
Jimmy Hoffa

19

Một nhà xây dựng nên làm ít nhất có thể - vấn đề là hành vi ngoại lệ đó trong các nhà xây dựng là khó xử. Rất ít lập trình viên biết hành vi phù hợp là gì (bao gồm các tình huống kế thừa) và buộc người dùng của bạn phải thử / nắm bắt mọi hoạt động tức thời là ... đau đớn nhất.

Có hai phần này, trong hàm tạo và sử dụng hàm tạo. Thật khó xử trong hàm tạo vì bạn không thể làm gì nhiều khi có ngoại lệ xảy ra. Bạn không thể trả lại một loại khác. Bạn không thể trả về null. Về cơ bản, bạn có thể điều chỉnh lại ngoại lệ, trả về một đối tượng bị hỏng (bộ dẫn xấu) hoặc (trường hợp tốt nhất) thay thế một số phần bên trong bằng một mặc định lành mạnh. Sử dụng hàm tạo rất khó xử vì sau đó các phần khởi tạo của bạn (và phần khởi tạo của tất cả các loại dẫn xuất!) Có thể ném. Sau đó, bạn không thể sử dụng chúng trong khởi tạo thành viên. Cuối cùng, bạn sử dụng các ngoại lệ cho logic để xem "sáng tạo của tôi có thành công không?", Vượt quá khả năng đọc (và mong manh) do thử / bắt ở mọi nơi.

Nếu nhà xây dựng của bạn đang làm những việc không tầm thường hoặc những điều có thể dự kiến ​​hợp lý sẽ thất bại, hãy xem xét một Createphương thức tĩnh (với một nhà xây dựng không công khai) thay vào đó. Nó dễ sử dụng hơn, dễ gỡ lỗi hơn và vẫn giúp bạn có được một bản dựng hoàn chỉnh khi thành công.


2
Các kịch bản xây dựng phức tạp chính xác là lý do tại sao các mô hình sáng tạo như bạn đề cập ở đây tồn tại. Đây là một cách tiếp cận tốt cho các công trình có vấn đề. Tôi được cho là nghĩ về cách .NET trong một Connectionđối tượng có thể CreateCommandgiúp bạn để bạn biết lệnh được kết nối phù hợp.
Jimmy Hoffa

2
Về vấn đề này, tôi sẽ nói thêm rằng Cơ sở dữ liệu là một sự phụ thuộc bên ngoài nặng nề, giả sử bạn muốn chuyển đổi cơ sở dữ liệu tại một thời điểm nào đó trong tương lai (hoặc giả định một đối tượng để thử nghiệm), chuyển loại mã này sang lớp Factory (hoặc một cái gì đó giống như IService nhà cung cấp) có vẻ như là một cách tiếp cận sạch hơn
Jason Sperske

4
bạn có thể giải thích "hành vi ngoại lệ trong các nhà xây dựng là khó xử" không? Nếu bạn đã sử dụng Tạo, bạn sẽ không cần phải bọc nó trong thử bắt giống như bạn sẽ thực hiện cuộc gọi đến nhà xây dựng? Tôi cảm thấy như Tạo chỉ là tên khác nhau cho nhà xây dựng. Có lẽ tôi không nhận được câu trả lời của bạn phải không?
Taein Kim

1
Phải cố gắng bắt các ngoại lệ nổi lên từ các nhà xây dựng, +1 cho các đối số chống lại điều này. Mặc dù đã làm việc với những người nói rằng "Không có công việc trong hàm tạo", điều này cũng có thể tạo ra một số mẫu lạ. Tôi đã thấy mã trong đó mọi đối tượng không chỉ có một hàm tạo, mà còn có một số dạng của Khởi tạo () ở đâu, đoán xem? Đối tượng không thực sự hợp lệ cho đến khi được gọi là một trong hai. Và sau đó, bạn có hai thứ để gọi: ctor và Khởi tạo (). Vấn đề thực hiện công việc để thiết lập một đối tượng không biến mất - C # có thể mang lại các giải pháp khác nhau so với C ++. Các nhà máy là tốt!
J Trana

1
@jtrana - yeah, khởi tạo là không tốt. Hàm tạo khởi tạo một số mặc định lành mạnh hoặc một nhà máy hoặc phương thức tạo xây dựng nó và trả về một thể hiện có thể sử dụng được.
Telastyn

1

Con Contor - cân bằng độ phức tạp của phương thức thực sự là một vấn đề thảo luận về stile tốt. Khá thường xây dựng rỗng Class() {}được sử dụng, với một phương pháp Init(..) {..}cho những thứ phức tạp hơn. Trong số các đối số sẽ được đưa vào tài khoản:

  • Khả năng tuần tự hóa lớp
  • Với phương thức init ban đầu từ hàm tạo, bạn thường cần lặp lại cùng một chuỗi Class x = new Class(); x.Init(..);nhiều lần, một phần trong Bài kiểm tra đơn vị. Không tốt, tuy nhiên, rõ ràng để đọc. Đôi khi, tôi chỉ cần đặt chúng vào một dòng mã.
  • Khi mục đích Kiểm thử đơn vị chỉ là kiểm tra khởi tạo Lớp, tốt hơn là có Ban đầu, để thực hiện các hành động phức tạp hơn từng bước một, với các Xác nhận trung gian.

Theo tôi, ưu điểm của initphương pháp là việc xử lý thất bại dễ dàng hơn rất nhiều. Cụ thể, giá trị trả về của initphương thức có thể cho biết việc khởi tạo có thành công hay không và phương thức có thể đảm bảo đối tượng luôn ở trạng thái tốt.
pqnet
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.