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 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.
Apa yang dipikirkan komunitas? Meskipun saya tidak menyukai pendekatan tunggal, saya tampaknya tidak memiliki argumen yang sangat kuat menentangnya.
sumber
DateTime
danFile
kelas sangat sulit untuk diuji karena alasan yang persis sama. Jika Anda memiliki kelas misalnya yang menetapkanCreated
tanggalDateTime.Now
dalam 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).private
konstruktor dangetInstance()
metode? Maaf, pemetik nit yang tidak dapat diperbaiki!Jawaban:
Mengapa Anda memisahkan pabrik Anda dari tipe objek yang dibuatnya?
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:
Kekurangan:
Sungguh, Anda harus membawa buku Bloch ke peninjau kode Anda dan melihat apakah Anda berdua dapat menyetujui gaya Bloch.
sumber
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.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:
Yang membuat saya penasaran untuk bertanya:
StringUtils
dirancang dan diimplementasikan dengan benar?sumber
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 dalamString
kelas. Mengganti sesuatu yang mendasar sebagaiString
bukan pilihan, jadi utilitas adalah cara untuk pergi. (lanjutan)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 mengejekStringUtils
ketika menguji - mengapa mereka perlu mengejek pabrik sederhana ini?StringUtils
karena ada jaminan yang masuk akal bahwa metode di sana tidak memiliki efek samping.StringUtils
mengembalikan beberapa hasil tanpa mengubah input, pabrik saya juga tidak mengubah apa pun.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 AndaSomeFactory
dan menulis semacam logika yang melihatsomeCondition
dan meneleponSomeFactory
atauSomeOtherFactory
. 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
IFooFactory
yang membuatIFoo
. 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 memanggilgimmeAFoo()
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
QuestionFactoryMap
hash 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 masingInstructionsFactory
- masing atauExerciseFactory
akan mendapatkan salinan dari QuestionFactoryMap. Kemudian, berbagai instruksi dan latihan pabrik diserahkan kepadaContentBlockBuilder
yang lagi memelihara hash yang digunakan saat. Dan kemudian ketika XML dimuat, ContentBlockBuilder mengeluarkan Latihan atau Instruksi Pabrik keluar dari hash dan memintanyacreateContent()
, 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 ituBaseQuestion
bukanIQuestion
, 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?
sumber
Integer
kelas - hal-hal sederhana seperti mengkonversi dari String. Itu yangFoo
dilakukannya. SayaFoo
tidak 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 sederhanaInteger
yang saat ini dipanggang melalui pabrik statis?Foo
, bagaimanapun, adalah jauh lebih sederhana daripada banyak kelas. Ini hanya POJO sederhana.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.Foo
. Memang seharusnya begitu - saya tidak pernah menyatakannya final.Foo
adalah kacang yang tidak dimaksudkan untuk diperpanjang.