Thực hành tốt hay xấu? Đang khởi tạo các đối tượng trong getter


167

Tôi có một thói quen kỳ lạ dường như ... ít nhất là theo đồng nghiệp của tôi. Chúng tôi đã cùng nhau thực hiện một dự án nhỏ. Cách tôi viết các lớp là (ví dụ đơn giản hóa):

[Serializable()]
public class Foo
{
    public Foo()
    { }

    private Bar _bar;

    public Bar Bar
    {
        get
        {
            if (_bar == null)
                _bar = new Bar();

            return _bar;
        }
        set { _bar = value; }
    }
}

Vì vậy, về cơ bản, tôi chỉ khởi tạo bất kỳ trường nào khi một getter được gọi và trường vẫn là null. Tôi hình dung điều này sẽ giảm quá tải bằng cách không khởi tạo bất kỳ thuộc tính nào không được sử dụng ở bất cứ đâu.

ETA: Lý do tôi đã làm điều này là vì lớp của tôi có một số thuộc tính trả về một thể hiện của một lớp khác, do đó cũng có các thuộc tính với nhiều lớp hơn, v.v. Gọi hàm tạo cho lớp trên cùng sau đó sẽ gọi tất cả các hàm tạo cho tất cả các lớp này, khi chúng không phải lúc nào cũng cần thiết.

Có bất kỳ phản đối chống lại thực hành này, ngoài sở thích cá nhân?

CẬP NHẬT: Tôi đã xem xét nhiều ý kiến ​​khác nhau liên quan đến câu hỏi này và tôi sẽ đứng trước câu trả lời được chấp nhận của tôi. Tuy nhiên, bây giờ tôi đã hiểu rõ hơn về khái niệm này và tôi có thể quyết định khi nào nên sử dụng nó và khi nào thì không.

Nhược điểm:

  • Vấn đề an toàn chủ đề
  • Không tuân theo yêu cầu "setter" khi giá trị được truyền là null
  • Tối ưu hóa vi mô
  • Xử lý ngoại lệ nên diễn ra trong một nhà xây dựng
  • Cần kiểm tra null trong mã của lớp

Ưu điểm:

  • Tối ưu hóa vi mô
  • Thuộc tính không bao giờ trả về null
  • Trì hoãn hoặc tránh tải các đối tượng "nặng"

Hầu hết các nhược điểm không thể áp dụng cho thư viện hiện tại của tôi, tuy nhiên tôi sẽ phải kiểm tra xem liệu "tối ưu hóa vi mô" có thực sự tối ưu hóa bất cứ điều gì không.

CẬP NHẬT CUỐI CÙNG:

Được rồi, tôi đã thay đổi câu trả lời của tôi. Câu hỏi ban đầu của tôi là liệu đây có phải là một thói quen tốt hay không. Và bây giờ tôi tin chắc rằng không phải vậy. Có thể tôi vẫn sẽ sử dụng nó trong một số phần của mã hiện tại của mình, nhưng không phải vô điều kiện và chắc chắn không phải lúc nào cũng vậy. Vì vậy, tôi sẽ mất thói quen và suy nghĩ về nó trước khi sử dụng nó. Cảm ơn mọi người!


14
Đây là mô hình tải lười biếng, nó không chính xác mang lại cho bạn một lợi ích tuyệt vời ở đây nhưng nó vẫn là một điều tốt.
Machinarius

28
Khởi tạo lười biếng có ý nghĩa nếu bạn có tác động hiệu suất có thể đo lường được, hoặc nếu các thành viên đó hiếm khi được sử dụng và tiêu tốn một lượng bộ nhớ cắt cổ, hoặc nếu phải mất một thời gian dài để khởi tạo chúng và chỉ muốn thực hiện theo yêu cầu. Ở bất kỳ giá nào, hãy đảm bảo tính đến các vấn đề an toàn của luồng (mã hiện tại của bạn không ) và xem xét sử dụng lớp Lazy <T> được cung cấp.
Chris Sinclair

10
Tôi nghĩ rằng câu hỏi này phù hợp hơn trên codereview.stackexchange.com
Eduardo Brites

