Con constructor vs Factory Phương thức [đóng]


181

Khi mô hình hóa các lớp, cách khởi tạo ưa thích là gì:

  1. Người xây dựng, hoặc
  2. Phương pháp nhà máy

Và những gì sẽ được xem xét để sử dụng một trong hai?

Trong một số trường hợp nhất định, tôi thích có một phương thức xuất xưởng trả về null nếu đối tượng không thể được xây dựng. Điều này làm cho mã gọn gàng. Tôi chỉ có thể kiểm tra xem giá trị trả về có phải là null hay không trước khi thực hiện hành động thay thế, ngược lại với việc ném ngoại lệ từ hàm tạo. (Cá nhân tôi không thích ngoại lệ)

Giả sử, tôi có một hàm tạo trên một lớp có giá trị id. Hàm tạo sử dụng giá trị này để điền vào lớp từ cơ sở dữ liệu. Trong trường hợp không tồn tại một bản ghi với id được chỉ định, hàm tạo sẽ ném RecordNotFoundException. Trong trường hợp này, tôi sẽ phải bao gồm việc xây dựng tất cả các lớp như vậy trong một khối try..catch.

Ngược lại với điều này, tôi có thể có một phương thức nhà máy tĩnh trên các lớp đó sẽ trả về null nếu không tìm thấy bản ghi.

Cách tiếp cận nào tốt hơn trong trường hợp này, phương thức xây dựng hoặc phương pháp nhà máy?

Câu trả lời:


66

Từ trang 108 của Mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng của Gamma, Helm, Johnson và Vlissides.

Sử dụng mẫu Phương thức nhà máy khi

  • một lớp không thể lường trước lớp đối tượng mà nó phải tạo
  • một lớp muốn các lớp con của nó chỉ định các đối tượng mà nó tạo ra
  • các lớp ủy thác trách nhiệm cho một trong một số lớp con của người trợ giúp và bạn muốn bản địa hóa kiến ​​thức mà lớp con của người trợ giúp là đại biểu

21
Phương thức nhà máy tĩnh khác với mẫu thiết kế GoF - Mẫu phương thức nhà máy. stackoverflow.com/questions/929021/ cấp
Sree Rama

Vui lòng không so sánh với mẫu phương thức Factory của các mẫu thiết kế GoF.
Sree Rama

137
điều này không giải thích gì cho tôi
Sushant

@Sushant, sao lại như vậy?
PaulD

2
Câu trả lời này không trả lời câu hỏi chỉ chuyển tiếp thông tin cần đọc để hiểu / giải thích khái niệm ... Đây là một nhận xét nhiều hơn.
Crt

202

Hãy tự hỏi họ là gì và tại sao chúng ta có chúng. Cả hai đều ở đó để tạo ra thể hiện của một đối tượng.

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

Không có sự khác biệt cho đến nay. Bây giờ hãy tưởng tượng rằng chúng tôi có nhiều loại trường khác nhau và chúng tôi muốn chuyển từ sử dụng Trường tiểu học sang Trường trung học (có nguồn gốc từ Trường tiểu học hoặc triển khai cùng một giao diện ISchool như Trường tiểu học). Thay đổi mã sẽ là:

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

Trong trường hợp có giao diện, chúng ta sẽ có:

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

Bây giờ nếu bạn có mã này ở nhiều nơi, bạn có thể thấy rằng sử dụng phương thức xuất xưởng có thể khá rẻ vì một khi bạn thay đổi phương thức xuất xưởng thì bạn đã hoàn thành (nếu chúng ta sử dụng ví dụ thứ hai với giao diện).

Và đây là sự khác biệt chính và lợi thế. Khi bạn bắt đầu xử lý một hệ thống phân cấp lớp phức tạp và bạn muốn tự động tạo một thể hiện của một lớp từ hệ thống phân cấp như vậy, bạn sẽ nhận được mã sau đây. Các phương thức xuất xưởng sau đó có thể lấy một tham số cho biết phương thức cụ thể nào để khởi tạo. Giả sử bạn có một lớp MyStudent và bạn cần khởi tạo đối tượng ISchool tương ứng để học sinh của bạn là thành viên của trường đó.

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

Bây giờ bạn có một vị trí trong ứng dụng chứa logic nghiệp vụ xác định đối tượng ISchool nào để khởi tạo cho các đối tượng IStudent khác nhau.

Vì vậy - đối với hàm xây dựng các lớp đơn giản (đối tượng giá trị, v.v.) là tốt (bạn không muốn áp đảo ứng dụng của mình) nhưng đối với phương pháp nhà máy phân cấp lớp phức tạp là một cách ưa thích.

Bằng cách này, bạn tuân theo nguyên tắc thiết kế đầu tiên từ nhóm bốn cuốn sách "Chương trình đến giao diện, không phải triển khai".


2
Ngay cả khi bạn nghĩ rằng đó là một lớp đơn giản, vẫn có khả năng ai đó cần mở rộng lớp đơn giản của bạn, vì vậy phương pháp xuất xưởng vẫn tốt hơn. Ví dụ: bạn có thể bắt đầu với Trường tiểu học nhưng sau đó ai đó (bao gồm cả chính bạn) có thể mở rộng nó với PrivateEuityarySchool và PublicEuityarySchool.
jack

