Các phương thức init () có phải là mùi mã không?


20

Có bất kỳ mục đích để khai báo một init()phương thức cho một loại?

Tôi không hỏi liệu chúng ta nên ưu tiên init()hơn một nhà xây dựng hay làm thế nào để tránh khai báoinit() .

Tôi đang hỏi liệu có bất kỳ lý do nào đằng sau việc khai báo một init()phương thức (xem mức độ phổ biến của nó) hoặc nếu đó là một mùi mã và nên tránh.


Thành init()ngữ này khá phổ biến, nhưng tôi chưa thấy lợi ích thực sự nào.

Tôi đang nói về các loại khuyến khích khởi tạo thông qua một phương thức:

class Demo {
    public void init() {
        //...
    }
}

Khi nào thì cái này sẽ được sử dụng trong mã sản xuất?


Tôi cảm thấy nó có thể là một mùi mã vì nó cho thấy hàm tạo không khởi tạo hoàn toàn đối tượng, dẫn đến một đối tượng được tạo một phần. Đối tượng không nên tồn tại nếu trạng thái không được đặt.

Điều này khiến tôi tin rằng nó có thể là một phần của một số loại kỹ thuật được sử dụng để tăng tốc độ sản xuất, theo nghĩa các ứng dụng doanh nghiệp. Đó là lý do hợp lý duy nhất tôi có thể nghĩ đến khi có một thành ngữ như vậy, tôi chỉ không chắc nó sẽ có ích như thế nào nếu như vậy.


1
Để "... thấy mức độ phổ biến của nó ...": nó có phổ biến không? Bạn có thể cho một số ví dụ? Có lẽ bạn đang xử lý một khung yêu cầu tách biệt việc khởi tạo và xây dựng.
ĐẾN TỪ

Là phương thức được tìm thấy trên một lớp cơ sở hoặc một lớp dẫn xuất, hoặc cả hai? (Hoặc: là phương pháp được tìm thấy trên một lớp học mà thuộc về một hệ thống phân cấp thừa kế Liệu các lớp cơ sở gọi là? init()Trên lớp được thừa kế, hoặc ngược lại?) Nếu vậy, nó là một ví dụ để cho lớp cơ sở thực hiện một "hậu constructor "Chỉ có thể được thực thi sau khi lớp dẫn xuất nhất đã hoàn thành xây dựng. Đây là một ví dụ về khởi tạo nhiều pha.
rwong

Nếu bạn không muốn khởi tạo tại thời điểm khởi tạo, sẽ rất hợp lý khi tách hai cái đó ra.
JMᴇᴇ

bạn có thể quan tâm is-a-start-run-or-
exec

Câu trả lời:


39

Vâng, đó là một mùi mã. Một mùi mã không phải là thứ luôn luôn cần phải loại bỏ. Đó là thứ khiến bạn có cái nhìn thứ hai.

Ở đây bạn có một đối tượng ở hai trạng thái cơ bản khác nhau: pre-init và post-init. Những tiểu bang đó có trách nhiệm khác nhau, các phương pháp khác nhau được phép gọi và hành vi khác nhau. Đó thực sự là hai lớp khác nhau.

Nếu bạn thực sự biến chúng thành hai lớp riêng biệt, bạn sẽ loại bỏ hoàn toàn cả một lớp lỗi tiềm ẩn, với chi phí có thể làm cho mô hình của bạn không khớp với "mô hình thế giới thực" một cách chặt chẽ. Bạn thường đặt tên cho một đầu Confighoặc Setuphoặc một cái gì đó như thế.

Vì vậy, lần tới, hãy thử tái cấu trúc các thành ngữ init-init của bạn thành các mô hình hai lớp và xem nó biến ra như thế nào cho bạn.


6
Đề nghị của bạn để thử mô hình hai lớp là tốt. Đề xuất một bước cụ thể để giải quyết mùi mã là hữu ích.
Ivan