7
@PLB nó không phải là một mẫu đơn.
Colin Mackay

30
Tôi ngạc nhiên rằng không ai đã đề cập đến một lỗi nghiêm trọng với mã này. Bạn có một tài sản công cộng mà tôi có thể đặt nó từ bên ngoài. Nếu tôi đặt nó thành NULL, bạn sẽ luôn tạo một đối tượng mới và bỏ qua quyền truy cập setter của tôi. Đây có thể là một lỗi rất nghiêm trọng. Đối với tài sản riêng, điều này có thể là okie. Cá nhân, tôi không muốn thực hiện tối ưu hóa sớm như vậy. Thêm sự phức tạp cho không có lợi ích bổ sung.
SolutionYogi

Câu trả lời:


170

Những gì bạn có ở đây là một - thực hiện - ngây thơ - thực hiện "khởi tạo lười biếng".

Câu trả lời ngắn:

Sử dụng khởi tạo lười biếng vô điều kiện không phải là một ý tưởng tốt. Nó có vị trí của nó nhưng người ta phải xem xét các tác động mà giải pháp này có.

Bối cảnh và giải thích:

Triển khai cụ thể:
Trước tiên chúng ta hãy xem mẫu cụ thể của bạn và lý do tại sao tôi coi việc triển khai của nó là ngây thơ:

  1. Nó vi phạm Nguyên tắc bất ngờ tối thiểu (POLS) . Khi một giá trị được gán cho một thuộc tính, dự kiến ​​giá trị này sẽ được trả về. Trong triển khai của bạn, đây không phải là trường hợp cho null:

    foo.Bar = null;
    Assert.Null(foo.Bar); // This will fail
  2. Nó giới thiệu khá nhiều vấn đề về luồng: Hai người gọi foo.Bartrên các luồng khác nhau có thể có khả năng nhận được hai trường hợp khác nhau Barvà một trong số họ sẽ không có kết nối với Foothể hiện. Mọi thay đổi được thực hiện cho Bartrường hợp đó đều âm thầm bị mất.
    Đây là một trường hợp vi phạm POLS. Khi chỉ có giá trị được lưu trữ của một thuộc tính được truy cập, nó được dự kiến ​​là an toàn luồng. Mặc dù bạn có thể lập luận rằng lớp đơn giản là không an toàn cho chuỗi - bao gồm cả người nhận tài sản của bạn - bạn sẽ phải ghi lại điều này đúng vì đó không phải là trường hợp bình thường. Hơn nữa, việc giới thiệu vấn đề này là không cần thiết vì chúng ta sẽ thấy sớm.

Nói chung:
Bây giờ đã đến lúc xem xét việc khởi tạo lười biếng nói chung:
Khởi tạo lười biếng thường được sử dụng để trì hoãn việc xây dựng các đối tượng mất nhiều thời gian để xây dựng hoặc mất rất nhiều bộ nhớ khi được xây dựng hoàn chỉnh.
Đó là một lý do rất hợp lệ cho việc sử dụng khởi tạo lười biếng.

Tuy nhiên, các thuộc tính như vậy thường không có setters, loại bỏ vấn đề đầu tiên được chỉ ra ở trên.
Hơn nữa, việc triển khai an toàn luồng sẽ được sử dụng - như Lazy<T>- để tránh vấn đề thứ hai.

Ngay cả khi xem xét hai điểm này trong việc triển khai một tài sản lười biếng, những điểm sau đây là những vấn đề chung của mẫu này:

  1. Việc xây dựng đối tượng có thể không thành công, dẫn đến một ngoại lệ từ một người nhận tài sản. Đây là một vi phạm khác của POLS và do đó nên tránh. Ngay cả phần về các thuộc tính trong "Nguyên tắc thiết kế để phát triển thư viện lớp" cũng tuyên bố rõ ràng rằng các nhân viên thuộc tính không nên ném ngoại lệ:

    Tránh ném ngoại lệ từ getters tài sản.

    Getters tài sản nên được hoạt động đơn giản mà không cần bất kỳ điều kiện tiên quyết. Nếu một getter có thể đưa ra một ngoại lệ, hãy xem xét thiết kế lại tài sản là một phương pháp.

  2. Tối ưu hóa tự động bởi trình biên dịch bị tổn thương, cụ thể là dự đoán nội tuyến và nhánh. Xin vui lòng xem câu trả lời của Bill K để được giải thích chi tiết.

