Chúng ta có thể sống mà không có nhà xây dựng?


25

Giả sử trong một số lý do, tất cả các đối tượng được tạo theo cách này $ obj = CLASS :: getInstance (). Sau đó, chúng tôi thêm các phụ thuộc bằng setters và thực hiện khởi tạo bằng cách sử dụng $ obj-> initInstance (); Có bất kỳ rắc rối hoặc tình huống thực tế nào không thể giải quyết được không, nếu chúng ta hoàn toàn không sử dụng các nhà xây dựng?

Vì lý do để tạo đối tượng theo cách này, là chúng ta có thể thay thế lớp bên trong getInstance () theo một số quy tắc.

Tôi đang làm việc trong PHP, nếu vấn đề đó


ngôn ngữ lập trình gì?
gnat

2
PHP (tại sao nó quan trọng?)
Axel Foley

8
Có vẻ như những gì bạn muốn làm có thể đạt được bằng cách sử dụng mẫu Factory.
superM

1
Cũng giống như một lưu ý: nhà máy ps @superM được đề cập là một cách để thực hiện Inversion of Control (Dependency Injection) là một cách khác.
Joachim Sauer

Đó không phải là những gì javascript làm với các đối tượng?
Thijser

Câu trả lời:


44

Tôi muốn nói rằng điều này cản trở nghiêm trọng không gian thiết kế của bạn.

