Nhà máy tĩnh so với nhà máy là một đơn


24

Trong một số mã của tôi, tôi có một nhà máy tĩnh tương tự như sau:

public class SomeFactory {

    // Static class
    private SomeFactory() {...}

    public static Foo createFoo() {...}

    public static Foo createFooerFoo() {...}
}

Trong quá trình xem xét mã, nó đã được đề xuất rằng đây phải là một singleton và được tiêm. Vì vậy, nó sẽ trông như thế này:

public class SomeFactory {

    public SomeFactory() {}

    public Foo createFoo() {...}

    public Foo createFooerFoo() {...}
}

Một vài điều cần làm nổi bật:

  • Cả hai nhà máy đều không quốc tịch.
  • Sự khác biệt duy nhất giữa các phương thức là phạm vi của chúng (thể hiện so với tĩnh). Việc thực hiện là như nhau.
  • Foo là một bean không có giao diện.

Các đối số tôi có để đi tĩnh là:

  • Lớp không trạng thái, do đó không cần phải được khởi tạo
  • Có vẻ tự nhiên hơn khi có thể gọi một phương thức tĩnh hơn là phải khởi tạo một nhà máy

Các đối số cho nhà máy là một đơn vị là:

  • Nó tốt để tiêm tất cả mọi thứ
  • Bất chấp sự không quốc tịch của nhà máy, thử nghiệm dễ dàng hơn bằng cách tiêm (dễ giả)
  • Nó nên bị chế giễu khi kiểm tra người tiêu dùng

Tôi có một số vấn đề nghiêm trọng với cách tiếp cận đơn lẻ vì dường như nó gợi ý rằng không có phương pháp nào nên tĩnh. Nó dường như cũng gợi ý rằng các tiện ích như StringUtilsnên được bọc và tiêm, có vẻ ngớ ngẩn. Cuối cùng, điều đó ngụ ý rằng tôi sẽ cần phải chế giễu nhà máy tại một số điểm, điều này có vẻ không đúng. Tôi không thể nghĩ đến khi nào tôi cần chế giễu nhà máy.

Cộng đồng nghĩ gì? Mặc dù tôi không thích cách tiếp cận đơn lẻ, tôi dường như không có một cuộc tranh cãi mạnh mẽ nào chống lại nó.


2
Nhà máy đang được sử dụng bởi một cái gì đó . Để kiểm tra thứ đó một cách cô lập, bạn cần cung cấp cho nó một bản giả của nhà máy của bạn. Nếu bạn không chế nhạo nhà máy của mình thì một lỗi ở đó có thể gây ra lỗi kiểm tra đơn vị khi không nên.
Mike

Vì vậy, bạn đang ủng hộ chống lại các tiện ích tĩnh nói chung, hay chỉ các nhà máy tĩnh?
bstempi

1
Tôi đang ủng hộ họ nói chung. Trong C # chẳng hạn, DateTimeFilecác lớp nổi tiếng là khó kiểm tra vì những lý do chính xác giống nhau. Nếu bạn có một lớp, ví dụ, đặt Createdngày thành DateTime.Nowtrong hàm tạo, làm thế nào để bạn tạo một bài kiểm tra đơn vị với hai trong số các đối tượng này được tạo cách nhau 5 phút? Những năm xa nhau thì sao? Bạn thực sự không thể làm như vậy (không có nhiều công việc).
Mike

2
Nhà máy singleton của bạn không nên có một nhà privatexây dựng và một getInstance()phương pháp? Xin lỗi, không thể chọn nit-pick!
TMN

1
@Mike - Đồng ý - Tôi thường sử dụng lớp DateTimeProvider để trả về DateTime một cách tầm thường. Sau đó, bạn có thể trao đổi nó cho một giả, trả về thời gian xác định trước, trong các thử nghiệm của bạn. Đó là công việc làm thêm khi chuyển nó cho tất cả các nhà xây dựng - vấn đề đau đầu nhất là khi đồng nghiệp không mua 100% và chuyển các tham chiếu rõ ràng vào DateTime. Thoát khỏi sự lười biếng ... :)
Julia Hayward

Câu trả lời:


15

Tại sao bạn lại tách nhà máy của mình khỏi loại đối tượng mà nó tạo ra?

public class Foo {

    // Private constructor
    private Foo(...) {...}

    public static Foo of(...) { return new Foo(...); }
}

Đây là những gì Joshua Bloch mô tả như Mục 1 trên trang 5 của cuốn sách "Java hiệu quả". Java đủ dài mà không cần thêm các lớp và singletons và whatnot. Có một số trường hợp trong đó một lớp nhà máy riêng biệt có ý nghĩa, nhưng chúng rất ít và xa.