Kết luận của những điểm này là như sau:
Đối với mỗi thuộc tính được triển khai một cách lười biếng, bạn nên xem xét các điểm này.
Điều đó có nghĩa, đó là một quyết định cho từng trường hợp và không thể được coi là một thực tiễn tốt nhất chung.

Mẫu này có vị trí của nó, nhưng nó không phải là một thực tiễn tốt nhất chung khi thực hiện các lớp. Nó không nên được sử dụng vô điều kiện , vì những lý do đã nêu ở trên.


Trong phần này tôi muốn thảo luận về một số điểm mà những người khác đã đưa ra làm đối số cho việc sử dụng khởi tạo lười biếng vô điều kiện:

  1. Tuần tự hóa:
    EricJ nêu trong một bình luận:

    Một đối tượng có thể được tuần tự hóa sẽ không có đối tượng được gọi khi nó được giải tuần tự hóa (phụ thuộc vào trình tuần tự hóa, nhưng nhiều đối tượng phổ biến hành xử như thế này). Đặt mã khởi tạo vào hàm tạo có nghĩa là bạn phải cung cấp hỗ trợ bổ sung cho quá trình khử lưu huỳnh. Mẫu này tránh được mã hóa đặc biệt.

    Có một số vấn đề với lập luận này:

    1. Hầu hết các đối tượng sẽ không bao giờ được nối tiếp. Thêm một số loại hỗ trợ cho nó khi không cần thiết sẽ vi phạm YAGNI .
    2. Khi một lớp cần hỗ trợ tuần tự hóa, có những cách để kích hoạt nó mà không cần một cách giải quyết mà không liên quan gì đến việc tuần tự hóa ngay từ cái nhìn đầu tiên.
  2. Tối ưu hóa vi mô: Đối số chính của bạn là bạn chỉ muốn xây dựng các đối tượng khi ai đó thực sự truy cập chúng. Vì vậy, bạn đang thực sự nói về việc tối ưu hóa việc sử dụng bộ nhớ.
    Tôi không đồng ý với lập luận này vì những lý do sau:

    1. Trong hầu hết các trường hợp, một vài đối tượng trong bộ nhớ không có tác động gì đến bất cứ điều gì. Máy tính hiện đại có đủ bộ nhớ. Không có trường hợp các vấn đề thực tế được xác nhận bởi một hồ sơ, đây là tối ưu hóa trước khi trưởng thành và có những lý do chính đáng để chống lại nó.
    2. Tôi thừa nhận thực tế là đôi khi loại tối ưu hóa này là hợp lý. Nhưng ngay cả trong những trường hợp này, khởi tạo lười biếng dường như không phải là giải pháp chính xác. Có hai lý do để chống lại nó:

      1. Khởi tạo lười biếng có khả năng làm tổn thương hiệu suất. Có thể chỉ là ngoài lề, nhưng như câu trả lời của Bill cho thấy, tác động lớn hơn người ta có thể nghĩ ngay từ cái nhìn đầu tiên. Vì vậy, phương pháp này về cơ bản giao dịch hiệu suất so với bộ nhớ.
      2. Nếu bạn có một thiết kế trong đó là trường hợp sử dụng phổ biến chỉ sử dụng các phần của lớp, thì điều này gợi ý một vấn đề với chính thiết kế: Lớp trong câu hỏi rất có thể có nhiều hơn một trách nhiệm. Giải pháp sẽ là chia lớp thành nhiều lớp tập trung hơn.

4
@JohnWillemse: Đó là một vấn đề về kiến ​​trúc của bạn. Bạn nên cấu trúc lại các lớp của mình theo cách chúng nhỏ hơn và tập trung hơn. Đừng tạo một lớp cho 5 việc / nhiệm vụ khác nhau. Tạo 5 lớp thay thế.
Daniel Hilgarth