1
Đây là câu trả lời tốt nhất cho đến nay. Mặc dù vậy, có một điều khiến tôi bực mình: " Những tiểu bang đó có trách nhiệm khác nhau, các phương thức khác nhau được phép gọi và hành vi khác nhau " - Không tách rời các trách nhiệm này vi phạm SRP. Nếu mục đích của phần mềm là tái tạo một kịch bản trong thế giới thực ở mọi khía cạnh, điều này sẽ có ý nghĩa. Nhưng trong sản xuất, các nhà phát triển chủ yếu viết mã khuyến khích khả năng quản lý dễ dàng, sửa đổi mô hình nếu cần để phù hợp hơn với một môi trường dựa trên phần mềm (tiếp tục bình luận tiếp theo)
Vince Emigh

1
Thế giới thực là một môi trường khác. Các chương trình cố gắng sao chép nó có vẻ như miền cụ thể, vì trong bạn sẽ không / không nên tính đến nó trong hầu hết các dự án. Rất nhiều điều được tán thành khi được chấp nhận khi nói đến các dự án cụ thể của miền, vì vậy tôi đang cố gắng giữ điều này chung chung nhất có thể (chẳng hạn như cách gọi thistrong hàm tạo là mùi mã và có thể dẫn đến mã dễ bị lỗi, và tránh nó được khuyến khích bất kể dự án của bạn thuộc miền nào).
Vince Emigh

14

Nó phụ thuộc.

Một initphương thức là một mùi mã khi không cần thiết phải khởi tạo đối tượng khỏi hàm tạo. Đôi khi có những trường hợp có ý nghĩa để tách các bước này.

Một tìm kiếm nhanh trên Google đã cho tôi ví dụ này . Tôi có thể dễ dàng tưởng tượng ra nhiều trường hợp trong đó mã được thực thi trong khi cấp phát đối tượng (hàm tạo) có thể được tách ra khỏi chính việc khởi tạo. Có thể bạn có một hệ thống được phân cấp và việc phân bổ / xây dựng diễn ra ở cấp X, nhưng chỉ khởi tạo ở cấp Y, vì chỉ Y mới có thể cung cấp các tham số cần thiết. Có thể "init" tốn kém và chỉ được chạy cho một tập hợp con của các đối tượng được phân bổ và việc xác định tập hợp con đó chỉ có thể được thực hiện ở cấp Y. Hoặc bạn muốn ghi đè phương thức "init" (ảo) trong dẫn xuất lớp không thể được thực hiện với một hàm tạo. Có thể cấp X cung cấp cho bạn các đối tượng được phân bổ từ cây thừa kế, nhưng cấp Y không biết về đạo hàm cụ thể, chỉ về giao diện chung (trong đóinit có thể định nghĩa).

Tất nhiên, theo kinh nghiệm của tôi, những trường hợp này chỉ là một tỷ lệ nhỏ của trường hợp tiêu chuẩn trong đó tất cả việc khởi tạo có thể được thực hiện trực tiếp trong hàm tạo và bất cứ khi nào bạn thấy một initphương thức riêng biệt , có thể nên đặt câu hỏi về sự cần thiết của nó.


2
Câu trả lời trong liên kết đó nói rằng nó hữu ích cho việc giảm lượng công việc trong hàm tạo, nhưng nó khuyến khích các đối tượng được tạo một phần. Các nhà xây dựng nhỏ hơn có thể đạt được thông qua phân tách, vì vậy đối với tôi, dường như các câu trả lời tạo ra một vấn đề mới (khả năng quên gọi tất cả các phương thức khởi tạo được yêu cầu, khiến đối tượng dễ bị lỗi) và thuộc loại mùi mã
Vince Emigh

@VinceEmigh: ok, đó là ví dụ đầu tiên tôi có thể tìm thấy ở đây trên chương trình SE, có thể không phải là trường hợp tốt nhất, nhưng có những trường hợp sử dụng hợp pháp cho một initphương pháp riêng biệt . Tuy nhiên, bất cứ khi nào bạn thấy một phương pháp như vậy, hãy thoải mái đặt câu hỏi về sự cần thiết của nó.
Doc Brown