Các nhà xây dựng là một nơi tuyệt vời để khởi tạo và xác thực các tham số được truyền vào. Nếu bạn không còn có thể sử dụng chúng cho việc đó, thì việc khởi tạo, xử lý trạng thái (hoặc loại bỏ hàm tạo của các đối tượng "bị hỏng" trở nên khó khăn hơn và một phần không thể.

Ví dụ, nếu mọi Foođối tượng cần một Frobnicatorthì nó có thể kiểm tra hàm tạo của nó nếu Frobnicatorkhông phải là null. Nếu bạn lấy đi hàm tạo, thì sẽ khó kiểm tra hơn. Bạn có kiểm tra tại mọi điểm mà nó sẽ được sử dụng không? Trong một init()phương thức (có hiệu quả bên ngoài phương thức constructor)? Không bao giờ kiểm tra nó và hy vọng cho điều tốt nhất?

Mặc dù bạn thể vẫn có thể thực hiện mọi thứ (sau tất cả, bạn vẫn đang hoàn thành công việc), một số điều sẽ khó thực hiện hơn nhiều.

Cá nhân tôi khuyên bạn nên xem xét phụ thuộc vào tiêm / đảo ngược kiểm soát . Các kỹ thuật này cũng cho phép chuyển đổi các lớp triển khai cụ thể, nhưng chúng không ngăn việc viết / sử dụng các hàm tạo.


Ý của bạn là gì khi "hoặc từ chối xây dựng các đối tượng" bị hỏng "?
Geek

2
@Geek: một hàm tạo có thể kiểm tra đối số của nó và quyết định xem chúng có dẫn đến một đối tượng làm việc không (ví dụ nếu đối tượng của bạn cần HttpClientthì nó sẽ kiểm tra xem tham số đó có phải là null không). Và nếu những ràng buộc đó không được đáp ứng, thì nó có thể ném ra một ngoại lệ. Điều đó thực sự không khả thi với cách tiếp cận xây dựng và thiết lập giá trị.
Joachim Sauer

1
Tôi nghĩ OP chỉ mô tả sự bên ngoài của nhà xây dựng bên trong init(), điều này hoàn toàn có thể xảy ra mặc dù điều này chỉ đặt ra nhiều gánh nặng bảo trì.
Peter

26

2 lợi thế cho các nhà xây dựng:

Các nhà xây dựng cho phép các bước xây dựng của đối tượng được thực hiện nguyên tử.

Tôi có thể tránh một nhà xây dựng và sử dụng setters cho mọi thứ, nhưng còn các thuộc tính bắt buộc như Joachim Sauer đề xuất thì sao? Với một hàm tạo, một đối tượng sở hữu logic xây dựng riêng để đảm bảo rằng không có trường hợp không hợp lệ của lớp đó .

Nếu tạo một thể hiện Fooyêu cầu 3 thuộc tính được đặt, hàm tạo có thể lấy tham chiếu đến cả 3, xác thực chúng và ném ngoại lệ nếu chúng không hợp lệ.

Đóng gói

Bằng cách chỉ dựa vào setters, gánh nặng là ở người tiêu dùng của một đối tượng để xây dựng nó đúng cách. Có thể có sự kết hợp khác nhau của các thuộc tính là hợp lệ.

Ví dụ, mọi Foothể hiện đều cần một thể hiện Barlà thuộc tính barhoặc một thể hiện của thuộc BarFindertính barFinder. Nó có thể sử dụng một trong hai. Bạn có thể tạo một hàm tạo cho mọi bộ tham số hợp lệ và thực thi các quy ước theo cách đó.

Logic và ngữ nghĩa cho các đối tượng sống trong chính đối tượng. Đó là sự đóng gói tốt.


15

Vâng, bạn có thể sống mà không cần nhà xây dựng.

Chắc chắn, bạn có thể kết thúc với rất nhiều mã tấm nồi hơi trùng lặp. Và nếu ứng dụng của bạn ở bất kỳ quy mô nào, bạn có thể sẽ mất rất nhiều thời gian để cố gắng xác định nguồn gốc của vấn đề khi mã nồi hơi đó không được sử dụng nhất quán trong toàn bộ ứng dụng.

Nhưng không, bạn không nghiêm túc 'cần' các nhà xây dựng của riêng bạn. Tất nhiên, bạn cũng không nghiêm túc 'cần' các lớp và đối tượng.

Bây giờ nếu mục tiêu của bạn là sử dụng một số kiểu mẫu của nhà máy để tạo đối tượng, thì điều đó không loại trừ lẫn nhau đối với việc sử dụng các hàm tạo khi khởi tạo các đối tượng của bạn.


Chắc chắn rồi. Một người thậm chí có thể sống mà không có lớp và đối tượng, để bắt đầu.
JensG

5
Tất cả mưa đá lắp ráp hùng mạnh!
Thưởng thức Ždralo

9

Ưu điểm của việc sử dụng các hàm tạo là chúng giúp dễ dàng hơn để đảm bảo rằng bạn sẽ không bao giờ có một đối tượng không hợp lệ.

Một constructor cung cấp cho bạn cơ hội để đặt tất cả các biến thành viên của đối tượng về trạng thái hợp lệ. Khi đó bạn đảm bảo rằng không có phương thức trình biến đổi nào có thể thay đổi đối tượng sang trạng thái không hợp lệ, bạn sẽ không bao giờ có đối tượng không hợp lệ, điều này sẽ cứu bạn khỏi rất nhiều lỗi.

Nhưng khi một đối tượng mới được tạo ở trạng thái không hợp lệ và bạn phải gọi một số setters để đưa nó vào trạng thái hợp lệ có thể sử dụng, bạn có nguy cơ rằng một người tiêu dùng của lớp quên gọi những setters này hoặc gọi chúng không chính xác và bạn kết thúc với một đối tượng không hợp lệ.

Một cách giải quyết khác có thể là tạo các đối tượng chỉ thông qua phương thức xuất xưởng để kiểm tra mọi đối tượng mà nó tạo ra để xác thực trước khi trả lại cho người gọi.


3

$ obj = LỚP :: getInstance (). Sau đó, chúng tôi thêm các phụ thuộc bằng setters và thực hiện khởi tạo bằng cách sử dụng $ obj-> initInstance ();

Tôi nghĩ rằng bạn đang làm điều này khó khăn hơn nó cần phải được. Chúng tôi có thể thêm các phụ thuộc tốt thông qua hàm tạo - và nếu bạn có nhiều phần tử, chỉ cần sử dụng cấu trúc giống như từ điển để bạn có thể chỉ định những gì bạn muốn sử dụng:

$obj = new CLASS(array(
    'Frobnicator' => (),
    'Foonicator' => (),
));

Và trong hàm tạo, bạn có thể đảm bảo tính nhất quán như vậy:

if (!array_key_exists('Frobnicator', $args)) {
    throw new Exception('Frobnicator required');
}
if (!array_key_exists('Foonicator', $args)) {
    $args['Foonicator'] = new DefaultFoonicator();
}

$args sau đó có thể được sử dụng để thiết lập các thành viên tư nhân khi cần thiết.

Khi được thực hiện hoàn toàn trong hàm tạo như vậy, sẽ không bao giờ có trạng thái trung gian $objtồn tại nhưng không được khởi tạo, như sẽ có trong hệ thống được mô tả trong câu hỏi. Tốt hơn hết là tránh các trạng thái trung gian như vậy, vì bạn không thể đảm bảo đối tượng sẽ luôn được sử dụng chính xác.


2

Tôi đã thực sự nghĩ về những điều tương tự.

Câu hỏi tôi đặt ra là "Những gì nhà xây dựng làm, và nó có thể làm nó khác đi không?" Tôi đã đi đến những kết luận đó:

  • Nó đảm bảo một số thuộc tính được khởi tạo. Bằng cách chấp nhận chúng làm tham số và thiết lập chúng. Nhưng điều này có thể dễ dàng được thực thi bởi trình biên dịch. Bằng cách đơn giản chú thích các trường hoặc thuộc tính là "bắt buộc", trình biên dịch sẽ kiểm tra trong quá trình tạo cá thể nếu mọi thứ được đặt đúng. Cuộc gọi để tạo cá thể có thể giống nhau, sẽ không có bất kỳ phương thức xây dựng nào.

  • Đảm bảo các thuộc tính là hợp lệ. Điều này có thể dễ dàng đạt được bằng điều kiện khẳng định. Một lần nữa, bạn chỉ cần chú thích các thuộc tính với các điều kiện chính xác. Một số ngôn ngữ đã làm điều này.

  • Một số logic xây dựng phức tạp hơn. Các mẫu hiện đại không khuyến nghị thực hiện điều đó trong hàm tạo, nhưng đề xuất sử dụng các phương thức hoặc lớp chuyên dụng của nhà máy. Vì vậy, việc sử dụng các nhà xây dựng trong trường hợp này là tối thiểu.

Vì vậy, để trả lời câu hỏi của bạn: Có, tôi tin rằng nó có thể. Nhưng nó sẽ đòi hỏi một số thay đổi lớn trong thiết kế ngôn ngữ.

Và tôi chỉ nhận thấy câu trả lời của tôi là OT đẹp.


2

Có, bạn có thể thực hiện gần như mọi thứ mà không cần sử dụng các hàm tạo nhưng đây rõ ràng là lợi ích của các ngôn ngữ lập trình hướng đối tượng.

Trong các ngôn ngữ hiện đại (tôi sẽ nói chuyện ở đây về C # mà tôi chương trình trong), bạn có thể giới hạn các phần của mã có thể chạy chỉ trong một constructor. Nhờ đó bạn có thể tránh được những sai lầm vụng về. Một trong những điều như vậy là sửa đổi chỉ đọc :

public class A {
    readonly string rostring;

    public A(string arg) {
        rostring = arg;
    }

    public static A CreateInstance(string arg) {
        var result = new A();
        A.rostring = arg;  // < because of this the code won't compile!
        return result;
    }
}

Theo khuyến cáo của Joachim Sauer trước đây thay vì sử dụng Factorythiết kế patter đọc về Dependency Injection. Tôi khuyên bạn nên đọc Dependency Injection in .NET của Mark Seemann .


1

Khởi tạo một đối tượng với một loại tùy thuộc vào yêu cầu, điều đó là hoàn toàn có thể. Nó có thể là chính đối tượng, sử dụng các biến toàn cục của hệ thống để trả về một loại cụ thể.

Tuy nhiên, có một lớp trong mã có thể là "Tất cả" là khái niệm về kiểu động . Cá nhân tôi tin rằng cách tiếp cận này tạo ra sự không nhất quán trong mã của bạn, làm cho các phức hợp thử nghiệm * và "tương lai trở nên không chắc chắn" đối với dòng công việc được đề xuất.

* Tôi đang đề cập đến thực tế là các bài kiểm tra phải xem xét loại đầu tiên, thứ hai là kết quả bạn muốn đạt được. Sau đó, bạn đang tạo ra thử nghiệm lồng nhau lớn.


1

Để cân bằng một số câu trả lời khác, tuyên bố:

Hàm tạo cho bạn cơ hội đặt tất cả các biến thành viên của đối tượng thành trạng thái hợp lệ ... bạn sẽ không bao giờ có đối tượng không hợp lệ, điều này sẽ giúp bạn tránh được rất nhiều lỗi.

Với một hàm tạo, một đối tượng sở hữu logic xây dựng riêng để đảm bảo rằng không có trường hợp không hợp lệ của lớp đó.

Những tuyên bố như vậy đôi khi ngụ ý giả định rằng:

Nếu một lớp có một hàm tạo, vào lúc nó thoát ra, đã đưa đối tượng vào trạng thái hợp lệ và không có phương thức nào của lớp biến đổi trạng thái đó thành không hợp lệ, thì không thể mã bên ngoài lớp phát hiện ra một đối tượng của lớp đó trong trạng thái không hợp lệ.

Nhưng điều này không hoàn toàn đúng. Hầu hết các ngôn ngữ không có quy tắc chống lại một nhà xây dựng chuyển this( selfhoặc bất cứ điều gì ngôn ngữ gọi nó) cho mã bên ngoài. Một hàm tạo như vậy hoàn toàn tuân thủ quy tắc đã nêu ở trên, và nó có nguy cơ phơi bày các đối tượng bán cấu trúc thành mã bên ngoài. Đó là một điểm nhỏ nhưng dễ bị bỏ qua.


0

Điều này có phần giai thoại, nhưng tôi thường dành các nhà xây dựng cho trạng thái thiết yếu để đối tượng được hoàn thiện và có thể sử dụng được. Chặn mọi thao tác tiêm setter, khi hàm tạo được chạy, đối tượng của tôi sẽ có thể thực hiện các tác vụ cần thiết của nó.

Bất cứ điều gì có thể được hoãn lại, tôi rời khỏi hàm tạo (chuẩn bị các giá trị đầu ra, v.v.). Với phương pháp này, tôi cảm thấy không sử dụng các hàm tạo cho bất cứ điều gì ngoài việc tiêm phụ thuộc có ý nghĩa.

Điều này cũng có thêm lợi ích của việc cứng cáp quá trình thiết kế tinh thần của bạn để không làm bất cứ điều gì sớm. Bạn sẽ không khởi tạo hoặc thực hiện logic có thể không bao giờ được sử dụng vì tốt nhất tất cả những gì bạn đang làm là thiết lập cơ bản cho công việc trước mắt.

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.