26
@JohnWillemse Có lẽ coi đây là một trường hợp tối ưu hóa sớm. Trừ khi bạn có một nút cổ chai hiệu năng / bộ nhớ được đo , tôi khuyên bạn nên chống lại nó vì nó làm tăng độ phức tạp và đưa ra các vấn đề luồng.
Chris Sinclair

2
+1, đây không phải là một lựa chọn thiết kế tốt cho 95% các lớp. Khởi tạo lười biếng có ưu điểm của nó, nhưng không nên khái quát cho TẤT CẢ các thuộc tính. Nó thêm độ phức tạp, khó đọc mã, các vấn đề an toàn của luồng ... để không tối ưu hóa rõ ràng trong 99% trường hợp. Ngoài ra, như SolutionYogi đã nói như một nhận xét, mã của OP là lỗi, điều này chứng tỏ mô hình này không tầm thường để thực hiện và nên tránh nếu không thực sự cần khởi tạo lười biếng.
ken2k

2
@DanielHilgarth Cảm ơn bạn đã tìm mọi cách để viết ra (gần như) mọi thứ sai khi sử dụng mẫu này vô điều kiện. Bạn đã làm rất tốt!
Alex

1
@DanielHilgarth Vâng có và không. Vi phạm là vấn đề ở đây nên có. Nhưng cũng 'không' vì POLS hoàn toàn là nguyên tắc mà có lẽ bạn sẽ không ngạc nhiên về mã này . Nếu Foo không được tiếp xúc bên ngoài chương trình của bạn, đó là rủi ro bạn có thể chấp nhận hoặc không. Trong trường hợp này, tôi gần như có thể đảm bảo rằng cuối cùng bạn sẽ ngạc nhiên , vì bạn không kiểm soát cách các thuộc tính được truy cập. Rủi ro chỉ trở thành một lỗi, và lập luận của bạn về nullvụ việc trở nên mạnh mẽ hơn nhiều. :-)
tùy ý

49

Đó là một sự lựa chọn thiết kế tốt. Rất khuyến khích cho mã thư viện hoặc các lớp cốt lõi.

Nó được gọi bởi một số "khởi tạo lười biếng" hoặc "khởi tạo chậm trễ" và nó thường được coi là một lựa chọn thiết kế tốt.

Đầu tiên, nếu bạn khởi tạo trong khai báo các biến hoặc hàm xây dựng mức lớp, thì khi đối tượng của bạn được xây dựng, bạn có chi phí tạo tài nguyên có thể không bao giờ được sử dụng.

Thứ hai, tài nguyên chỉ được tạo nếu cần.

Thứ ba, bạn tránh rác thu thập một đối tượng không được sử dụng.

Cuối cùng, việc xử lý các ngoại lệ khởi tạo có thể xảy ra trong thuộc tính sẽ dễ dàng hơn sau đó các ngoại lệ xảy ra trong quá trình khởi tạo các biến cấp độ lớp hoặc hàm tạo.

Có những ngoại lệ cho quy tắc này.

Về đối số hiệu suất của kiểm tra bổ sung cho việc khởi tạo trong thuộc tính "get", nó không đáng kể. Khởi tạo và xử lý một đối tượng là một cú đánh hiệu suất quan trọng hơn so với kiểm tra con trỏ null đơn giản với một bước nhảy.

Hướng dẫn thiết kế để phát triển thư viện lớp tại http://msdn.microsoft.com/en-US/l Library / vstudio / ms229042.aspx

Về Lazy<T>

Lớp chung Lazy<T>được tạo chính xác cho những gì người đăng muốn, xem Khởi tạo lười biếng tại http://msdn.microsoft.com/en-us/l Library / dd997286 (v = vs.100) .aspx . Nếu bạn có các phiên bản .NET cũ hơn, bạn phải sử dụng mẫu mã được minh họa trong câu hỏi. Mẫu mã này đã trở nên phổ biến đến mức Microsoft thấy phù hợp để bao gồm một lớp trong các thư viện .NET mới nhất để giúp triển khai mẫu dễ dàng hơn. Ngoài ra, nếu việc triển khai của bạn cần sự an toàn của luồng, thì bạn phải thêm nó.