Tôi đang đặt câu hỏi / thách thức mọi trường hợp sử dụng cho nó, vì tôi cảm thấy không có tình huống nào là cần thiết. Đối với tôi, đó là thời điểm tạo đối tượng kém và nên tránh vì đây là một ứng cử viên cho các lỗi có thể tránh được thông qua thiết kế phù hợp. Nếu có một cách sử dụng đúng init()phương pháp, tôi chắc chắn tôi sẽ được lợi từ việc tìm hiểu về mục đích của nó. Xin lỗi vì sự thiếu hiểu biết của tôi, tôi chỉ ngạc nhiên về mức độ khó khăn của việc tìm kiếm sử dụng nó, ngăn tôi xem xét nó là điều nên tránh
Vince Emigh

1
@VinceEmigh: khi bạn không thể nghĩ ra tình huống như vậy, bạn cần phải làm việc theo trí tưởng tượng của mình ;-). Hoặc đọc lại câu trả lời của tôi, đừng giảm nó chỉ để "phân bổ". Hoặc làm việc với nhiều khung từ các nhà cung cấp khác nhau.
Doc Brown

1
@DocBrown Sử dụng trí tưởng tượng thay vì thực hành đã được chứng minh dẫn đến một số mã thú vị, chẳng hạn như khởi tạo cú đúp: thông minh, nhưng không hiệu quả và nên tránh. Tôi biết các nhà cung cấp sử dụng nó, nhưng điều đó không có nghĩa là việc sử dụng là hợp lý. Nếu bạn cảm thấy như vậy, xin vui lòng cho tôi biết tại sao, vì đó là những gì tôi đã cố gắng tìm ra. Bạn dường như đã chết vì nó có MỘT SỐ mục đích có lợi, nhưng có vẻ như bạn đang gặp khó khăn khi đưa ra một ví dụ khuyến khích thiết kế tốt. Tôi biết trong trường hợp nào nó có thể được sử dụng, nhưng nó nên được sử dụng?
Vince Emigh

5

Kinh nghiệm của tôi chia thành hai nhóm:

  1. Mã nơi init () thực sự được yêu cầu. Điều này có thể xảy ra khi siêu lớp hoặc khung ngăn không cho hàm tạo của lớp bạn nhận được tất cả các phụ thuộc của nó trong quá trình xây dựng.
  2. Mã nơi init () được sử dụng nhưng có thể tránh được.

Theo kinh nghiệm cá nhân của tôi, tôi chỉ thấy một vài trường hợp của (1) nhưng nhiều trường hợp khác của (2). Do đó, tôi thường cho rằng init () là mùi mã, nhưng điều này không phải lúc nào cũng đúng. Đôi khi bạn không thể đi xung quanh nó.

Tôi đã tìm thấy bằng cách sử dụng Mẫu xây dựng thường có thể giúp loại bỏ nhu cầu / mong muốn có init ().


1
Nếu một siêu lớp hoặc khung không cho phép một kiểu đạt được các phụ thuộc mà nó cần thông qua hàm tạo, thì việc thêm một init()phương thức sẽ giải quyết nó như thế nào? Các init()phương pháp hoặc là sẽ cần các thông số để chấp nhận phụ thuộc, hoặc bạn sẽ phải nhanh chóng phụ thuộc trong các init()phương pháp, mà bạn cũng có thể làm với một constructor. Bạn có thể cho một ví dụ?
Vince Emigh

1
@VinceEmigh: init () đôi khi có thể được sử dụng để tải tệp cấu hình từ nguồn bên ngoài, mở kết nối cơ sở dữ liệu hoặc một cái gì đó của ilk đó. Phương thức DoFn.initialize () (từ khung Crunch apache) được sử dụng theo cách này. Nó cũng có thể được sử dụng để tải lên các trường nội bộ không tuần tự hóa (DoFns phải được tuần tự hóa). Hai vấn đề ở đây là (1) một cái gì đó cần đảm bảo phương thức khởi tạo được gọi và (2) đối tượng cần biết nó sẽ lấy ở đâu (hoặc cách nó sẽ xây dựng) các tài nguyên đó.
Ivan

1

Một kịch bản điển hình khi phương thức init có ích là khi bạn có tệp cấu hình mà bạn muốn thay đổi và phải tính đến thay đổi mà không cần khởi động lại ứng dụng. Tất nhiên, điều này không có nghĩa là một phương thức init phải được gọi riêng với hàm tạo. Bạn có thể gọi một phương thức init từ một hàm tạo, rồi gọi nó sau khi / nếu các tham số cấu hình thay đổi.

