Làm thế nào một ngôn ngữ phổ biến khác tránh được việc phải sử dụng mẫu nhà máy trong khi quản lý độ phức tạp tương tự như trong Java / Java EE?


23

Mô hình nhà máy (hoặc ít nhất là sử dụng FactoryFactory..) là mông của nhiều trò đùa, như ở đây .

Ngoài việc có các tên dài dòng và "sáng tạo" như RequestProcessorFactoryFactory.RequestProcessorFactory , có bất kỳ điều gì sai về cơ bản với mẫu nhà máy nếu bạn phải lập trình trong Java / C ++ và có một usecase cho Abstract_factory_potype không?

Làm thế nào một ngôn ngữ phổ biến khác (ví dụ, Ruby hoặc Scala ) tránh phải sử dụng nó trong khi quản lý sự phức tạp tương tự?

Lý do tôi hỏi là tôi thấy phần lớn sự chỉ trích của các nhà máy được đề cập trong bối cảnh hệ sinh thái Java / Java EE , nhưng họ không bao giờ giải thích cách các ngôn ngữ / khung công tác khác giải quyết chúng.



4
Vấn đề không phải là việc sử dụng các nhà máy - đó là một mô hình hoàn toàn tốt. Đó là sự lạm dụng của các nhà máy đặc biệt phổ biến trong thế giới java enterprisey.
CodeInChaos

Liên kết đùa rất hay. Tôi rất thích nhìn thấy một giải thích ngôn ngữ chức năng của việc lấy các công cụ để xây dựng một giá gia vị. Điều đó thực sự sẽ lái nó về nhà, tôi nghĩ.
Patrick M

Câu trả lời:


28

Câu hỏi của bạn được gắn thẻ "Java", không có gì ngạc nhiên khi bạn hỏi tại sao mẫu Factory bị chế giễu: Bản thân Java đi kèm với một sự lạm dụng được đóng gói độc đáo của mẫu đó.

Ví dụ: thử tải tài liệu XML từ tệp và chạy truy vấn XPath đối với tệp đó. Bạn cần một cái gì đó giống như 10 dòng mã chỉ để thiết lập Nhà máy và Nhà xây dựng:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder(); 

Document xmlDocument = builder.parse(new FileInputStream("c:\\employees.xml"));
XPath xPath =  XPathFactory.newInstance().newXPath();

xPath.compile(expression).evaluate(xmlDocument);

Tôi tự hỏi liệu những kẻ thiết kế API này có từng làm nhà phát triển hay họ chỉ đọc sách và ném mọi thứ xung quanh. Tôi hiểu rằng họ chỉ không muốn tự viết một trình phân tích cú pháp và giao nhiệm vụ cho người khác nhưng nó vẫn tạo ra một triển khai xấu.

Vì bạn đang hỏi cái gì thay thế ở đây đang tải tệp XML trong C #:

XDocument xml = XDocument.Load("c:\\employees.xml");
var nodes = xml.XPathSelectElements(expression);

Tôi nghi ngờ các nhà phát triển Java mới nở đã thấy sự điên rồ của Factory và nghĩ rằng việc làm đó là ổn - nếu các thiên tài xây dựng Java đã sử dụng chúng nhiều như vậy.

Nhà máy và bất kỳ mẫu nào khác là các công cụ, mỗi mẫu phù hợp cho một công việc cụ thể. Nếu bạn áp dụng chúng cho các công việc chúng không phù hợp với bạn, bạn sẽ có mã xấu.


1
Chỉ cần lưu ý rằng giải pháp thay thế C # của bạn đang sử dụng LINQ to XML mới hơn - giải pháp thay thế DOM cũ hơn sẽ trông giống như thế này: XmlDocument xml = new XmlDocument(); xml.Load("c:\\employees.xml"); XmlNodeList nodes = xml.SelectNodes(expression); (Nhìn chung, LINQ to XML dễ sử dụng hơn, mặc dù chủ yếu ở các khu vực khác.) Và đây là bài viết KB Tôi đã tìm thấy, với phương pháp được MS khuyên dùng: support.microsoft.com/kb/308333
Bob

36