Kiểu dữ liệu nguyên thủy và các lớp đơn giản

Obvioulsy, bạn sẽ không sử dụng khởi tạo lười biếng cho kiểu dữ liệu nguyên thủy hoặc sử dụng lớp đơn giản như thế nào List<string>.

Trước khi bình luận về Lười

Lazy<T> đã được giới thiệu trong .NET 4.0, vì vậy vui lòng không thêm một nhận xét nào khác về lớp này.

Trước khi bình luận về tối ưu hóa vi mô

Khi bạn đang xây dựng thư viện, bạn phải xem xét tất cả các tối ưu hóa. Ví dụ, trong các lớp .NET, bạn sẽ thấy các mảng bit được sử dụng cho các biến lớp Boolean trong toàn bộ mã để giảm mức tiêu thụ bộ nhớ và phân mảnh bộ nhớ, chỉ để đặt tên cho hai "tối ưu hóa vi mô".

Về giao diện người dùng

Bạn sẽ không sử dụng khởi tạo lười biếng cho các lớp được giao diện người dùng sử dụng trực tiếp. Tuần trước tôi đã dành phần tốt hơn trong một ngày để loại bỏ việc lười biếng tải tám bộ sưu tập được sử dụng trong mô hình chế độ xem cho hộp tổ hợp. Tôi có một LookupManagerxử lý lười tải và lưu trữ các bộ sưu tập cần thiết bởi bất kỳ yếu tố giao diện người dùng nào.

"Setters"

Tôi chưa bao giờ sử dụng thuộc tính set ("setters") cho bất kỳ thuộc tính được tải lười biếng nào. Do đó, bạn sẽ không bao giờ cho phép foo.Bar = null;. Nếu bạn cần thiết lập Barthì tôi sẽ tạo một phương thức được gọi SetBar(Bar value)và không sử dụng khởi tạo lười biếng

Bộ sưu tập

Các thuộc tính bộ sưu tập lớp luôn được khởi tạo khi được khai báo bởi vì chúng không bao giờ là null.

Lớp học phức tạp

Hãy để tôi nhắc lại điều này một cách khác biệt, bạn sử dụng khởi tạo lười biếng cho các lớp phức tạp. Mà thường, các lớp học được thiết kế kém.

Cuối cùng

Tôi không bao giờ nói để làm điều này cho tất cả các lớp hoặc trong mọi trường hợp. Đó là một thói quen xấu.


6
Nếu bạn có thể gọi foo.Bar nhiều lần trong các luồng khác nhau mà không có bất kỳ cài đặt giá trị can thiệp nào nhưng nhận được các giá trị khác nhau, thì bạn có một lớp nghèo tệ hại.
Lie Ryan

25
Tôi nghĩ rằng đây là một quy tắc xấu mà không có nhiều cân nhắc. Trừ khi Bar là một tài nguyên biết, đây là một tối ưu hóa vi mô không cần thiết. Và trong trường hợp Bar sử dụng nhiều tài nguyên, có chủ đề an toàn Lazy <T> được tích hợp vào .net.
Andrew Hanlon

10
"dễ dàng hơn để xử lý các ngoại lệ khởi tạo có thể xảy ra trong thuộc tính sau đó các ngoại lệ xảy ra trong quá trình khởi tạo các biến cấp độ lớp hoặc hàm tạo." - được rồi, điều này thật ngớ ngẩn. Nếu một đối tượng không thể được khởi tạo vì một số lý do, tôi muốn biết càng sớm càng tốt. Tức là ngay lập tức khi nó được xây dựng. Có những lý lẽ tốt được đưa ra cho việc sử dụng khởi tạo lười biếng nhưng tôi không nghĩ nên sử dụng nó một cách phổ biến .
millimoose

