Jika Singletons buruk lalu mengapa Service Container bagus?

91

Kita semua tahu betapa buruknya para Lajang karena mereka menyembunyikan ketergantungan dan untuk alasan lain .

Namun dalam kerangka kerja, mungkin ada banyak objek yang perlu dibuat instantiated hanya sekali dan dipanggil dari mana saja (logger, db, dll).

Untuk mengatasi masalah ini saya telah diberitahu untuk menggunakan apa yang disebut "Objects Manager" (atau Service Container seperti symfony) yang secara internal menyimpan setiap referensi ke Services (logger dll).

Tetapi mengapa Penyedia Layanan tidak seburuk Singleton murni?

Penyedia layanan juga menyembunyikan dependensi dan mereka hanya menyelesaikan pembuatan istance pertama. Jadi saya benar-benar berjuang untuk memahami mengapa kita harus menggunakan penyedia layanan daripada lajang.

PS. Saya tahu bahwa untuk tidak menyembunyikan dependensi saya harus menggunakan DI (seperti yang dinyatakan oleh Misko)

Menambahkan

Saya akan menambahkan: Akhir-akhir ini para lajang tidak seburuk itu, pencipta PHPUnit menjelaskannya di sini:

DI + Singleton memecahkan masalah:

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }

        // ...
    }
}
?>

itu cukup pintar meskipun ini tidak menyelesaikan sama sekali setiap masalah.

Selain DI dan Service Container , adakah solusi yang baik untuk mengakses objek helper ini?

dinamis
sumber
2
@ ya Hasil edit Anda membuat asumsi yang salah. Sebastian tidak, dengan cara apa pun, menyarankan bahwa cuplikan kode tersebut mengurangi masalah menggunakan Singleons. Ini hanyalah satu cara untuk membuat kode yang tidak mungkin diuji lebih dapat diuji. Tapi itu masih kode bermasalah. Bahkan, dia secara eksplisit mencatat: "Hanya Karena Anda Bisa, Tidak Berarti Anda Harus". Solusi yang benar masih tetap tidak menggunakan Singletons sama sekali.
Gordon
3
@ ya ikuti prinsip SOLID.
Gordon
19
Saya membantah pernyataan bahwa lajang itu buruk. Mereka bisa disalahgunakan, tapi begitu juga dengan alat apapun . Pisau bedah dapat digunakan untuk menyelamatkan hidup atau mengakhirinya. Gergaji mesin dapat membuka hutan untuk mencegah kebakaran hutan atau dapat memotong sebagian besar lengan Anda jika Anda tidak tahu apa yang Anda lakukan. Belajar untuk menggunakan alat Anda dengan bijak dan jangan memperlakukan nasihat sebagai Injil - dengan cara itu terletak pikiran yang tidak berpikir.
paxdiablo
4
@paxdiablo tetapi mereka yang buruk. Lajang melanggar SRP, OCP dan DIP. Mereka memperkenalkan keadaan global dan hubungan erat ke dalam aplikasi Anda dan akan membuat API Anda berbohong tentang ketergantungannya. Semua ini akan berdampak negatif pada pemeliharaan, keterbacaan, dan kemampuan pengujian kode Anda. Mungkin ada kasus yang jarang terjadi di mana kekurangan ini lebih besar daripada manfaat kecilnya, tetapi saya berpendapat bahwa dalam 99% Anda tidak memerlukan Singleton. Terutama di PHP di mana Singletons hanya unik untuk Request dan sangat mudah untuk mengumpulkan grafik kolaborator dari Builder.
Gordon
5
Tidak, saya rasa tidak. Alat adalah sarana untuk menjalankan suatu fungsi, biasanya dengan membuatnya lebih mudah, meskipun beberapa (emacs?) Memiliki perbedaan yang jarang membuatnya lebih sulit :-) Dalam hal ini, singleton tidak berbeda dengan pohon seimbang atau kompiler . Jika Anda perlu memastikan hanya satu salinan objek, singleton melakukan ini. Apakah itu berhasil dengan baik dapat diperdebatkan tetapi saya tidak percaya Anda dapat membantah bahwa itu tidak melakukannya sama sekali. Dan mungkin ada cara yang lebih baik, seperti gergaji mesin lebih cepat dari gergaji tangan, atau pistol paku vs. palu. Itu tidak membuat gergaji tangan / palu menjadi kurang dari sebuah alat.
paxdiablo