Như thường lệ, mọi người hiểu sai những gì đang diễn ra (và bao gồm nhiều người cười).
Đây không phải là mô hình nhà máy mỗi lần nó tệ như cách mà nhiều người (thậm chí là hầu hết) mọi người sử dụng nó.

Và không có nghi ngờ gì bắt nguồn từ cách lập trình (và các mẫu) được dạy. Schoolkids (thường tự gọi mình là "học sinh") được yêu cầu "tạo X bằng cách sử dụng mẫu Y" và sau một vài lần lặp lại, nghĩ rằng đó là cách để tiếp cận bất kỳ vấn đề lập trình nào.
Vì vậy, họ bắt đầu áp dụng một mô hình cụ thể mà họ thích ở trường chống lại bất cứ điều gì và mọi thứ, cho dù nó có phù hợp hay không.

Và điều đó bao gồm các giáo sư đại học, những người viết sách về thiết kế phần mềm, thật đáng buồn.
Đỉnh cao của việc này là một hệ thống mà tôi cảm thấy không hài lòng khi phải duy trì do một người như vậy tạo ra (người đàn ông thậm chí còn có một vài cuốn sách về thiết kế hướng đối tượng theo tên của mình, và vị trí giảng dạy trong khoa CS của một trường đại học lớn ).
Nó dựa trên mô hình 3 tầng, với mỗi tầng là một hệ thống 3 tầng (phải tách rời ...). Trên giao diện giữa mỗi tầng, ở cả hai phía, là một nhà máy sản xuất các đối tượng để truyền dữ liệu sang tầng khác và một nhà máy sản xuất đối tượng để dịch đối tượng đã nhận sang một đối tượng thuộc lớp nhận.
Đối với mỗi nhà máy có một nhà máy trừu tượng (ai biết được, nhà máy có thể phải thay đổi và sau đó bạn không muốn mã cuộc gọi phải thay đổi ...).
Và toàn bộ mớ hỗn độn đó tất nhiên là hoàn toàn không có giấy tờ.

Hệ thống có cơ sở dữ liệu được chuẩn hóa thành dạng bình thường thứ 5 (Tôi không yêu bạn).

Một hệ thống về cơ bản ít hơn một cuốn sổ địa chỉ có thể được sử dụng để ghi nhật ký và theo dõi các tài liệu đến và đi, nó có một cơ sở mã 100 MB trong C ++ và hơn 50 bảng cơ sở dữ liệu. In 500 chữ cái mẫu sẽ mất tới 72 giờ bằng máy in được kết nối trực tiếp với máy tính chạy phần mềm.

Đó là lý do tại sao mọi người chế giễu các mẫu, và cụ thể là mọi người tập trung vào một mẫu cụ thể duy nhất.


40
Một lý do khác là một số mẫu của Gang of Four chỉ là những bản hack thực sự dài dòng cho những thứ có thể được thực hiện một cách tầm thường trong một ngôn ngữ với các chức năng hạng nhất, loại này khiến chúng không thể tránh khỏi các mẫu chống. Các mẫu "Chiến lược", "Quan sát viên", "Nhà máy", "Lệnh" và "Phương thức mẫu" là các bản hack để truyền các hàm làm đối số; "Khách truy cập" là một hack để thực hiện khớp mẫu trên liên kết loại / biến thể / được gắn thẻ. Việc một số người đưa ra nhiều lời quảng cáo về một thứ gì đó tầm thường trong nhiều ngôn ngữ khác là điều khiến mọi người chế giễu C ++, Java và các ngôn ngữ tương tự.
Doval

7
Cảm ơn bạn cho giai thoại này. Bạn cũng có thể giải thích một chút về "các lựa chọn thay thế là gì?" một phần của câu hỏi để chúng ta không trở nên như vậy?
Philipp

1
@Doval Bạn có thể cho tôi biết làm thế nào để bạn trả về giá trị từ lệnh đã thực hiện hoặc được gọi là chiến lược khi bạn chỉ có thể truyền hàm? Và nếu bạn nói đóng cửa, thì hãy nhớ rằng nó hoàn toàn giống với việc tạo một lớp, ngoại trừ rõ ràng hơn.
Euphoric