Để diễn giải Mục 1 của Joshua Bloch, không giống như các nhà xây dựng, các phương thức nhà máy tĩnh có thể:

  • Có các tên có thể mô tả các loại tạo đối tượng cụ thể (Bloch sử dụng probablePrime (int, int, Random) làm ví dụ)
  • Trả về một đối tượng hiện có (nghĩ: fly weight)
  • Trả về một kiểu con của lớp gốc hoặc một giao diện mà nó thực hiện
  • Giảm mức độ chi tiết (của các tham số loại chỉ định - xem Bloch chẳng hạn)

Nhược điểm:

  • Các lớp không có hàm tạo công khai hoặc được bảo vệ có thể được phân lớp (nhưng bạn có thể sử dụng các hàm tạo được bảo vệ và / hoặc Mục 16: ưu tiên thành phần so với kế thừa)
  • Các phương thức tĩnh của nhà máy không nổi bật so với các phương thức tĩnh khác (sử dụng tên tiêu chuẩn như của () hoặc valueOf ())

Thực sự, bạn nên mang cuốn sách của Bloch đến người đánh giá mã của bạn và xem liệu hai bạn có thể đồng ý với phong cách của Bloch không.


Tôi cũng đã nghĩ về điều này, và tôi nghĩ rằng tôi thích nó. Tôi không nhớ tại sao tôi lại tách chúng ra ngay từ đầu.
bstempi

Nói chung, chúng tôi đồng ý về phong cách của Bloch. Cuối cùng, chúng tôi sẽ chuyển các phương thức nhà máy của chúng tôi vào lớp mà chúng đang bắt đầu. Sự khác biệt duy nhất giữa giải pháp của chúng tôi và của bạn là hàm tạo sẽ vẫn công khai để mọi người vẫn có thể trực tiếp khởi tạo lớp. Cảm ơn câu trả lời của bạn!
bstempi

Nhưng ... thật kinh khủng. Bạn rất có thể muốn một lớp thực hiện IFoo và đã chuyển nó vào. Sử dụng mẫu này, điều đó không còn có thể; bạn phải mã hóa IFoo để có nghĩa là Foo để có được các thể hiện. Nếu bạn có IFooFactory có chứa IFoo tạo (), mã máy khách không cần biết chi tiết triển khai mà Foo cụ thể được chọn là IFoo.
Wilbert

@Wilbert public static IFoo of(...) { return new Foo(...); } Vấn đề là gì? Danh sách Bloch thỏa mãn mối quan tâm của bạn là một trong những lợi thế của thiết kế này (điểm đạn thứ hai ở trên). Tôi không được hiểu bình luận của bạn.
GlenPeterson

Để tạo một thể hiện, thiết kế này mong đợi: Foo.of (...). Tuy nhiên, sự tồn tại của Foo không bao giờ được phơi bày, chỉ nên biết IFoo (vì Foo là một ẩn ý cụ thể của IFoo). Có một phương pháp nhà máy tĩnh trên implant bê tông phá vỡ điều này và dẫn đến sự khớp nối giữa mã máy khách và việc triển khai cụ thể.
Wilbert

5

Ngay trên đỉnh đầu của tôi, đây là một số vấn đề với các thống kê trong Java:

  • phương thức tĩnh không chơi theo "quy tắc" OOP. Bạn không thể buộc một lớp thực hiện các phương thức tĩnh cụ thể (theo như tôi biết). Vì vậy, bạn không thể có nhiều lớp thực hiện cùng một tập các phương thức tĩnh có cùng chữ ký. Vì vậy, bạn bị mắc kẹt với một trong những gì bạn đang làm. Bạn thực sự không thể phân lớp một lớp tĩnh. Vì vậy, không đa hình, vv

  • vì các phương thức không phải là đối tượng, nên các phương thức tĩnh không thể được truyền qua và chúng không thể được sử dụng (mà không thực hiện một tấn mã hóa nồi hơi), ngoại trừ bởi mã được liên kết cứng với chúng - điều này làm cho mã máy khách rất cao ghép đôi. (Công bằng mà nói, đây không chỉ là lỗi của thống kê).

  • lĩnh vực thống kê khá giống với toàn cầu


Bạn đã đề cập:

Tôi có một số vấn đề nghiêm trọng với cách tiếp cận đơn lẻ vì dường như nó gợi ý rằng không có phương pháp nào nên tĩnh. Nó dường như cũng gợi ý rằng các tiện ích như StringUtils nên được bọc và tiêm, điều này có vẻ ngớ ngẩn. Cuối cùng, điều đó ngụ ý rằng tôi sẽ cần phải chế giễu nhà máy tại một số điểm, điều này có vẻ không đúng. Tôi không thể nghĩ đến khi nào tôi cần chế giễu nhà máy.

