Ringkasan
Tujuan artikel ini adalah upaya untuk melihat, dari sudut pandang yang berbeda, pada deskripsi sistem distribusi peristiwa.
Pada saat penulisan ini, sebagian besar kerangka kerja php terkemuka menerapkan sistem acara berdasarkan pada deskripsi suatu EventObject .
Ini telah menjadi standar dalam php, yang baru-baru ini dikonfirmasi oleh adopsi standar psr / event-dispatcher .
Tetapi ternyata deskripsi objek acara tidak banyak membantu dalam mengembangkan pendengar. Untuk detail di bawah kucing.
Apa masalahnya?
Mari kita lihat peran dan tujuan dari mereka yang menggunakan EventObject dalam pengembangan.
Pengembang (A) yang menetapkan kemampuan untuk menyuntikkan instruksi pihak ketiga ke dalam prosesnya dengan membuat acara.
Pengembang menjelaskan EventObject atau tanda tangannya melalui antarmuka.
Saat mendeskripsikan EventObject, tujuan pengembang adalah untuk memberikan pengembang lain deskripsi objek data, dan dalam beberapa kasus penggunaan, untuk menggambarkan mekanisme interaksi dengan utas utama melalui objek ini.
Pengembang (B) yang menggambarkan "pendengar".
Pengembang berlangganan acara yang ditentukan. Dalam kebanyakan kasus, deskripsi pendengar harus memenuhi tipe yang dapat dipanggil .
Pada saat yang sama, pengembang tidak malu menyebutkan kelas atau metode pendengar. Tetapi ada pembatasan lebih lanjut oleh konvensi bahwa pawang menerima EventObject sebagai argumen.
Ketika psr / event-dispatcher diadopsi oleh kelompok kerja, banyak pilihan untuk menggunakan sistem distribusi acara dianalisis.
Standar psr menyebutkan kasus penggunaan berikut:
- pemberitahuan satu arah - "Saya melakukan sesuatu jika Anda tertarik"
- perbaikan objek - "Ini ada satu hal, tolong ubah sebelum saya melakukan sesuatu dengannya"
- koleksi - "Beri aku semua barangmu sehingga aku bisa melakukan sesuatu dengan daftar ini"
- rantai alternatif - “Inilah masalahnya, yang pertama dari Anda untuk mengatasinya, lakukan, lalu berhenti”
Pada saat yang sama, kelompok kerja mengajukan banyak pertanyaan untuk penggunaan yang sama dari sistem distribusi acara, terkait dengan fakta bahwa masing-masing kasus penggunaan memiliki "ketidakpastian" yang tergantung pada implementasi objek Dispatcher .
Dalam peran yang dijelaskan di atas untuk pengembang (B), tidak ada cara yang mudah dan mudah dibaca untuk memahami opsi mana dari penggunaan sistem acara yang dipilih oleh pengembang (A). Pengembang akan selalu harus melihat ke dalam kode deskripsi tidak hanya dari EventObject , tetapi juga dalam kode tempat acara ini dihasilkan.
Akibatnya, tanda tangan adalah deskripsi objek acara, yang dirancang untuk memudahkan pekerjaan pengembang (B) .Pekerjaan ini tidak berjalan dengan baik.
Masalah lain adalah tidak selalu dibenarkan adanya objek yang terpisah, yang juga menjelaskan entitas yang sudah dijelaskan dalam sistem.
namespace MyApp\Customer\Events; use MyApp\Customer\CustomerNameInterface; use MyFramevork\Event\SomeEventInterface; class CustomerNameChangedEvent implements SomeEventInterface { public function getCustomerId(): int; public function getCustomerName(): CustomerNameInterface; }
Dalam contoh di atas, objek CustomerNameInterface telah dijelaskan dalam sistem.
Ini mengingatkan pada pengenalan konsep baru yang berlebihan. Lagi pula, ketika kita perlu menerapkan metode, misalnya, menulis ke log perubahan nama klien, kita tidak menggabungkan argumen metode menjadi entitas yang terpisah, kami menggunakan deskripsi standar dari metode formulir:
function writeToLogCustomerNameChange( int $customerId, CustomerNameInterface $customerName ) {
Akibatnya, kami melihat masalah berikut:
- tanda tangan kode pendengar yang buruk
- Ketidakpastian dispatcher
- tipe ketidakpastian kembali
- pengenalan banyak entitas tambahan seperti SomeEventObject
Mari kita melihatnya dari perspektif yang berbeda
Jika salah satu masalahnya adalah deskripsi pendengar yang buruk, mari kita lihat sistem distribusi acara bukan dari deskripsi objek acara, tetapi dari deskripsi pendengar.
Pengembang (A) menjelaskan bagaimana pendengar harus dijelaskan.
namespace MyApp\Customer\Events; interface CustomerNameChangedListener { public function onCustomerNameChange( int $customerId, CustomerNameInterface $customerName ); }
Pengembang luar biasa (A) mampu menyampaikan deskripsi pendengar dan data yang dikirimkan kepada pengembang pihak ketiga.
Saat menulis pendengar, pengembang (B) mengetikkan CustomerNameChangedListener di lingkungan implement dan IDE dapat menambahkan deskripsi metode pendengar ke kelasnya. Pelengkapan kode sangat bagus.
Mari kita lihat tanda tangan metode pendengar baru. Bahkan pandangan sekilas saja sudah cukup untuk memahami bahwa versi sistem distribusi acara yang digunakan adalah: "pemberitahuan satu arah."
Data input tidak ditransmisikan dengan referensi, yang berarti bahwa tidak ada cara untuk memodifikasinya dengan cara apa pun sehingga perubahan jatuh ke aliran utama. Nilai pengembalian tidak ada, tidak ada umpan balik dari utas utama.
Bagaimana dengan kasus penggunaan lainnya? Mari bermain dengan deskripsi antarmuka pendengar acara.
namespace MyApp\Customer\Events; interface CustomerNameChangedListener { public function onCustomerNameChange( int $customerId, CustomerNameInterface $customerName ): CustomerNameInterface; }
Ada persyaratan untuk nilai kembali, yang berarti pendengar dapat (tetapi tidak diharuskan) mengembalikan nilai yang berbeda jika cocok dengan antarmuka yang ditentukan. Gunakan case: "peningkatan objek".
namespace MyApp\Customer\Events; interface CustomerNameChangedListener { public function onCustomerNameChange( int $customerId, CustomerNameInterface $customerName ): array; }
Ada persyaratan untuk nilai pengembalian jenis tertentu yang dapat dipahami bahwa ini adalah elemen koleksi. Gunakan case: "collection".
namespace MyApp\Customer\Events; interface CustomerNameChangedListener { public function onCustomerNameChange( int $customerId, CustomerNameInterface $customerName ): VoteInterface; }
Gunakan case: "rantai alternatif, pemungutan suara."
namespace MyFramework\Events; interface EventControllInterface { public function stopPropagation(); }
namespace MyApp\Customer\Events; interface CustomerNameChangedListener { public function onCustomerNameChange( int $customerId, CustomerNameInterface $customerName, EventControllInterface &$eventControll ); }
Tanpa diskusi, menghentikan penyebaran acara itu baik atau buruk.
Opsi ini dibaca dengan jelas, utas utama memberikan kesempatan bagi pendengar untuk menghentikan acara.
Jadi, jika kita beralih ke deskripsi pendengar, kita mendapatkan tanda tangan metode pendengar yang jauh lebih baik saat mengembangkannya.
Selain itu, kami memiliki kesempatan untuk utas utama untuk secara eksplisit menunjuk ke:
- penerimaan perubahan data yang masuk
- tipe pengembalian selain tipe yang masuk
- transfer eksplisit dari suatu objek untuk menghentikan propagasi suatu peristiwa
Cara menerapkan langganan acara
Opsi mungkin berbeda. Arti umum dari semua opsi datang ke fakta bahwa kita perlu entah bagaimana menginformasikan objek ListenerProvider (objek yang menyediakan kesempatan untuk berlangganan acara), yang merupakan acara milik antarmuka tertentu.
Anda dapat mempertimbangkan konversi objek yang diteruskan ke jenis yang dapat dipanggil sebagai contoh. Harus dipahami bahwa mungkin ada banyak pilihan untuk mendapatkan informasi meta tambahan:
- dapat diteruskan secara eksplisit, seperti pada contoh
- dapat disimpan dalam anotasi antarmuka pendengar
- Anda dapat menggunakan nama antarmuka pendengar sebagai nama acara
Contoh Implementasi Berlangganan
namespace MyFramework\Events; class ListenerProvider { private $handlerAssociation = []; public function addHandlerAssociation( string $handlerInterfaceName, string $handlerMethodName, string $eventName ) { $this->handlerAssociation[$handlerInterfaceName] = [ 'methodName' => $handlerMethodName, 'eventName' => $eventName ]; } public function addHandler(object $handler) { $hasAssociation = false; foreach( $this->handlerAssociation as $handlerInterfaceName => $handlerMetaData ) { if ( $handler interfaceof $handlerInterfaceName ) { $methodName = $handlerMetaData['methodName']; $eventName = $handlerMetaData['eventName']; $this->addListener($eventName, [$handler, $methodName]); $hasAssociation = true; } } if ( !$hasAssociation ) { throw new \Exception('Unknown handler object'); } } }
Kami menambahkan metode konfigurasi ke objek berlangganan, yang untuk setiap antarmuka pendengar menjelaskan metadata-nya, seperti metode yang dipanggil dan nama acara.
Menurut data ini, pada saat berlangganan, kami mengubah $ handler yang diteruskan menjadi objek yang dapat dipanggil yang menunjukkan metode yang dipanggil.
Jika Anda perhatikan, kode ini menyiratkan bahwa satu objek $ handler dapat mengimplementasikan banyak antarmuka pendengar acara dan akan berlangganan masing-masing. Ini adalah analog dari SubscriberInterface untuk berlangganan massal suatu objek ke beberapa acara. Seperti yang Anda lihat, implementasi di atas tidak memerlukan mekanisme terpisah seperti addSubscriber(SubscriberInterface $subscriber)
ternyata berfungsi di luar kotak.
Dispatcher
Sayangnya, pendekatan yang dijelaskan bertentangan dengan antarmuka yang diterima sebagai standar psr / event-dispatcher
Karena kita tidak perlu meneruskan objek apa pun ke Dispatcher. Ya, Anda dapat melewatkan objek seperti gula:
class Event { public function __construct(string $eventName, ...$arguments) {
Dan menggunakannya saat membuat acara pada antarmuka psr, tapi itu benar-benar jelek.
Dalam cara yang baik, antarmuka Dispatcher akan terlihat lebih baik seperti ini:
interface EventDispatcherInterface { public function dispatch(string $eventName, ...$arguments); public function dispatchStopabled(string $eventName, ...$arguments); }
Mengapa ada dua metode? Sulit untuk menggabungkan semua kasus penggunaan menjadi implementasi tunggal. Lebih baik menambahkan metode Anda sendiri untuk setiap kasus penggunaan, akan ada interpretasi yang jelas tentang bagaimana Dispatcher akan memproses nilai yang dikembalikan dari pendengar.
Itu saja. Akan menarik untuk berdiskusi dengan masyarakat apakah pendekatan yang digambarkan memiliki hak untuk hidup.