Pabrik statis vs pabrik sebagai singleton

24

Dalam beberapa kode saya, saya memiliki pabrik statis yang mirip dengan ini:

public class SomeFactory {

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

    public static Foo createFoo() {...}

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

Selama tinjauan kode, diusulkan bahwa ini haruslah singleton dan disuntikkan. Jadi, akan terlihat seperti ini:

public class SomeFactory {

    public SomeFactory() {}

    public Foo createFoo() {...}

    public Foo createFooerFoo() {...}
}

Beberapa hal yang perlu diperhatikan:

  • Kedua pabrik itu tidak memiliki kewarganegaraan.
  • Satu-satunya perbedaan antara metode adalah cakupannya (contoh vs statis). Implementasinya sama.
  • Foo adalah kacang yang tidak memiliki antarmuka.

Argumen yang saya miliki untuk menjadi statis adalah:

  • Kelas tidak memiliki kewarganegaraan, oleh karena itu tidak perlu dipakai
  • Tampaknya lebih alami untuk dapat memanggil metode statis daripada harus membuat instantiate pabrik

Argumen untuk pabrik sebagai singleton adalah:

  • Bagus untuk menyuntikkan semuanya
  • Meskipun kewarganegaraan pabrik, pengujian lebih mudah dengan injeksi (mudah dipermainkan)
  • Itu harus diejek ketika menguji konsumen

Saya memiliki beberapa masalah serius dengan pendekatan singleton karena tampaknya menyarankan bahwa tidak ada metode yang seharusnya statis. Tampaknya juga menyarankan bahwa utilitas seperti StringUtilsharus dibungkus dan disuntikkan, yang tampaknya konyol. Terakhir, ini menyiratkan bahwa saya perlu mengejek pabrik pada titik tertentu, yang sepertinya tidak benar. Saya tidak bisa memikirkan kapan saya harus mengejek pabrik.

Apa yang dipikirkan komunitas? Meskipun saya tidak menyukai pendekatan tunggal, saya tampaknya tidak memiliki argumen yang sangat kuat menentangnya.

bstempi
sumber
2
Pabrik sedang digunakan oleh sesuatu . Untuk menguji benda itu secara terpisah, Anda harus memberinya tiruan dari pabrik Anda. Jika Anda tidak mengejek pabrik Anda maka bug di sana dapat menyebabkan unit test gagal padahal seharusnya tidak.
Mike
Jadi, apakah Anda menganjurkan menentang utilitas statis secara umum, atau hanya pabrik statis?
bstempi
1
Saya mendukung mereka secara umum. Dalam C # misalnya, DateTimedan Filekelas sangat sulit untuk diuji karena alasan yang persis sama. Jika Anda memiliki kelas misalnya yang menetapkan Createdtanggal DateTime.Nowdalam konstruktor, bagaimana Anda membuat tes unit dengan dua objek yang dibuat 5 menit terpisah? Bagaimana dengan tahun terpisah? Anda benar-benar tidak dapat melakukannya (tanpa banyak pekerjaan).
Mike
2
Bukankah seharusnya pabrik tunggal Anda memiliki privatekonstruktor dan getInstance()metode? Maaf, pemetik nit yang tidak dapat diperbaiki!
TMN
1
@ Mike - Setuju - Saya sering menggunakan kelas DateTimeProvider yang dengan sepele mengembalikan DateTime.Now. Anda kemudian dapat menukarnya dengan tiruan yang mengembalikan waktu yang ditentukan sebelumnya, dalam pengujian Anda. Ini adalah pekerjaan ekstra untuk menyerahkannya ke semua konstruktor - sakit kepala terbesar adalah ketika rekan kerja tidak menerima 100% dan memasukkan referensi eksplisit ke DateTime. Sekarang karena kemalasan ... :)
Julia Hayward

Jawaban:

15

Mengapa Anda memisahkan pabrik Anda dari tipe objek yang dibuatnya?

public class Foo {

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

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

Inilah yang Joshua Bloch gambarkan sebagai Item 1 di halaman 5 bukunya, "Java Efektif." Java cukup verbose tanpa menambahkan kelas dan lajang tambahan dan yang lainnya. Ada beberapa contoh di mana kelas pabrik yang terpisah masuk akal, tetapi mereka sedikit dan jarang.

Mengutip Joshua Bloch's Item 1, tidak seperti konstruktor, metode pabrik statis dapat:

  • Memiliki nama yang dapat menggambarkan jenis spesifik dari objek yang dibuat (Bloch menggunakan probablePrime (int, int, Random) sebagai contoh)
  • Kembalikan objek yang ada (pikirkan: kelas terbang)
  • Kembalikan sub-jenis kelas asli atau antarmuka yang diterapkannya
  • Kurangi verbositas (dari menentukan parameter tipe - lihat Bloch misalnya)

Kekurangan:

  • Kelas tanpa konstruktor publik atau terlindungi tidak dapat disubklasifikasikan (tetapi Anda dapat menggunakan konstruktor yang dilindungi dan / atau Butir 16: mendukung komposisi daripada warisan)
  • Metode pabrik statis tidak menonjol dari metode statis lainnya (gunakan nama standar seperti dari () atau valueOf ())

Sungguh, Anda harus membawa buku Bloch ke peninjau kode Anda dan melihat apakah Anda berdua dapat menyetujui gaya Bloch.

GlenPeterson
sumber
Saya sudah memikirkan hal ini juga, dan saya rasa saya menyukainya. Saya tidak ingat mengapa saya memisahkan mereka sejak awal.
bstempi
Secara umum, kami setuju dengan gaya Bloch. Pada akhirnya, kita akan memindahkan metode pabrik kita ke kelas yang mereka instantiating. Satu-satunya perbedaan antara solusi kami dan solusi Anda adalah bahwa konstruktor akan tetap publik sehingga orang-orang masih dapat langsung membuat instance kelas. Terima kasih atas jawaban anda!
bstempi
Tapi ... itu mengerikan. Anda kemungkinan besar menginginkan kelas yang mengimplementasikan IFoo, dan telah menerimanya. Menggunakan pola ini, itu tidak mungkin lagi; Anda harus hardcode IFoo berarti Foo untuk mendapatkan instance. Jika Anda memiliki IFooFactory yang berisi IFoo create (), kode klien tidak perlu mengetahui detail implementasi yang Foo konkret dipilih sebagai IFoo.
Wilbert
@ Willbert public static IFoo of(...) { return new Foo(...); } Apa masalahnya? Daftar Bloch memuaskan kekhawatiran Anda sebagai salah satu keunggulan desain ini (poin kedua di atas). Saya tidak boleh memahami komentar Anda.
GlenPeterson
Untuk membuat contoh, desain ini mengharapkan: Foo.of (...). Namun, keberadaan Foo tidak boleh diekspos, hanya IFoo yang harus diketahui (karena Foo hanyalah alat konkret dari IFoo). Memiliki metode pabrik statis pada imp beton merusak ini dan mengarah ke sambungan antara kode klien dan implementasi konkret.
Wilbert
5

Tepat di atas kepala saya, berikut adalah beberapa masalah dengan statika di Jawa:

  • metode statis tidak dimainkan oleh "aturan" OOP. Anda tidak dapat memaksa kelas untuk mengimplementasikan metode statis tertentu (sejauh yang saya tahu). Jadi Anda tidak dapat memiliki beberapa kelas yang menerapkan metode statis yang sama dengan tanda tangan yang sama. Jadi Anda terjebak dengan salah satu dari apa pun yang sedang Anda lakukan. Anda juga tidak bisa membuat subkelas kelas statis. Jadi tidak ada polimorfisme, dll.

  • karena metode bukan objek, metode statis tidak dapat diedarkan dan mereka tidak dapat digunakan (tanpa melakukan banyak pengkodean boilerplate), kecuali dengan kode yang sulit dikaitkan dengan mereka - yang membuat kode klien sangat digabungkan. (Agar adil, ini bukan semata-mata kesalahan statika).

  • bidang statika sangat mirip dengan global


Anda menyebutkan:

Saya memiliki beberapa masalah serius dengan pendekatan singleton karena tampaknya menyarankan bahwa tidak ada metode yang seharusnya statis. Tampaknya juga menyarankan bahwa utilitas seperti StringUtils harus dibungkus dan disuntikkan, yang tampaknya konyol. Terakhir, ini menyiratkan bahwa saya perlu mengejek pabrik pada titik tertentu, yang sepertinya tidak benar. Saya tidak bisa memikirkan kapan saya harus mengejek pabrik.

Yang membuat saya penasaran untuk bertanya:

  • apa, menurut Anda, kelebihan metode statis?
  • apakah Anda yakin itu StringUtilsdirancang dan diimplementasikan dengan benar?
  • mengapa Anda pikir Anda tidak perlu mengejek pabrik?

sumber
Hei, terima kasih atas jawabannya! Dalam urutan yang Anda tanyakan: (1) Keuntungan metode statis adalah gula sintaksis dan kurangnya instance yang tidak perlu. Sangat menyenangkan untuk membaca kode yang memiliki impor statis dan untuk mengatakan sesuatu seperti Foo f = createFoo();daripada memiliki contoh bernama. Mesin virtual itu sendiri tidak menyediakan utilitas. (2) Saya kira begitu. Itu dirancang untuk mengatasi fakta bahwa banyak dari metode tersebut tidak ada di dalam Stringkelas. Mengganti sesuatu yang mendasar sebagai Stringbukan pilihan, jadi utilitas adalah cara untuk pergi. (lanjutan)
bstempi
Mereka mudah digunakan dan tidak mengharuskan Anda cary di sekitar setiap contoh. Mereka merasa alami. (3) Karena pabrik saya sangat mirip dengan metode pabrik di dalamnya Integer. Mereka sangat sederhana, memiliki sedikit logika, dan apakah hanya ada untuk memberikan kenyamanan (misalnya, tidak ada alasan OO lain untuk memilikinya). Bagian utama dari argumen saya adalah mereka tidak bergantung pada kondisi - tidak ada alasan untuk mengisolasi mereka selama pengujian. Orang-orang tidak mengejek StringUtilsketika menguji - mengapa mereka perlu mengejek pabrik sederhana ini?
bstempi
3
Mereka tidak mengejek StringUtilskarena ada jaminan yang masuk akal bahwa metode di sana tidak memiliki efek samping.
Robert Harvey
@ RobertTarvey Saya kira saya membuat klaim yang sama tentang pabrik saya - tidak mengubah apa pun. Sama seperti StringUtilsmengembalikan beberapa hasil tanpa mengubah input, pabrik saya juga tidak mengubah apa pun.
bstempi
@ bstempi saya akan sedikit metode Sokrates di sana. Kurasa aku payah.
0

Saya pikir Aplikasi yang Anda buat harusnya tidak rumit, atau Anda masih relatif awal dalam siklus hidupnya. Satu-satunya skenario lain yang dapat saya pikirkan di mana Anda tidak akan mengalami masalah dengan statika Anda adalah jika Anda telah berhasil membatasi bagian lain dari kode Anda yang tahu tentang Pabrik Anda ke satu atau dua tempat.

Bayangkan bahwa Aplikasi Anda berkembang dan sekarang dalam beberapa kasus Anda perlu menghasilkan FooBar(subkelas Foo). Sekarang Anda harus melalui semua tempat dalam kode Anda yang tahu tentang Anda SomeFactorydan menulis semacam logika yang melihat someConditiondan menelepon SomeFactoryatau SomeOtherFactory. Sebenarnya, melihat kode contoh Anda, ini lebih buruk dari itu. Anda berencana untuk hanya menambahkan metode demi metode untuk membuat Foo yang berbeda dan semua kode klien Anda harus memeriksa kondisi sebelum mencari tahu Foo mana yang harus dibuat. Yang berarti semua klien harus memiliki pengetahuan yang mendalam tentang cara kerja Pabrik Anda dan mereka semua harus berubah setiap kali Foo baru diperlukan (atau dihapus!).

Sekarang bayangkan jika Anda baru saja membuat IFooFactoryyang membuat IFoo. Sekarang, menghasilkan subclass yang berbeda atau bahkan implementasi yang sama sekali berbeda adalah permainan anak-anak - Anda cukup memberi makan di IFooFactory yang lebih tinggi, dan kemudian ketika klien mendapatkannya, ia memanggil gimmeAFoo()dan akan mendapatkan foo yang tepat karena mendapat Pabrik yang tepat .

Anda mungkin bertanya-tanya bagaimana ini terjadi dalam kode produksi. Untuk memberi Anda satu contoh dari basis kode yang saya kerjakan yang mulai meratakan setelah lebih dari setahun proyek cranking, saya memiliki banyak Pabrik yang digunakan untuk membuat objek data Pertanyaan (mereka seperti Presentation Models ). Saya memiliki QuestionFactoryMaphash yang pada dasarnya untuk mencari tahu pabrik pertanyaan mana yang harus digunakan kapan. Jadi untuk membuat Aplikasi yang mengajukan pertanyaan berbeda, saya memasukkan Pabrik yang berbeda ke dalam peta.

Pertanyaan dapat disajikan dalam Instruksi atau Latihan (yang keduanya diimplementasikan IContentBlock), sehingga masing InstructionsFactory- masing atau ExerciseFactoryakan mendapatkan salinan dari QuestionFactoryMap. Kemudian, berbagai instruksi dan latihan pabrik diserahkan kepada ContentBlockBuilderyang lagi memelihara hash yang digunakan saat. Dan kemudian ketika XML dimuat, ContentBlockBuilder mengeluarkan Latihan atau Instruksi Pabrik keluar dari hash dan memintanya createContent(), dan kemudian Pabrik mendelegasikan pembangunan pertanyaan untuk apa pun yang menarik keluar dari internal QuestionFactoryMap berdasarkan pada apa ada di setiap simpul XML vs. hash. Sayangnya , hal itu BaseQuestionbukanIQuestion, tapi itu hanya menunjukkan bahwa meskipun Anda berhati-hati dengan desain, Anda dapat membuat kesalahan yang dapat menghantui Anda.

Naluri Anda memberi tahu Anda bahwa statika ini akan menyakiti Anda, dan tentu saja ada banyak hal dalam literatur untuk mendukung usus Anda. Anda tidak akan pernah kehilangan dengan memiliki Injeksi Ketergantungan yang akhirnya Anda gunakan hanya untuk Tes Unit Anda (bukan bahwa saya percaya bahwa jika Anda membuat kode yang baik Anda tidak akan menemukan diri Anda menggunakannya lebih dari itu), tetapi statika dapat sepenuhnya menghancurkan kemampuan Anda untuk dengan cepat dan mudah memodifikasi kode Anda. Jadi mengapa bahkan pergi ke sana?

Amy Blankenship
sumber
Pikirkan sejenak metode pabrik di Integerkelas - hal-hal sederhana seperti mengkonversi dari String. Itu yang Foodilakukannya. Saya Footidak dimaksudkan untuk diperpanjang (kesalahan saya karena tidak menyatakan ini), jadi saya tidak memiliki masalah polimorfik Anda. Saya mengerti nilai memiliki pabrik polimorfik dan telah menggunakannya di masa lalu ... Saya hanya tidak berpikir mereka cocok di sini. Bisakah Anda bayangkan jika Anda harus membuat instantiate sebuah pabrik untuk melakukan operasi sederhana Integeryang saat ini dipanggang melalui pabrik statis?
bstempi
Juga, asumsi Anda tentang aplikasi ini cukup buruk. Aplikasi ini cukup terlibat. Ini Foo, bagaimanapun, adalah jauh lebih sederhana daripada banyak kelas. Ini hanya POJO sederhana.
bstempi
Jika Foo dimaksudkan untuk diperpanjang, maka di sana ada alasan bagi Pabrik untuk menjadi contoh - Anda memberikan contoh pabrik yang benar untuk memberi Anda Subclass yang benar daripada memanggil if (this) then {makeThisFoo()} else {makeThatFoo()}semua melalui kode Anda. Satu hal yang saya perhatikan tentang orang-orang dengan arsitektur buruk kronis adalah mereka seperti orang-orang yang menderita sakit kronis. Mereka tidak benar-benar tahu bagaimana rasanya tidak sakit, sehingga rasa sakit yang mereka alami tampaknya tidak terlalu buruk.
Amy Blankenship
Juga, Anda mungkin ingin memeriksa tautan ini tentang masalah dengan metode statis (bahkan yang matematika!)
Amy Blankenship
Saya telah memperbarui pertanyaan. Anda dan satu orang lainnya disebutkan memperpanjang Foo. Memang seharusnya begitu - saya tidak pernah menyatakannya final. Fooadalah kacang yang tidak dimaksudkan untuk diperpanjang.
bstempi