Điều này làm tôi tò mò muốn hỏi bạn:

  • Theo bạn, lợi thế của phương pháp tĩnh là gì?
  • Bạn có tin rằng nó StringUtilsđược thiết kế và thực hiện chính xác?
  • Tại sao bạn nghĩ rằng bạn sẽ không bao giờ cần phải chế giễu nhà máy?

Này, cảm ơn vì câu trả lời! Theo thứ tự mà bạn đã hỏi: (1) Ưu điểm của phương pháp tĩnh là đường cú pháp và thiếu một trường hợp không cần thiết. Thật tuyệt khi đọc mã có nhập tĩnh và nói một cái gì đó giống như Foo f = createFoo();là có một thể hiện được đặt tên. Bản thân trường hợp không cung cấp tiện ích. (2) Tôi nghĩ vậy. Nó được thiết kế để khắc phục thực tế là nhiều phương thức đó không tồn tại trong Stringlớp. Thay thế một cái gì đó cơ bản như Stringkhông phải là một lựa chọn, vì vậy, utils là cách để đi. (tiếp)
bstempi

Chúng dễ sử dụng và không yêu cầu bạn phải lo lắng về mọi trường hợp. Họ cảm thấy tự nhiên. (3) Bởi vì nhà máy của tôi rất giống với các phương pháp của nhà máy bên trong Integer. Chúng rất đơn giản, có rất ít logic và chỉ có để cung cấp sự tiện lợi (ví dụ: không có lý do OO nào khác để có chúng). Phần chính trong lập luận của tôi là họ không dựa vào trạng thái nào - không có lý do gì để cô lập họ trong các thử nghiệm. Mọi người không chế giễu StringUtilskhi thử nghiệm - tại sao họ cần phải chế giễu nhà máy tiện lợi đơn giản này?
bstempi

3
Họ không chế nhạo StringUtilsvì có một sự đảm bảo hợp lý rằng các phương pháp ở đó không có bất kỳ tác dụng phụ nào.
Robert Harvey

@RobertHarvey Tôi cho rằng tôi đang đưa ra tuyên bố tương tự về nhà máy của mình - nó không sửa đổi bất cứ điều gì. Giống như StringUtilstrả về một số kết quả mà không thay đổi đầu vào, nhà máy của tôi cũng không sửa đổi gì.
bstempi

@bstempi Tôi sẽ tìm một chút phương pháp Socrates ở đó. Tôi đoán tôi hút nó.

0

Tôi nghĩ rằng Ứng dụng bạn đang xây dựng phải khá phức tạp hoặc nếu không thì bạn còn khá sớm trong vòng đời của nó. Kịch bản khác duy nhất tôi có thể nghĩ về nơi bạn sẽ không gặp phải vấn đề với các thống kê của mình là nếu bạn đã quản lý để giới hạn các phần khác trong mã của bạn biết về Nhà máy của bạn ở một hoặc hai nơi.

Hãy tưởng tượng rằng Ứng dụng của bạn phát triển và bây giờ trong một số trường hợp bạn cần tạo một FooBar(lớp con của Foo). Bây giờ bạn cần đi qua tất cả các vị trí trong mã của bạn biết về bạn SomeFactoryvà viết một số loại logic nhìn someConditionvà gọi SomeFactoryhoặc SomeOtherFactory. Trên thực tế, nhìn vào mã ví dụ của bạn, nó còn tệ hơn thế. Bạn dự định chỉ thêm phương thức sau phương thức để tạo các Foos khác nhau và tất cả mã khách hàng của bạn phải kiểm tra một điều kiện trước khi tìm ra Foo nào để thực hiện. Điều đó có nghĩa là tất cả khách hàng cần có kiến ​​thức sâu sắc về cách thức hoạt động của Nhà máy của bạn và tất cả họ cần thay đổi bất cứ khi nào cần một Foo mới (hoặc loại bỏ!).

Bây giờ hãy tưởng tượng nếu bạn vừa tạo một IFooFactorycái mà làm cho một IFoo. Bây giờ, tạo ra một lớp con khác hoặc thậm chí là một triển khai hoàn toàn khác là trò chơi của trẻ em - bạn chỉ cần cung cấp đúng IFooFactory lên chuỗi và sau đó khi khách hàng nhận được nó, nó sẽ gọi gimmeAFoo()và sẽ nhận được foo đúng vì nó có đúng Factory .