10
đây phải là câu trả lời được chấp nhận
am05mhz

2
@David, câu trả lời tốt, nhưng bạn có thể mở rộng trên một ví dụ trong đó mỗi lần thực hiện giao diện có thể yêu cầu các tham số khác nhau để xây dựng. Đây là một ví dụ ngớ ngẩn: IFood sandwich = new Sandwich(Cheese chz, Meat meat);IFood soup = new Soup(Broth broth, Vegetable veg);Làm thế nào nhà máy và nhà xây dựng có thể giúp đỡ ở đây?
Brian

1
Tôi vừa đọc ba lời giải thích khác về mục đích sử dụng của nhà máy, và đây là lần cuối cùng tôi đã "bấm" cho tôi. Cảm ơn bạn!
Daniel Peirano

Tại sao điều này không được chấp nhận câu trả lời?
Tomas

74

Bạn cần đọc (nếu bạn có quyền truy cập) Java 2 Mục 1 hiệu quả : Xem xét các phương thức nhà máy tĩnh thay vì các hàm tạo .

Ưu điểm phương pháp nhà máy tĩnh:

  1. Họ có tên.
  2. Họ không bắt buộc phải tạo một đối tượng mới mỗi lần chúng được gọi.
  3. Họ có thể trả về một đối tượng của bất kỳ kiểu con nào của kiểu trả về của họ.
  4. Chúng làm giảm tính dài dòng của việc tạo các thể hiện kiểu tham số.

Nhược điểm phương pháp nhà máy tĩnh:

  1. Khi chỉ cung cấp các phương thức nhà máy tĩnh, các lớp không có các hàm tạo công khai hoặc được bảo vệ có thể được phân lớp.
  2. Chúng không dễ phân biệt với các phương thức tĩnh khác

4
Đây có vẻ như là một lỗi nghiêm trọng trong Java, sau đó là một vấn đề chung của 3M. Có rất nhiều ngôn ngữ OO thậm chí không có nhà xây dựng, nhưng phân lớp vẫn hoạt động tốt.
Jörg W Mittag 10/03/2016

1
@cherouvim tại sao phần lớn mã được viết bằng Con constructor nếu( factory methods are better than Constructors. ( Item-1 ) ) Effective java
Asif Mushtaq

Điểm tốt. Mặc dù đó là Java cụ thể. Một trường hợp có thể được tạo cho một tính năng ngôn ngữ làm cho các phương thức xuất xưởng có thể phân biệt được với các phương thức tĩnh khác.
OCDev

30

Theo mặc định, các nhà xây dựng nên được ưu tiên, bởi vì chúng đơn giản hơn để hiểu và viết. Tuy nhiên, nếu bạn đặc biệt cần tách riêng các cấu trúc xây dựng của một đối tượng khỏi ý nghĩa ngữ nghĩa của nó theo cách hiểu của mã máy khách, bạn nên sử dụng các nhà máy tốt hơn.

Sự khác biệt giữa các nhà xây dựng và các nhà máy tương tự như, một biến và một con trỏ tới một biến. Có một mức độ gián tiếp khác, đó là một bất lợi; nhưng cũng có một mức độ linh hoạt khác, đó là một lợi thế. Vì vậy, trong khi đưa ra lựa chọn, bạn nên làm chi phí này so với phân tích lợi ích.


17
Vì vậy, (kiểu TDD) bạn sẽ bắt đầu với các nhà xây dựng như là cách đơn giản nhất để hoàn thành công việc. Và sau đó tái cấu trúc cho các nhà máy một khi bạn bắt đầu nhận được mùi mã (như logic điều kiện lặp đi lặp lại xác định hàm tạo nào sẽ gọi)?
AndyM

1
Điểm rất quan trọng. Một nghiên cứu người dùng so sánh các nhà máy và nhà xây dựng đã tìm thấy kết quả rất có ý nghĩa cho thấy các nhà máy gây bất lợi cho khả năng sử dụng API: "người dùng cần nhiều thời gian hơn (p = 0,005) để xây dựng một đối tượng với nhà máy so với nhà xây dựng" [Mô hình nhà máy trong thiết kế API : Đánh giá khả năng sử dụng ].
mdeff

12

Chỉ sử dụng một nhà máy khi bạn cần kiểm soát thêm với việc tạo đối tượng, theo cách không thể thực hiện được với các nhà xây dựng.

Các nhà máy có khả năng lưu trữ ví dụ.

Một cách khác để sử dụng các nhà máy là trong một kịch bản mà bạn không biết loại bạn muốn xây dựng. Thường thì bạn thấy loại sử dụng này trong các kịch bản xuất xưởng của plugin, trong đó mỗi plugin phải xuất phát từ một lớp cơ sở hoặc thực hiện một số loại giao diện. Nhà máy tạo ra các thể hiện của các lớp xuất phát từ lớp cơ sở hoặc thực hiện giao diện.


11