2
@Euphoric Tôi không thấy vấn đề, bạn có thể giải thích? Trong mọi trường hợp, chúng không hoàn toàn giống nhau. Bạn cũng có thể nói hơn một đối tượng có một trường duy nhất giống hệt như một con trỏ / tham chiếu đến một biến hoặc một số nguyên giống hệt như một enum (thực). Có sự khác biệt trong thực tế: không có ý nghĩa gì khi so sánh các chức năng; Tôi vẫn chưa thấy một cú pháp ngắn gọn cho các lớp ẩn danh; và tất cả các hàm có cùng loại đối số và kiểu trả về có cùng loại (không giống như các lớp / giao diện có tên khác nhau, là các loại khác nhau ngay cả khi chúng giống hệt nhau.)
Doval

2
@Euphoric Đúng. Đó sẽ được coi là phong cách chức năng xấu nếu có cách để hoàn thành công việc mà không có tác dụng phụ. Một cách khác đơn giản là sử dụng loại tổng / liên kết được gắn thẻ để trả về một trong N loại giá trị. Bất kể, hãy cho rằng không có cách thực tế; nó không giống như một lớp học. Đây thực sự là một giao diện (theo nghĩa OOP.) Không khó để xem liệu bạn có nghĩ rằng bất kỳ cặp hàm nào có cùng chữ ký sẽ có thể hoán đổi cho nhau hay không, trong khi hai lớp không bao giờ có thể hoán đổi cho nhau ngay cả khi chúng có cùng nội dung.
Doval

17

Các nhà máy có nhiều ưu điểm cho phép thiết kế ứng dụng thanh lịch trong một số tình huống. Một là bạn có thể đặt các thuộc tính của các đối tượng mà sau này bạn muốn tạo ở một nơi bằng cách tạo một nhà máy, và sau đó bàn giao nhà máy đó. Nhưng thường thì bạn không thực sự cần phải làm điều đó. Trong trường hợp đó, sử dụng Factory chỉ tăng thêm độ phức tạp mà không thực sự mang lại cho bạn bất cứ điều gì. Hãy lấy nhà máy này, ví dụ:

WidgetFactory redWidgetFactory = new ColoredWidgetFactory(COLOR_RED);
Widget widget = redWidgetFactory.create();

Một thay thế cho mẫu Factory là mẫu Builder rất giống nhau. Sự khác biệt chính là các thuộc tính của các đối tượng được tạo bởi Factory được đặt khi Factory được khởi tạo, trong khi Builder được khởi tạo với trạng thái mặc định và tất cả các thuộc tính được đặt sau đó.

WidgetBuilder widgetBuilder = new WidgetBuilder();
widgetBuilder.setColor(COLOR_RED);
Widget widget = widgetBuilder.create();

Nhưng khi áp đảo là vấn đề của bạn, việc thay thế Factory bằng Builder có thể không có nhiều cải tiến.

Tất nhiên, sự thay thế đơn giản nhất cho một trong hai mẫu là tạo các thể hiện đối tượng với một hàm tạo đơn giản với newtoán tử:

Widget widget = new ColoredWidget(COLOR_RED);

Tuy nhiên, các nhà xây dựng có một nhược điểm rất quan trọng trong hầu hết các ngôn ngữ hướng đối tượng: Họ phải trả về một đối tượng của lớp chính xác đó và không thể trả về một kiểu con.

Khi bạn cần chọn loại phụ trong thời gian chạy nhưng không muốn sử dụng để tạo một lớp Builder hoặc Factory hoàn toàn mới cho điều đó, bạn có thể sử dụng phương thức nhà máy thay thế. Đây là một phương thức tĩnh của một lớp trả về một thể hiện mới của lớp đó hoặc một trong các lớp con của nó. Một Nhà máy không duy trì bất kỳ trạng thái nội bộ nào thường có thể được thay thế bằng phương pháp nhà máy như vậy:

 Widget widget = Widget.createColoredWidget(COLOR_RED); // returns an object of class RedColoredWidget