Tóm lại: như đối với hầu hết các tình huống khó xử ngoài kia, cho dù đây có phải là mùi mã hay không, tùy thuộc vào tình huống và hoàn cảnh.


Nếu các bản cập nhật cấu hình, và điều này đòi hỏi phải có đối tượng để thiết lập lại / thay đổi trạng thái của nó dựa trên cấu hình, bạn không nghĩ rằng nó sẽ tốt hơn nếu có hành động đối tượng như một người quan sát về phía Config?
Vince Emigh

@Vince Emigh Không phải necesarilly. Người quan sát sẽ làm việc nếu tôi biết thời điểm chính xác khi cấu hình thay đổi. Tuy nhiên, nếu dữ liệu cấu hình được lưu trong một tệp có thể thay đổi bên ngoài ứng dụng, thì không có cách tiếp cận thực sự nào. Chẳng hạn, nếu tôi có một chương trình phân tích một số tệp và chuyển đổi dữ liệu thành một mô hình bên trong và một tệp cấu hình riêng biệt chứa các giá trị mặc định cho dữ liệu bị thiếu, nếu tôi thay đổi các giá trị mặc định, tôi sẽ đọc lại chúng khi tôi chạy lần sau phân tích cú pháp. Có một phương thức init trong ứng dụng của tôi sẽ khá tiện lợi trong trường hợp đó.
Vladimir Stokic

Nếu tệp cấu hình được sửa đổi bên ngoài khi chạy, không có cách nào để tải lại các biến đó mà không có một loại thông báo nào đó thông báo cho ứng dụng của bạn rằng nó cần gọi init ( update/ reloadcó thể mô tả nhiều hơn cho loại hành vi này) để thực sự đăng ký những thay đổi đó . Trong trường hợp đó, thông báo đó sẽ khiến giá trị của cấu hình thay đổi trong ứng dụng của bạn, điều mà tôi tin rằng có thể quan sát được bằng cách cấu hình của bạn có thể quan sát được, thông báo cho người quan sát khi cấu hình được yêu cầu thay đổi một trong các giá trị của nó. Hay tôi đang hiểu sai ví dụ của bạn?
Vince Emigh

Một ứng dụng lấy một số tệp bên ngoài (được xuất từ ​​một số hệ thống GIS hoặc một số hệ thống khác), phân tích các tệp đó và chuyển đổi chúng thành một số mô hình nội bộ mà các hệ thống mà ứng dụng là một phần của việc sử dụng. Trong quá trình đó, một số khoảng trống dữ liệu có thể được tìm thấy và những khoảng trống dữ liệu đó có thể được lấp đầy bằng cách mặc định các giá trị. Các giá trị mặc định đó có thể được lưu trữ trong tệp cấu hình có thể được đọc khi bắt đầu quá trình chuyển đổi, được gọi bất cứ khi nào có tệp mới để chuyển đổi. Đó là nơi mà một phương thức init có thể có ích.
Vladimir Stokic

1

Phụ thuộc vào cách bạn sử dụng chúng.

Tôi sử dụng mô hình đó trong các ngôn ngữ được thu thập rác như Java / C # khi tôi không muốn tiếp tục phân bổ lại một đối tượng trên đống (như khi tôi tạo trò chơi video và cần duy trì hiệu suất cao, trình thu gom rác giết chết hiệu suất). Tôi sử dụng hàm tạo để thực hiện các phân bổ heap khác mà nó cần và initđể tạo trạng thái hữu ích cơ bản ngay trước mỗi lần tôi muốn sử dụng lại nó. Điều này có liên quan đến khái niệm nhóm đối tượng.

Nó cũng hữu ích nếu bạn có một số hàm tạo chia sẻ một tập hợp con chung của các hướng dẫn khởi tạo, nhưng trong trường hợp đó initsẽ là riêng tư. Bằng cách đó tôi có thể giảm thiểu mỗi hàm tạo càng nhiều càng tốt, vì vậy mỗi hàm chỉ chứa các hướng dẫn duy nhất của nó và một lệnh gọi initđể thực hiện phần còn lại.