Jawaban:

76

Service Locator adalah salah satu dari dua kejahatan yang bisa dikatakan. Yang "lebih rendah" yang mengarah ke empat perbedaan ini ( setidaknya saya tidak dapat memikirkan yang lain sekarang ):

Prinsip Tanggung Jawab Tunggal

Service Container tidak melanggar Prinsip Tanggung Jawab Tunggal seperti yang dilakukan Singleton. Singletons menggabungkan pembuatan objek dan logika bisnis, sedangkan Service Container bertanggung jawab secara ketat untuk mengelola siklus hidup objek aplikasi Anda. Dalam hal ini Service Container lebih baik.

Kopel

Singletons biasanya di-hardcode ke dalam aplikasi Anda karena panggilan metode statis, yang mengarah ke dependensi yang berpasangan erat dan sulit untuk dipalsukan dalam kode Anda. SL di sisi lain hanyalah satu kelas dan dapat disuntikkan. Jadi, sementara semua kelas Anda akan bergantung padanya, setidaknya itu adalah dependensi yang digabungkan secara longgar. Jadi, kecuali Anda menerapkan ServiceLocator sebagai Singleton itu sendiri, itu agak lebih baik dan juga lebih mudah untuk diuji.

Namun, semua kelas yang menggunakan ServiceLocator sekarang akan bergantung pada ServiceLocator, yang juga merupakan bentuk penggandengan. Ini dapat dikurangi dengan menggunakan antarmuka untuk ServiceLocator sehingga Anda tidak terikat ke implementasi ServiceLocator konkret tetapi kelas Anda akan bergantung pada keberadaan semacam Locator sedangkan tidak menggunakan ServiceLocator sama sekali meningkatkan penggunaan kembali secara dramatis.

Dependensi Tersembunyi

Masalah menyembunyikan ketergantungan sangat banyak muncul. Saat Anda baru saja memasukkan locator ke kelas konsumsi Anda, Anda tidak akan tahu dependensi apa pun. Namun berbeda dengan Singleton, SL biasanya akan membuat instance semua dependensi yang dibutuhkan di balik layar. Jadi, ketika Anda mengambil Layanan, Anda tidak berakhir seperti Misko Hevery dalam contoh CreditCard , misalnya Anda tidak perlu membuat contoh semua ketergantungan dependensi dengan tangan.

Mengambil dependensi dari dalam instance juga melanggar Law of Demeter , yang menyatakan bahwa Anda tidak boleh menggali kolaborator. Sebuah instance hanya boleh berbicara dengan kolaborator langsungnya. Ini masalah dengan Singleton dan ServiceLocator.

Status Global

Masalah Status Global juga agak berkurang karena ketika Anda membuat contoh Service Locator baru antara tes semua contoh yang dibuat sebelumnya akan dihapus juga (kecuali Anda membuat kesalahan dan menyimpannya dalam atribut statis di SL). Itu tidak berlaku untuk kondisi global mana pun di kelas yang dikelola oleh SL, tentu saja.

Juga lihat Fowler di Service Locator vs Dependency Injection untuk diskusi yang lebih mendalam.


Catatan tentang pembaruan Anda dan artikel yang ditautkan oleh Sebastian Bergmann tentang kode pengujian yang menggunakan Singletons : Sebastian sama sekali tidak menyarankan bahwa solusi yang diusulkan membuat penggunaan Singleons tidak terlalu menjadi masalah. Ini hanyalah satu cara untuk membuat kode yang tidak mungkin diuji lebih dapat diuji. Tapi itu masih kode bermasalah. Bahkan, dia secara eksplisit mencatat: "Hanya Karena Anda Bisa, Tidak Berarti Anda Harus".