Một trích dẫn từ "Java hiệu quả", tái bản lần 2, Mục 1: Xem xét các phương thức nhà máy tĩnh thay vì các hàm tạo, p. 5:

"Lưu ý rằng phương thức nhà máy tĩnh không giống với mẫu Phương thức nhà máy từ Mẫu thiết kế [Gamma95, trang 107]. Phương pháp nhà máy tĩnh được mô tả trong mục này không có tương đương trực tiếp trong Mẫu thiết kế."


10

Ngoài "java hiệu quả" (như được đề cập trong câu trả lời khác), một cuốn sách kinh điển khác cũng gợi ý:

Thích các phương thức nhà máy tĩnh (với các tên mô tả các đối số) cho các hàm tạo quá tải.

Ví dụ. đừng viết

Complex complex = new Complex(23.0);

nhưng thay vào đó viết

Complex complex = Complex.fromRealNumber(23.0);

Cuốn sách đi xa đến mức đề xuất làm cho hàm Complex(float)tạo riêng tư, để buộc người dùng gọi phương thức tĩnh của nhà máy.


2
Đọc phần đó của cuốn sách đã đưa tôi đến đây
Purple Haze

1
@Bayrem: tôi cũng vậy, tôi đã đọc lại nó gần đây và nghĩ rằng tôi nên thêm nó vào câu trả lời.
blue_note

1
Trên một lưu ý liên quan, bạn có thể thấy hữu ích một số quy ước đặt tên làm việc hiện bởi java.time khuôn khổ liên quan đến việc đặt tên của from…, to…, parse…, with…, và vân vân. Hãy ghi nhớ các lớp java.time được xây dựng để trở nên bất biến, nhưng một số quy ước đặt tên này có thể hữu ích để tuân theo các lớp có thể thay đổi.
Basil Bourque

7

Một ví dụ cụ thể từ ứng dụng CAD / CAM.

Một đường cắt sẽ được thực hiện bằng cách sử dụng một hàm tạo. Đó là một chuỗi các đường và cung tròn xác định đường dẫn cần cắt. Mặc dù chuỗi các đường và cung có thể khác nhau và có tọa độ khác nhau, nó dễ dàng xử lý bằng cách chuyển danh sách vào hàm tạo.

Một hình dạng sẽ được thực hiện bằng cách sử dụng một nhà máy. Bởi vì trong khi có một lớp hình dạng, mỗi hình dạng sẽ được thiết lập khác nhau tùy thuộc vào loại hình dạng đó. Chúng tôi không biết hình dạng nào chúng tôi sẽ bắt đầu cho đến khi người dùng thực hiện lựa chọn.


5

Giả sử, tôi có một hàm tạo trên một lớp có giá trị id. Hàm tạo sử dụng giá trị này để điền vào lớp từ cơ sở dữ liệu.

Quá trình này chắc chắn phải ở bên ngoài một nhà xây dựng.

  1. Con constructor không nên truy cập cơ sở dữ liệu.

  2. Nhiệm vụ và lý do cho một hàm tạo là khởi tạo các thành viên dữ liệuthiết lập bất biến lớp bằng cách sử dụng các giá trị được truyền vào hàm tạo.

  3. Đối với mọi thứ khác một cách tiếp cận tốt hơn là sử dụng phương thức tĩnh hoặc trong trường hợp phức tạp hơn một riêng nhà máy hoặc người xây dựng lớp.

Một số dòng hướng dẫn xây dựng từ Microsoft :

Làm công việc tối thiểu trong các nhà xây dựng. Các nhà xây dựng không nên làm nhiều việc khác ngoài việc nắm bắt các tham số của hàm tạo. Chi phí của bất kỳ xử lý khác nên được trì hoãn cho đến khi được yêu cầu.

Xem xét sử dụng một phương thức nhà máy tĩnh thay vì một nhà xây dựng nếu ngữ nghĩa của hoạt động mong muốn không ánh xạ trực tiếp đến việc xây dựng một thể hiện mới.


2

Đôi khi bạn phải kiểm tra / tính toán một số giá trị / điều kiện trong khi tạo một đối tượng. Và nếu nó có thể ném Exception - construcro là cách rất tệ. Vì vậy, bạn cần phải làm một cái gì đó như thế này:

var value = new Instance(1, 2).init()
public function init() {
    try {
        doSome()
    }
    catch (e) {
        soAnotherSome()
    }
}

Trường hợp tất cả các tính toán bổ sung là trong init (). Nhưng chỉ có bạn là nhà phát triển thực sự biết về init () này. Và tất nhiên, sau nhiều tháng bạn chỉ cần quên nó đi. Nhưng nếu bạn có một nhà máy - chỉ cần làm tất cả những gì bạn cần trong một phương thức với việc ẩn init () này khỏi cuộc gọi trực tiếp - vì vậy không có vấn đề gì. Với phương pháp này, không có vấn đề gì với việc rơi vào sáng tạo và rò rỉ bộ nhớ.

Ai đó nói với bạn về bộ nhớ đệm. Thật tốt Nhưng bạn cũng phải nhớ về mẫu Flykg rất phù hợp khi sử dụng theo cách của Factory.

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.