Perhatikan dua contoh ini:
Melewati objek ke konstruktor
class ExampleA
{
private $config;
public function __construct($config)
{
$this->config = $config;
}
}
$config = new Config;
$exampleA = new ExampleA($config);
Instantiating kelas
class ExampleB
{
private $config;
public function __construct()
{
$this->config = new Config;
}
}
$exampleA = new ExampleA();
Mana cara yang benar untuk menangani menambahkan objek sebagai properti? Kapan saya harus menggunakan yang satu? Apakah pengujian unit memengaruhi apa yang harus saya gunakan?
Jawaban:
Saya pikir yang pertama akan memberi Anda kemampuan untuk membuat
config
objek di tempat lain dan meneruskannyaExampleA
. Jika Anda memerlukan Ketergantungan Injeksi, ini bisa menjadi hal yang baik karena Anda dapat memastikan bahwa semua niat berbagi objek yang sama.Di sisi lain, mungkin Anda
ExampleA
memerlukan objek baru dan bersihconfig
sehingga mungkin ada kasus di mana contoh kedua lebih tepat, seperti kasus di mana setiap contoh berpotensi memiliki konfigurasi yang berbeda.sumber
Jangan lupa tentang testabilitas!
Biasanya jika perilaku
Example
kelas tergantung pada konfigurasi yang Anda inginkan untuk dapat mengujinya tanpa menyimpan / mengubah keadaan internal kelas contoh (yaitu Anda ingin memiliki tes sederhana untuk jalur bahagia dan untuk konfigurasi yang salah tanpa memodifikasi properti / memeberExample
kelas).Karena itu saya akan pergi dengan opsi pertama
ExampleA
sumber
Jika objek memiliki tanggung jawab untuk mengelola masa pakai dependensi, maka boleh saja untuk membuat objek di konstruktor (dan membuangnya di destruktor). *
Jika objek tidak bertanggung jawab untuk mengelola masa ketergantungan, objek harus diteruskan ke konstruktor dan dikelola dari luar (misalnya oleh wadah IoC).
Dalam hal ini saya tidak berpikir ClassA Anda harus bertanggung jawab untuk membuat $ config, kecuali jika itu juga bertanggung jawab untuk membuangnya, atau jika konfigurasi tersebut unik untuk setiap instance ClassA.
* Untuk membantu dalam testabilitas, konstruktor dapat mengambil referensi ke kelas pabrik / metode untuk membangun ketergantungan pada konstruktornya, meningkatkan kohesi dan testabilitas.
sumber
Saya telah melakukan diskusi yang sama persis dengan tim arsitektur kami baru-baru ini dan ada beberapa alasan halus untuk melakukannya. Sebagian besar tergantung pada Injeksi Ketergantungan (seperti yang telah dicatat orang lain) dan apakah Anda benar-benar memiliki kendali atas penciptaan objek yang Anda baru di konstruktor.
Dalam contoh Anda, bagaimana jika kelas Config Anda:
a) memiliki alokasi non-sepele, seperti berasal dari kumpulan atau metode pabrik.
b) bisa gagal dialokasikan. Meneruskannya ke konstruktor dengan rapi menghindari masalah ini.
c) sebenarnya adalah subclass dari Config.
Melewati objek ke konstruktor memberikan fleksibilitas paling.
sumber
Jawaban di bawah ini salah, tetapi saya akan menyimpannya agar orang lain dapat belajar darinya (lihat di bawah)
Di
ExampleA
, Anda dapat menggunakanConfig
contoh yang sama di beberapa kelas. Namun, jika harus ada hanya satuConfig
instance dalam seluruh aplikasi, pertimbangkan menerapkan pola Singleton padaConfig
untuk menghindari memiliki beberapa instanceConfig
. Dan jika AndaConfig
seorang Singleton, Anda bisa melakukan yang berikut:Di
ExampleB
sisi lain, Anda akan selalu mendapatkan instance terpisahConfig
untuk setiap instanceExampleB
.Versi mana yang harus Anda terapkan benar-benar tergantung pada bagaimana aplikasi akan menangani instance dari
Config
:ExampleX
harus memiliki instance terpisahConfig
, ikutiExampleB
;ExampleX
akan membagikan satu (dan hanya satu) instance dariConfig
, gunakanExampleA with Config Singleton
;ExampleX
dapat menggunakan contoh berbedaConfig
, tetap denganExampleA
.Mengapa
Config
menjadi Singleton salah:Saya harus mengakui bahwa saya baru belajar tentang pola Singleton kemarin (membaca buku Kepala Pertama pola desain). Secara naif saya pergi dan menerapkannya untuk contoh ini, tetapi seperti yang telah ditunjukkan banyak orang, satu cara yang lain (beberapa lebih samar dan hanya mengatakan "Anda salah!"), Ini bukan ide yang baik. Jadi, untuk mencegah orang lain melakukan kesalahan yang sama seperti yang baru saja saya lakukan, berikut ini ringkasan mengapa pola Singleton bisa berbahaya (berdasarkan komentar dan apa yang saya temukan di Google):
Jika
ExampleA
mengambil referensi sendiri keConfig
instance, kelas akan digabungkan secara ketat. Tidak akan ada caraExampleA
untuk menggunakan versi yang berbeda dariConfig
(misalnya beberapa subclass). Ini mengerikan jika Anda ingin mengujiExampleA
menggunakan contoh mock-upConfig
karena tidak ada cara untuk menyediakannyaExampleA
.Premis akan ada satu, dan hanya satu, contoh dari
Config
mungkin berlaku sekarang , tetapi Anda tidak selalu dapat memastikan bahwa hal yang sama akan berlaku di masa depan . Jika pada suatu saat nanti ternyata beberapa contohConfig
akan diinginkan, tidak ada cara untuk mencapai ini tanpa menulis ulang kode.Meskipun instance satu-dan-hanya-satu
Config
mungkin benar untuk selamanya, itu bisa terjadi bahwa Anda ingin dapat menggunakan beberapa subclass dariConfig
(sementara masih hanya memiliki satu instance). Tetapi, karena kode secara langsung mendapatkan instance viagetInstance()
dariConfig
, yang merupakanstatic
metode, tidak ada cara untuk mendapatkan subclass. Sekali lagi, kode harus ditulis ulang.Fakta bahwa
ExampleA
penggunaanConfig
akan disembunyikan, setidaknya ketika hanya melihat APIExampleA
. Ini mungkin atau mungkin bukan hal yang buruk, tetapi secara pribadi saya merasa ini terasa tidak menguntungkan; misalnya, ketika mempertahankan, tidak ada cara sederhana untuk mengetahui kelas mana yang akan terpengaruh oleh perubahanConfig
tanpa melihat ke dalam implementasi setiap kelas lainnya.Bahkan jika fakta yang
ExampleA
menggunakan SingletonConfig
bukanlah masalah, itu mungkin masih menjadi masalah dari sudut pandang pengujian. Benda-benda singleton akan membawa keadaan yang akan bertahan sampai penghentian aplikasi. Ini bisa menjadi masalah ketika menjalankan tes unit karena Anda ingin satu tes diisolasi dari yang lain (yaitu bahwa menjalankan satu tes seharusnya tidak mempengaruhi hasil yang lain). Untuk memperbaikinya, objek Singleton harus dihancurkan antara setiap uji coba (berpotensi harus me-restart seluruh aplikasi), yang mungkin memakan waktu (belum lagi membosankan dan menjengkelkan).Setelah mengatakan ini, saya senang saya membuat kesalahan ini di sini dan tidak dalam penerapan aplikasi nyata. Bahkan, saya sebenarnya sedang mempertimbangkan untuk menulis ulang kode terbaru saya untuk menggunakan pola Singleton untuk beberapa kelas. Meskipun saya dapat dengan mudah mengembalikan perubahan (semuanya disimpan dalam SVN, tentu saja), saya masih akan membuang waktu untuk melakukannya.
sumber
ExampleA
danConfig
- yang bukan hal yang baik.Hal paling sederhana untuk dilakukan adalah beberapa
ExampleA
untukConfig
. Anda harus melakukan hal yang paling sederhana, kecuali ada alasan kuat untuk melakukan sesuatu yang lebih kompleks.Salah satu alasan untuk decoupling
ExampleA
danConfig
akan meningkatkan testability dariExampleA
. Kopling langsung akan menurunkan testabilitasExampleA
jikaConfig
memiliki metode yang lambat, kompleks, atau berkembang dengan cepat. Untuk pengujian, metode lambat jika berjalan lebih dari beberapa mikrodetik. Jika semua metodeConfig
yang sederhana dan cepat, maka saya akan mengambil pendekatan yang sederhana dan langsung beberapaExampleA
untukConfig
.sumber
Contoh pertama Anda adalah contoh dari pola Injeksi Ketergantungan. Kelas dengan dependensi eksternal diberikan dependensi oleh konstruktor, setter, dll.
Pendekatan ini menghasilkan kode yang digabungkan secara longgar. Kebanyakan orang berpikir bahwa kopling longgar adalah hal yang baik, karena Anda dapat dengan mudah mengganti konfigurasi dalam kasus-kasus di mana satu contoh tertentu dari suatu objek perlu dikonfigurasi secara berbeda dari yang lain, Anda dapat mengirimkan objek konfigurasi tiruan untuk pengujian, dan sebagainya di.
Pendekatan kedua lebih dekat dengan pola pencipta GRASP. Dalam hal ini, objek membuat dependensinya sendiri. Ini menghasilkan kode yang digabungkan secara ketat, ini dapat membatasi fleksibilitas kelas dan membuatnya lebih sulit untuk diuji. Jika Anda memerlukan satu instance kelas untuk memiliki ketergantungan yang berbeda dari yang lain, satu-satunya pilihan Anda adalah dengan subkelasnya.
Namun, ini bisa menjadi pola yang sesuai, dalam kasus di mana masa hidup objek bergantung ditentukan oleh masa pakai objek dependen, dan di mana objek dependen tidak digunakan di mana pun di luar objek yang bergantung padanya. Biasanya saya menyarankan DI untuk posisi default, tetapi Anda tidak harus mengesampingkan pendekatan lain sepenuhnya selama Anda mengetahui konsekuensinya.
sumber
Jika kelas Anda tidak mengekspos
$config
ke kelas eksternal, maka saya akan membuatnya di dalam konstruktor. Dengan cara ini, Anda menjaga keadaan internal Anda tetap pribadi.Jika persyaratan
$config
memerlukan keadaan internal sendiri untuk ditetapkan dengan benar (misalnya, membutuhkan koneksi database atau beberapa bidang internal diinisialisasi) sebelum digunakan, maka masuk akal untuk menunda inisialisasi ke beberapa kode eksternal (mungkin kelas pabrik) dan menyuntikkannya ke dalam konstruktor. Atau, seperti yang telah ditunjukkan orang lain, jika perlu dibagikan di antara objek-objek lain.sumber
Contoh A dipisahkan dari kelas beton Config yang baik , asalkan objek diterima jika bukan tipe Config tetapi jenis superclass abstrak Config.
Contoh B sangat digabungkan ke kelas beton Config yang buruk .
Instantiasi objek menciptakan kopling yang kuat antara kelas. Itu harus dilakukan di kelas Pabrik.
sumber