Gordon
sumber
1
Terutama testabilitas harus ditegakkan di sini. Anda tidak dapat meniru panggilan metode statis. Namun Anda dapat meniru layanan yang disuntikkan melalui konstruktor atau penyetel.
David
44

Pola pencari lokasi merupakan anti-pola. Itu tidak memecahkan masalah mengekspos dependensi (Anda tidak dapat membedakan dari definisi kelas apa dependensinya karena mereka tidak diinjeksi, sebaliknya mereka ditarik keluar dari pencari layanan).

Jadi, pertanyaan Anda adalah: mengapa pencari lokasi baik? Jawaban saya adalah: tidak.

Hindari, hindari, hindari.

jason
sumber
6
Sepertinya Anda tidak tahu apa-apa tentang antarmuka. Kelas hanya menjelaskan antarmuka yang diperlukan dalam tanda tangan konstruktor - dan hanya itu yang perlu dia ketahui. Lulus Service Locator harus mengimplementasikan antarmuka, itu saja. Dan jika IDE akan memeriksa implementasi antarmuka, akan sangat mudah untuk mengontrol perubahan apa pun.
OZ_
4
@ yes123: Orang yang mengatakan itu salah, dan mereka salah karena SL adalah anti-pola. Pertanyaan Anda adalah "mengapa SL bagus?" Jawaban saya adalah: tidak.
jason
5
Saya tidak akan membantah apakah SL adalah pola anit atau bukan, tetapi yang akan saya katakan adalah itu jauh lebih kecil dari kejahatan jika dibandingkan dengan singleton dan global. Anda tidak dapat menguji kelas yang bergantung pada satu tunggal, tetapi Anda pasti dapat menguji kelas yang bergantung pada SL (albiet Anda dapat mengacaukan desain SL ke titik di mana itu tidak berfungsi) ... Jadi itu layak mencatat ...
ircmaxell
3
@Jason Anda perlu meneruskan objek yang mengimplementasikan Interface - dan itu hanya apa yang perlu Anda ketahui. Anda membatasi diri Anda sendiri dengan hanya definisi konstruktor kelas dan ingin menulis di konstruktor semua kelas (bukan antarmuka) - itu ide yang bodoh. Yang Anda butuhkan hanyalah Antarmuka. Anda dapat berhasil menguji kelas ini dengan tiruan, Anda dapat dengan mudah mengubah perilaku tanpa mengubah kode, tidak ada ketergantungan dan penggandengan tambahan - hanya itu (secara umum) yang ingin kami miliki di Injeksi Ketergantungan.
OZ_
2
Tentu, saya hanya akan menggabungkan Database, Logger, Disk, Template, Cache, dan Pengguna ke dalam satu objek "Input", tentunya akan lebih mudah untuk membedakan dependensi mana yang diandalkan oleh objek saya daripada jika saya telah menggunakan container.
Mahn
4

Kontainer layanan menyembunyikan dependensi seperti yang dilakukan pola Singleton. Anda mungkin ingin menyarankan menggunakan wadah injeksi ketergantungan sebagai gantinya, karena ia memiliki semua keuntungan dari wadah layanan namun tidak ada (sejauh yang saya tahu) kerugian yang dimiliki wadah layanan.

Sejauh yang saya pahami, satu-satunya perbedaan antara keduanya adalah bahwa dalam wadah layanan, wadah layanan adalah objek yang disuntikkan (sehingga menyembunyikan ketergantungan), ketika Anda menggunakan DIC, DIC menyuntikkan dependensi yang sesuai untuk Anda. Kelas yang dikelola oleh DIC sama sekali tidak menyadari fakta bahwa ia dikelola oleh DIC, sehingga Anda memiliki lebih sedikit kopling, ketergantungan yang jelas, dan pengujian unit yang menyenangkan.