20
Tôi thực sự lo lắng rằng các nhà phát triển khác sẽ thấy câu trả lời này và nghĩ rằng đây thực sự là một cách thực hành tốt (oh boy). Đây là một thực tế rất xấu nếu bạn đang sử dụng nó vô điều kiện. Bên cạnh những gì đã được nói, bạn đang làm cho cuộc sống của mọi người trở nên khó khăn hơn rất nhiều (đối với các nhà phát triển khách hàng và cho các nhà phát triển bảo trì) để kiếm được rất ít (nếu có bất kỳ lợi ích nào). Bạn nên nghe từ các chuyên gia: Donald Knuth, trong loạt Nghệ thuật lập trình máy tính, nổi tiếng nói rằng "tối ưu hóa sớm là gốc rễ của mọi tội lỗi." Những gì bạn đang làm không chỉ là xấu xa, là bệnh tiểu đường!
Alex

4
Có rất nhiều chỉ số bạn đang chọn câu trả lời sai (và quyết định lập trình sai). Bạn có nhiều khuyết điểm hơn ưu điểm trong danh sách của bạn. Bạn có nhiều người chống lại nó hơn cho nó. Các thành viên giàu kinh nghiệm hơn của trang web này (@BillK và @DanielHilgarth) đã đăng trong câu hỏi này đều chống lại nó. Đồng nghiệp của bạn đã nói với bạn rằng nó sai. Nghiêm túc mà nói, nó sai rồi! Nếu tôi bắt được một trong những nhà phát triển của nhóm của tôi (tôi là trưởng nhóm) làm việc này, anh ta sẽ mất thời gian chờ 5 phút và sau đó được giảng tại sao anh ta không bao giờ nên làm điều này.
Alex

17

Bạn có xem xét việc thực hiện mô hình như vậy bằng cách sử dụng Lazy<T>?

Ngoài việc dễ dàng tạo các đối tượng tải lười biếng, bạn có được sự an toàn của luồng trong khi đối tượng được khởi tạo:

Như những người khác đã nói, bạn tải các đối tượng một cách lười biếng nếu chúng thực sự nặng về tài nguyên hoặc phải mất một thời gian để tải chúng trong thời gian xây dựng đối tượng.


Cảm ơn, tôi hiểu nó bây giờ và tôi chắc chắn sẽ xem xét Lazy<T>ngay bây giờ, và không sử dụng theo cách tôi luôn làm.
John Willemse

1
Bạn không nhận được ma thuật an toàn ... bạn vẫn phải suy nghĩ về nó. Từ MSDN:Making the Lazy<T> object thread safe does not protect the lazily initialized object. If multiple threads can access the lazily initialized object, you must make its properties and methods safe for multithreaded access.
Eric J.

@EricJ. Tất nhiên rồi. Bạn chỉ nhận được sự an toàn của luồng khi khởi tạo đối tượng, nhưng sau đó bạn cần xử lý đồng bộ hóa như bất kỳ đối tượng nào khác.
Matías Fidemraizer

9

Tôi nghĩ rằng nó phụ thuộc vào những gì bạn đang khởi tạo. Tôi có lẽ sẽ không làm điều đó cho một danh sách vì chi phí xây dựng là khá nhỏ, vì vậy nó có thể đi vào nhà xây dựng. Nhưng nếu đó là một danh sách được điền trước thì có lẽ tôi sẽ không cần đến lần đầu tiên.

Về cơ bản, nếu chi phí xây dựng lớn hơn chi phí thực hiện kiểm tra có điều kiện trên mỗi truy cập thì lười biếng tạo ra nó. Nếu không, làm điều đó trong hàm tạo.


Cảm ơn bạn! Điều đó có ý nghĩa.
John Willemse

9

Nhược điểm mà tôi có thể thấy là nếu bạn muốn hỏi nếu Bars là null, thì nó sẽ không bao giờ và bạn sẽ tạo danh sách ở đó.


Tôi không nghĩ đó là nhược điểm.
Peter Porfy

Tại sao đó là một nhược điểm? Chỉ cần kiểm tra với bất kỳ thay vì chống lại null. if (! Foo.Bars.Any ())
s.meijer

6
@PeterPorfy: Nó vi phạm POLS . Bạn đặt nullvào, nhưng không lấy lại được. Thông thường, bạn cho rằng bạn nhận lại cùng một giá trị mà bạn đã đưa vào một tài sản.
Daniel Hilgarth