Nói chung, mặc dù nó là một mùi mã.


Sẽ không phải là một reset()phương pháp mô tả nhiều hơn cho tuyên bố đầu tiên của bạn? Đối với thứ hai (nhiều nhà xây dựng), có nhiều nhà xây dựng là một mùi mã. Nó giả định đối tượng có nhiều mục đích / trách nhiệm, cho thấy vi phạm SRP. Một đối tượng nên có một trách nhiệm và nhà xây dựng nên xác định các phụ thuộc cần thiết cho một trách nhiệm đó. Nếu bạn có nhiều nhà xây dựng do các giá trị tùy chọn, họ nên sử dụng kính thiên văn (cũng là mùi mã, nên sử dụng công cụ xây dựng thay thế).
Vince Emigh

@VinceEmigh bạn có thể sử dụng init hoặc reset, nó chỉ là một cái tên. Ban đầu có ý nghĩa hơn trong bối cảnh tôi có xu hướng sử dụng nó vì nó rất ít có ý nghĩa để thiết lập lại một đối tượng không bao giờ được đặt lần đầu tiên tôi sử dụng nó. Đối với vấn đề nhà xây dựng, tôi cố gắng tránh có nhiều nhà xây dựng, nhưng đôi khi nó rất hữu ích. Nhìn vào stringdanh sách nhà xây dựng của ngôn ngữ , hàng tấn tùy chọn. Đối với tôi thường có thể là tối đa 3 hàm tạo, nhưng tập hợp con các hướng dẫn phổ biến khi khởi tạo có ý nghĩa khi chúng chia sẻ bất kỳ mã nào nhưng khác nhau theo bất kỳ cách nào.
Cody

Tên là vô cùng quan trọng. Họ mô tả hành vi đang được thực hiện mà không buộc khách hàng đọc hợp đồng. Đặt tên xấu có thể dẫn đến các giả định sai. Đối với nhiều constructor trong String, điều này có thể được giải quyết bằng cách tách rời việc tạo chuỗi. Cuối cùng, a Stringlà một String, và nhà xây dựng của nó chỉ nên chấp nhận những gì cần thiết để nó thực hiện khi cần thiết. Hầu hết các nhà xây dựng được tiếp xúc cho các mục đích chuyển đổi, đó là lạm dụng các nhà xây dựng. Các nhà xây dựng không nên thực hiện logic, hoặc họ có nguy cơ khởi tạo thất bại, để lại cho bạn một đối tượng vô dụng.
Vince Emigh

JDK chứa đầy những thiết kế khủng khiếp, tôi có thể liệt kê khoảng 10 cái trên đỉnh đầu. Thiết kế phần mềm đã phát triển kể từ khi các khía cạnh cốt lõi của nhiều ngôn ngữ được công khai và chúng tồn tại do khả năng phá vỡ mã nếu nó được thiết kế lại cho thời hiện đại.
Vince Emigh

1

init()các phương thức có thể có ý nghĩa khá lớn khi bạn có các đối tượng cần tài nguyên bên ngoài (ví dụ như kết nối mạng) được các đối tượng khác sử dụng đồng thời. Bạn có thể không muốn / cần hog tài nguyên cho vòng đời của đối tượng. Trong các tình huống như vậy, bạn có thể không muốn phân bổ tài nguyên trong hàm tạo khi phân bổ tài nguyên có khả năng thất bại.

Đặc biệt trong lập trình nhúng, bạn muốn có một dấu chân bộ nhớ xác định, do đó, thông thường (tốt?) Để gọi các nhà xây dựng của bạn sớm, thậm chí có thể tĩnh và chỉ khởi tạo sau khi đáp ứng một số điều kiện nhất định.

Khác với những trường hợp như vậy tôi nghĩ mọi thứ nên đi vào một nhà xây dựng.


Nếu loại yêu cầu kết nối, bạn nên sử dụng DI. Nếu có sự cố khi tạo kết nối, bạn không nên tạo đối tượng yêu cầu kết nối đó. Nếu bạn đẩy việc tạo kết nối bên trong lớp, bạn sẽ tạo một đối tượng, đối tượng đó sẽ khởi tạo các phụ thuộc của nó (kết nối). Nếu việc khởi tạo các phụ thuộc không thành công, bạn kết thúc với một đối tượng mà bạn không thể sử dụng, do đó sẽ lãng phí tài nguyên.
Vince Emigh