Ini adalah pertanyaan yang bagus di SO menjelaskan perbedaan keduanya: Apa perbedaan antara pola Injeksi Ketergantungan dan Lokasi Layanan?

rickchristie
sumber
"DIC menyuntikkan dependensi yang sesuai untuk Anda" Bukankah ini juga terjadi dengan Singleton?
dinamis
5
@ yes123 - Jika Anda menggunakan Singleton, Anda tidak akan menyuntikkannya, sering kali Anda hanya mengaksesnya secara global (itulah inti dari Singleton). Saya kira jika Anda mengatakan bahwa jika Anda menyuntikkan Singleton, itu tidak akan menyembunyikan dependensi, tetapi itu semacam mengalahkan tujuan asli dari pola Singleton - Anda akan bertanya pada diri sendiri, jika saya tidak membutuhkan kelas ini untuk diakses secara global, mengapa apakah saya perlu membuatnya menjadi Singleton?
rickchristie
2

Karena Anda dapat dengan mudah mengganti objek di Service Container dengan
1) pewarisan (kelas Object Manager dapat diwariskan dan metode dapat diganti)
2) mengubah konfigurasi (dalam kasus dengan Symfony)

Dan, Singletons buruk bukan hanya karena koplingnya yang tinggi, tetapi karena mereka adalah _ Single _tons. Itu arsitektur yang salah untuk hampir semua jenis objek.

Dengan DI 'murni' (dalam konstruktor) Anda akan membayar harga yang sangat besar - semua objek harus dibuat sebelum diteruskan dalam konstruktor. Ini berarti lebih banyak memori yang digunakan dan lebih sedikit kinerja. Selain itu, tidak selalu objek hanya dapat dibuat dan diteruskan dalam konstruktor - rantai dependensi dapat dibuat ... Bahasa Inggris saya tidak cukup baik untuk membahasnya secara lengkap, baca tentangnya di dokumentasi Symfony.

ONS_
sumber
0

Bagi saya, saya mencoba menghindari konstanta global, lajang karena alasan sederhana, ada beberapa kasus ketika saya mungkin perlu menjalankan API.

Misalnya, saya memiliki front-end dan admin. Di dalam admin, saya ingin mereka dapat masuk sebagai pengguna. Pertimbangkan kode di dalam admin.

$frontend = new Frontend();
$frontend->auth->login($_GET['user']);
$frontend->redirect('/');

Ini dapat membuat koneksi database baru, logger baru dll untuk inisialisasi frontend dan memeriksa apakah pengguna benar-benar ada, valid dll. Ini juga akan menggunakan cookie terpisah dan layanan lokasi yang tepat.

Ide saya tentang singleton adalah - Anda tidak dapat menambahkan objek yang sama di dalam induk dua kali. Contohnya

$logger1=$api->add('Logger');
$logger2=$api->add('Logger');

akan meninggalkan Anda dengan satu instance dan kedua variabel yang mengarah ke sana.

Akhirnya jika Anda ingin menggunakan pengembangan berorientasi objek, maka bekerjalah dengan objek, bukan dengan kelas.

romaninsh
sumber
1
Jadi metode Anda adalah meneruskan $api var di sekitar kerangka Anda? Saya tidak mengerti apa yang Anda maksud. Juga jika panggilan add('Logger')mengembalikan contoh yang sama pada dasarnya Anda memiliki wadah layanan
dinamis
ya itu benar. Saya menyebutnya sebagai "Pengontrol Sistem" dan dimaksudkan untuk meningkatkan fungsionalitas API. Dengan cara yang sama, menambahkan pengontrol "Dapat diaudit" ke model dua kali akan bekerja dengan cara yang persis sama - hanya membuat satu instance dan hanya satu set bidang audit.
romaninsh