@DanielHilgarth Cảm ơn một lần nữa. Đó là một lập luận rất hợp lệ mà tôi chưa từng xem xét trước đây.
John Willemse

6
@AMissico: Nó không phải là một khái niệm tạo nên. Theo cách tương tự như việc ấn một nút bên cạnh cửa trước dự kiến ​​sẽ rung chuông cửa, những thứ trông giống như tài sản được dự kiến ​​sẽ hoạt động giống như tài sản. Mở một cánh cửa bẫy dưới chân bạn là một hành vi đáng ngạc nhiên, đặc biệt là nếu nút không được dán nhãn như vậy.
Bryan Boettcher

8

Khởi tạo / khởi tạo lười biếng là một mô hình hoàn toàn khả thi. Tuy nhiên, xin lưu ý rằng, theo nguyên tắc chung, người tiêu dùng API của bạn không mong đợi các getters và setters mất thời gian rõ ràng từ POV của người dùng cuối (hoặc thất bại).


1
Tôi đồng ý, và tôi đã chỉnh sửa câu hỏi của mình một chút. Tôi hy vọng một chuỗi đầy đủ các hàm tạo bên dưới sẽ mất nhiều thời gian hơn so với khởi tạo các lớp chỉ khi chúng cần thiết.
John Willemse

8

Tôi chỉ định bình luận về câu trả lời của Daniel nhưng thật lòng tôi không nghĩ nó đi đủ xa.

Mặc dù đây là một mẫu rất tốt để sử dụng trong các tình huống nhất định (ví dụ: khi đối tượng được khởi tạo từ cơ sở dữ liệu), đó là thói quen HORRIBLE để tham gia.

Một trong những điều tốt nhất về một đối tượng là nó cung cấp một môi trường an toàn, đáng tin cậy. Trường hợp tốt nhất là nếu bạn tạo ra càng nhiều trường càng tốt "Cuối cùng", điền tất cả chúng vào với hàm tạo. Điều này làm cho lớp của bạn khá chống đạn. Cho phép các trường được thay đổi thông qua setters là ít hơn một chút, nhưng không phải là khủng khiếp. Ví dụ:

lớp SafeClass
{
    Tên chuỗi = "";
    Tuổi nguyên = 0;

    công khai void setName (String newName)
    {
        khẳng định (newName! = null)
        tên = newName;
    } // theo mô hình này theo tuổi
    ...
    chuỗi công khai toString () {
        Chuỗi s = "Lớp an toàn có tên:" + name + "và tuổi:" + tuổi
    }
}

Với mẫu của bạn, phương thức toString sẽ như thế này:

    if (name == null)
        ném IllegalStateException mới ("SafeClass đã vào trạng thái bất hợp pháp! tên là null")
    if (tuổi == null)
        ném IllegalStateException mới ("SafeClass rơi vào trạng thái bất hợp pháp! tuổi là null")

    chuỗi công khai toString () {
        Chuỗi s = "Lớp an toàn có tên:" + name + "và tuổi:" + tuổi
    }

Không chỉ điều này, mà bạn cần kiểm tra null ở mọi nơi bạn có thể sử dụng đối tượng đó trong lớp của mình (Bên ngoài lớp của bạn an toàn vì kiểm tra null trong getter, nhưng bạn chủ yếu nên sử dụng các thành viên lớp trong lớp)

Ngoài ra, lớp của bạn luôn ở trạng thái không chắc chắn - ví dụ nếu bạn quyết định biến lớp đó thành một lớp ngủ đông bằng cách thêm một vài chú thích, bạn sẽ làm thế nào?

Nếu bạn đưa ra bất kỳ quyết định nào dựa trên một số phương pháp tối ưu hóa vi mô mà không có yêu cầu và thử nghiệm, thì đó gần như chắc chắn là quyết định sai. Trên thực tế, có một cơ hội thực sự rất tốt là mô hình của bạn thực sự làm chậm hệ thống ngay cả trong hoàn cảnh lý tưởng nhất bởi vì câu lệnh if có thể gây ra lỗi dự đoán nhánh trên CPU, điều này sẽ làm mọi thứ chậm hơn nhiều lần so với chỉ gán một giá trị trong hàm tạo trừ khi đối tượng bạn đang tạo khá phức tạp hoặc đến từ nguồn dữ liệu từ xa.