Một tính năng mới trong Java 8 là các tham chiếu phương thức cho phép bạn truyền các phương thức xung quanh, giống như bạn làm với một nhà máy không trạng thái. Thuận tiện, bất cứ điều gì chấp nhận tham chiếu phương thức cũng chấp nhận bất kỳ đối tượng nào thực hiện cùng giao diện chức năng, cũng có thể là Nhà máy chính thức với trạng thái bên trong, vì vậy bạn có thể dễ dàng giới thiệu các nhà máy sau này, khi bạn thấy lý do để làm như vậy.


2
Một nhà máy thường cũng có thể được cấu hình sau khi tạo. Sự khác biệt chính, afaik, đối với một nhà xây dựng là một công cụ xây dựng thường được sử dụng để tạo một cá thể phức tạp, đơn lẻ, trong khi các nhà máy được sử dụng để tạo ra nhiều trường hợp tương tự.
Cephalepad

1
cảm ơn (+1), nhưng câu trả lời không giải thích được các PL khác sẽ giải quyết nó như thế nào, tuy nhiên cảm ơn vì đã chỉ ra các tham chiếu phương thức Java 8.
giác quan

1
Tôi thường nghĩ rằng nó sẽ có ý nghĩa trong một khung hướng đối tượng để giới hạn việc xây dựng đối tượng thực tế ở loại đang được đề cập và có foo = new Bar(23);giá trị tương đương foo = Bar._createInstance(23);. Một đối tượng sẽ có thể truy vấn một cách đáng tin cậy kiểu của chính nó bằng một getRealType()phương thức riêng tư , nhưng các đối tượng sẽ có thể chỉ định một siêu kiểu được trả về bởi các lệnh gọi bên ngoài tới getType(). Mã bên ngoài không nên quan tâm liệu một cuộc gọi new String("A")thực sự trả về một thể hiện của String[trái ngược với ví dụ của SingleCharacterString].
supercat

1
Tôi sử dụng rất nhiều nhà máy phương thức tĩnh khi tôi cần chọn một lớp con dựa trên ngữ cảnh nhưng sự khác biệt sẽ không rõ ràng đối với người gọi. Ví dụ, tôi có một loạt các lớp hình ảnh chứa tất cả draw(OutputStream out)nhưng tạo ra html hơi khác nhau, nhà máy phương thức tĩnh tạo lớp đúng cho tình huống và sau đó người gọi chỉ có thể sử dụng phương thức vẽ.
Michael Storesin

1
@supercat bạn có thể quan tâm đến việc xem xét mục tiêu-c. Có xây dựng các đối tượng bao gồm các cuộc gọi phương thức đơn giản, thường [[SomeClass alloc] init]. Có thể trả về một thể hiện của lớp hoàn toàn khác hoặc một đối tượng khác (chẳng hạn như giá trị được lưu trong bộ nhớ cache)
axelarge

3

Các nhà máy thuộc loại này hay loại khác được tìm thấy trong hầu hết mọi ngôn ngữ hướng đối tượng trong các trường hợp thích hợp. Đôi khi bạn chỉ cần một cách đơn giản để chọn loại đối tượng cần tạo dựa trên một tham số đơn giản như chuỗi.

Một số người đưa nó đi quá xa và cố gắng kiến ​​trúc mã của họ để không bao giờ cần gọi một nhà xây dựng ngoại trừ bên trong một nhà máy. Mọi thứ bắt đầu trở nên lố bịch khi bạn có nhà máy sản xuất.

Điều gây ấn tượng với tôi khi tôi học Scala và tôi không biết Ruby, nhưng tin rằng nó cũng giống như vậy, là ngôn ngữ đủ sức biểu cảm mà các lập trình viên không phải lúc nào cũng cố gắng đẩy công việc "ống nước" sang các tệp cấu hình bên ngoài . Thay vì bị cám dỗ tạo ra các nhà máy sản xuất để kết nối các lớp của bạn với các cấu hình khác nhau, trong Scala, bạn sử dụng các đối tượng có các đặc điểm trộn lẫn. Việc tạo ra các DSL đơn giản bằng ngôn ngữ cho các nhiệm vụ thường được thiết kế quá mức trong Java.

Ngoài ra, như các bình luận và câu trả lời khác đã chỉ ra, việc đóng cửa và các chức năng hạng nhất làm giảm nhu cầu về nhiều mẫu. Vì lý do đó, tôi tin rằng nhiều kiểu chống sẽ bắt đầu biến mất khi C ++ 11 và Java 8 được áp dụng rộng rãi.