Bạn có thể tự hỏi làm thế nào điều này diễn ra trong mã sản xuất. Để cho bạn một ví dụ từ cơ sở mã mà tôi đang làm việc bắt đầu tăng cấp chỉ sau hơn một năm hoàn thành các dự án, tôi có một loạt các Nhà máy được sử dụng để tạo các đối tượng dữ liệu Câu hỏi (chúng giống như Mô hình trình bày ). QuestionFactoryMapVề cơ bản, tôi có một hàm băm để tìm kiếm nhà máy câu hỏi sẽ sử dụng khi nào. Vì vậy, để tạo một Ứng dụng đặt các câu hỏi khác nhau, tôi đặt các Nhà máy khác nhau vào bản đồ.

Các câu hỏi có thể được trình bày theo một trong hai hướng dẫn hoặc bài tập (cả hai đều thực hiện IContentBlock), vì vậy mỗi InstructionsFactoryhoặc ExerciseFactorysẽ nhận được một bản sao của QuestionFactoryMap. Sau đó, các nhà máy Hướng dẫn và Tập thể dục khác nhau được trao cho ContentBlockBuildermột lần nữa duy trì hàm băm để sử dụng khi nào. Và sau đó khi XML được tải vào, ContentBlockBuilder sẽ gọi Nhà máy tập thể dục hoặc hướng dẫn ra khỏi hàm băm và yêu cầu nó createContent(), và sau đó, Nhà máy ủy thác việc xây dựng các câu hỏi cho bất cứ điều gì nó rút ra khỏi Câu hỏi nội bộ dựa trên câu hỏi nào là trong mỗi nút XML so với hàm băm. Thật không may , điều đó là một BaseQuestionthay vì mộtIQuestion, nhưng điều đó chỉ cho thấy rằng ngay cả khi bạn cẩn thận về thiết kế, bạn có thể mắc phải những sai lầm có thể ám ảnh bạn.

Ruột của bạn đang nói với bạn rằng những thống kê này sẽ làm tổn thương bạn, và chắc chắn có rất nhiều tài liệu để hỗ trợ ruột của bạn. Bạn sẽ không bao giờ thua bằng cách sử dụng Dependency Injection mà bạn chỉ sử dụng cho Bài kiểm tra đơn vị của mình (không phải tôi tin rằng nếu bạn xây dựng mã tốt mà bạn sẽ không thấy mình sử dụng nhiều hơn thế), nhưng thống kê có thể phá hủy hoàn toàn khả năng của bạn để nhanh chóng và dễ dàng sửa đổi mã của bạn. Vậy tại sao thậm chí đến đó?


Hãy suy nghĩ một chút về các phương thức xuất xưởng trong Integerlớp - những điều đơn giản như chuyển đổi từ Chuỗi. Đó là những gì Foo. Của tôi Fookhông có nghĩa là được gia hạn (lỗi của tôi vì không nêu rõ điều này), vì vậy tôi không có vấn đề đa hình của bạn. Tôi hiểu giá trị của việc có các nhà máy đa hình và đã sử dụng chúng trong quá khứ ... Tôi chỉ không nghĩ rằng chúng phù hợp ở đây. Bạn có thể tưởng tượng nếu bạn phải khởi tạo một nhà máy để thực hiện các thao tác đơn giản Integerhiện đang được triển khai thông qua các nhà máy tĩnh?
bstempi

Ngoài ra, bạn giả định về ứng dụng là khá tốt. Ứng dụng này khá liên quan. Điều này Foo, tuy nhiên, đơn giản hơn nhiều so với nhiều lớp. Nó chỉ là một POJO đơn giản.
bstempi

Nếu Foo có nghĩa là được mở rộng, thì đó là lý do để Factory trở thành một thể hiện - bạn cung cấp phiên bản chính xác của nhà máy để cung cấp cho bạn Lớp con chính xác thay vì gọi if (this) then {makeThisFoo()} else {makeThatFoo()}tất cả thông qua mã của bạn. Một điều mà tôi nhận thấy về những người có kiến ​​trúc tồi tệ thường xuyên là họ giống như những người bị đau mãn tính. Họ thực sự không biết cảm giác như thế nào khi không đau đớn, vì vậy nỗi đau mà họ gặp phải dường như không tệ lắm.
Amy Blankenship

Ngoài ra, bạn có thể muốn kiểm tra liên kết này về các vấn đề với các phương thức tĩnh (ngay cả các phương pháp Toán học!)
Amy Blankenship

Tôi đã cập nhật câu hỏi. Bạn và một người khác đề cập đến việc mở rộng Foo. Đúng như vậy - tôi không bao giờ tuyên bố nó cuối cùng. Foolà một loại đậu không có nghĩa là được mở rộng.
bstempi
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.