Để biết ví dụ về vấn đề dự đoán brance (mà bạn đang phát sinh liên tục, chỉ một lần), hãy xem câu trả lời đầu tiên cho câu hỏi tuyệt vời này: Tại sao xử lý một mảng được sắp xếp nhanh hơn một mảng chưa sắp xếp?


Cảm ơn vì đầu vào của bạn. Trong trường hợp của tôi, không có lớp nào có bất kỳ phương thức nào có thể cần kiểm tra null, vì vậy đó không phải là vấn đề. Tôi sẽ xem xét các phản đối khác của bạn.
John Willemse

Tôi không thực sự hiểu điều đó. Nó ngụ ý rằng bạn không sử dụng các thành viên của mình trong các lớp nơi chúng được lưu trữ - rằng bạn chỉ đang sử dụng các lớp làm cấu trúc dữ liệu. Nếu đó là trường hợp bạn có thể muốn đọc javaworld.com/javaworld/jw-01-2004/jw-0102-toolbox.html có mô tả hay về cách mã của bạn có thể được cải thiện bằng cách tránh thao tác bên ngoài. Nếu bạn đang thao túng chúng trong nội bộ, làm thế nào để bạn làm như vậy mà không kiểm tra null mọi thứ liên tục?
Bill K

Các phần của câu trả lời này là tốt, nhưng các phần dường như bị chiếm đoạt. Thông thường khi sử dụng mẫu này, toString()sẽ gọi getName(), không sử dụng nametrực tiếp.
Izkata

@BillK Vâng, các lớp là một cấu trúc dữ liệu khổng lồ. Tất cả các công việc được thực hiện trong các lớp tĩnh. Tôi sẽ kiểm tra bài viết tại liên kết. Cảm ơn!
John Willemse

1
@izkata Trên thực tế, bên trong một lớp học có vẻ như là một vấn đề khó khăn khi thời tiết bạn có sử dụng getter hay không, hầu hết những nơi tôi từng làm việc trực tiếp sử dụng thành viên. Ngoài ra, nếu bạn luôn sử dụng phương thức getter, phương thức if () thậm chí còn có hại hơn vì các lỗi dự đoán nhánh sẽ xảy ra thường xuyên hơn VÀ do việc phân nhánh thời gian chạy có thể sẽ gặp nhiều rắc rối hơn khi cài đặt getter. Tuy nhiên, đó là tất cả các moot, với sự tiết lộ của john rằng chúng là các cấu trúc dữ liệu và các lớp tĩnh, đó là điều tôi sẽ quan tâm nhất.
Bill K

4

Hãy để tôi chỉ thêm một điểm vào nhiều điểm tốt được thực hiện bởi những người khác ...

Trình gỡ lỗi sẽ ( theo mặc định ) đánh giá các thuộc tính khi bước qua mã, có khả năng khởi tạo Barsớm hơn bình thường sẽ xảy ra bằng cách chỉ thực thi mã. Nói cách khác, hành động gỡ lỗi đơn thuần đang thay đổi việc thực hiện chương trình.

Điều này có thể hoặc không thể là một vấn đề (tùy thuộc vào tác dụng phụ), nhưng là điều cần lưu ý.


2

Bạn có chắc chắn Foo nên bắt đầu bất cứ điều gì không?

Đối với tôi có vẻ như có mùi (mặc dù không nhất thiết là sai ) khi để Foo khởi tạo bất cứ điều gì cả. Trừ khi mục đích rõ ràng của Foo là trở thành một nhà máy, không nên khởi tạo các cộng tác viên của riêng mình, mà thay vào đó, hãy đưa họ vào công cụ xây dựng .

Tuy nhiên, nếu mục đích tồn tại của Foo là tạo ra các thể loại của Bar, thì tôi không thấy có gì sai khi làm điều đó một cách lười biếng.


4
@BenjaminGruenbaum Không, không thực sự. Và trân trọng, ngay cả khi đó là, bạn đang cố gắng thực hiện điểm nào?
KaptajnKold
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.