cảm ơn (+1). Câu trả lời của bạn giải thích một số nghi ngờ của tôi về cách Scala sẽ tránh nó. Thành thật mà nói, bạn không nghĩ rằng một khi (nếu) Scala trở nên ít phổ biến bằng một nửa Java ở cấp độ doanh nghiệp, các đội quân khung, mẫu, máy chủ ứng dụng phải trả tiền, v.v. sẽ bật lên?
giác quan

Tôi nghĩ rằng nếu Scala vượt qua Java, thì đó là vì mọi người thích "cách Scala" của phần mềm kiến ​​trúc. Điều khiến tôi ngạc nhiên hơn là Java tiếp tục áp dụng nhiều tính năng hơn để thúc đẩy mọi người chuyển sang Scala, như các khái niệm chung và các luồng dữ liệu của Java 5, hy vọng cuối cùng sẽ dẫn đến việc các lập trình viên từ bỏ các mô hình chống lại xuất hiện khi chúng xuất hiện tính năng không có sẵn. Sẽ luôn có những người tuân thủ FactoryFactory, bất kể ngôn ngữ tốt như thế nào. Rõ ràng một số người thích những kiến ​​trúc đó, hoặc chúng sẽ không phổ biến như vậy.
Karl Bielefeldt

2

Nhìn chung, có xu hướng rằng các chương trình Java được thiết kế quá mức khủng khiếp [cần dẫn nguồn]. Có nhiều nhà máy là một trong những triệu chứng phổ biến nhất của kỹ thuật quá mức; đó là lý do tại sao mọi người làm cho vui

Cụ thể, vấn đề mà Java gặp phải với các nhà máy là trong Java a) các hàm tạo không phải là các hàm và b) các hàm không phải là công dân hạng nhất.

Hãy tưởng tượng bạn có thể viết một cái gì đó như thế này (hãy Functionlà một giao diện nổi tiếng của JRE )

// Framework code
public Node buildTree(Function<BranchNode, Node, Node> branchNodeFactory) {
    Node current = nextNode();
    while (hasNextNode()) {
        current = branchNodeFactory.call(current, nextNode());
    }
    return current
}

// MyDataNode.java
public class MyBranchNode implements BranchNode {

    public MyBranchNode(Node left, Node right) { ... }
}

// Client code
Node root = buildTree(MyBranchNode::new);

Xem, không có giao diện nhà máy hoặc các lớp. Nhiều ngôn ngữ động có chức năng hạng nhất. Than ôi, điều này là không thể với Java 7 hoặc sớm hơn (thực hiện tương tự với các nhà máy được để lại như một bài tập cho người đọc).

Vì vậy, giải pháp thay thế không phải là "sử dụng một mô hình khác", mà là "sử dụng ngôn ngữ với mô hình đối tượng tốt hơn" hoặc "không xử lý quá nhiều vấn đề đơn giản".


2
điều này thậm chí không cố gắng trả lời câu hỏi được hỏi: "các lựa chọn thay thế là gì?"
gnat

1
Đọc qua đoạn thứ hai và bạn sẽ đến phần "cách các ngôn ngữ khác làm điều đó". (PS: câu trả lời khác cũng không hiển thị các lựa chọn thay thế)
Cephalepad

4
@gnat Không phải anh ấy gián tiếp nói thay thế là sử dụng các chức năng hạng nhất sao? Nếu thứ bạn muốn là một hộp đen có thể tạo đối tượng, thì các tùy chọn của bạn là nhà máy hoặc chức năng và nhà máy chỉ là một chức năng ngụy trang.
Doval

3
"Trong nhiều ngôn ngữ động" là một chút sai lệch, vì những gì thực sự cần là một ngôn ngữ có chức năng hạng nhất. "Năng động" là trực giao với điều này.
Andres F.

Đúng, nhưng nó là một tài sản thường được tìm thấy trong các ngôn ngữ động. Tôi đã đề cập đến sự cần thiết của chức năng hạng nhất khi bắt đầu câu trả lời của tôi.
Cephalepad
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.