Không cần thiết. Bạn kết thúc với một đối tượng mà bạn tạm thời không thể sử dụng cho tất cả các mục đích. Trong các trường hợp như vậy, đối tượng chỉ có thể hoạt động như một hàng đợi hoặc proxy cho đến khi tài nguyên có sẵn. Các initphương pháp hoàn toàn lên án là quá hạn chế, IMHO.
tofro

0

Nói chung, tôi thích một hàm tạo nhận tất cả các đối số cần thiết cho một thể hiện chức năng. Điều đó làm rõ tất cả các phụ thuộc của đối tượng đó.

Mặt khác, tôi sử dụng một khung cấu hình đơn giản đòi hỏi một hàm tạo công khai không tham số và các giao diện để thêm các giá trị phụ thuộc và cấu hình. Sau khi hoàn thành, khung cấu hình gọi initphương thức của đối tượng: bây giờ bạn đã nhận được tất cả những thứ tôi có cho bạn, thực hiện các bước cuối cùng để sẵn sàng cho công việc. Nhưng lưu ý: đó là khung cấu hình tự động gọi phương thức init, vì vậy bạn sẽ không quên gọi nó.


0

Không có mùi mã nếu phương thức init () - được nhúng vào ngữ nghĩa trong vòng đời trạng thái của đối tượng.

Nếu bạn cần gọi init () để đặt đối tượng vào trạng thái nhất quán thì đó là mùi mã.

Có một số lý do kỹ thuật mà cấu trúc như vậy tồn tại:

  1. khung móc
  2. Đặt lại đối tượng về trạng thái ban đầu (tránh dư thừa)
  3. khả năng ghi đè trong các bài kiểm tra

1
Nhưng tại sao một kỹ sư phần mềm lại nhúng nó vào vòng đời? Có mục đích nào hợp lý (xem xét nó khuyến khích các đối tượng được xây dựng một phần) và không thể bị bắn hạ bởi một giải pháp thay thế hiệu quả hơn? Tôi cảm thấy việc nhúng nó vào vòng đời sẽ là một mùi mã và nó nên được thay thế bằng thời gian tạo đối tượng tốt hơn (Tại sao lại phân bổ bộ nhớ cho một đối tượng nếu bạn không có kế hoạch sử dụng nó vào thời điểm đó? đối tượng được xây dựng một phần khi bạn có thể đợi đến khi bạn thực sự cần đối tượng trước khi tạo ra nó?)
Vince Emigh

Vấn đề là đối tượng phải có thể sử dụng được TRƯỚC KHI bạn gọi phương thức init. Có thể theo một cách khác hơn là sau khi nó được gọi. Xem mô hình nhà nước. Theo nghĩa xây dựng một phần đối tượng, nó là một mùi mã.
oopexpert

-4

Tên init đôi khi có thể mờ đục. Hãy lấy một chiếc xe hơi và một động cơ. Để khởi động xe (chỉ bật nguồn, nghe radio), bạn muốn xác minh rằng tất cả các hệ thống đã sẵn sàng để đi.

Vì vậy, bạn xây dựng một động cơ, một cánh cửa, một bánh xe, vv màn hình của bạn hiển thị động cơ = tắt.

Không cần phải bắt đầu theo dõi động cơ, vv vì tất cả đều đắt tiền. Sau đó, khi bạn bật chìa khóa để kích hoạt, bạn gọi công cụ-> bắt đầu. Nó bắt đầu chạy tất cả các quy trình đắt tiền.

Bây giờ bạn thấy engine = on. Và quá trình đánh lửa bắt đầu.

Chiếc xe sẽ không tăng sức mạnh nếu không có động cơ.

Bạn có thể thay thế động cơ với tính toán phức tạp của bạn. Giống như một ô Excel. Không phải tất cả các ô cần phải hoạt động với tất cả các trình xử lý sự kiện mọi lúc. Khi bạn tập trung vào một tế bào, bạn có thể bắt đầu nó. Tăng hiệu suất theo cách đó.

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.