Jawaban lain berhasil menjelaskan perbedaan antara antarmuka dan sifat. Saya akan fokus pada contoh dunia nyata yang berguna, khususnya yang menunjukkan bahwa sifat dapat menggunakan variabel instan - memungkinkan Anda menambahkan perilaku ke kelas dengan kode boilerplate minimal.
Sekali lagi, seperti yang disebutkan oleh orang lain, sifat-sifat berpasangan dengan baik dengan antarmuka, memungkinkan antarmuka untuk menentukan kontrak perilaku, dan sifat untuk memenuhi implementasi.
Menambahkan kemampuan mempublikasikan / berlangganan acara ke kelas dapat menjadi skenario umum di beberapa basis kode. Ada 3 solusi umum:
- Tentukan kelas dasar dengan pub peristiwa / subkode, dan kemudian kelas yang ingin menawarkan acara dapat memperluasnya untuk mendapatkan kemampuan.
- Tentukan kelas dengan pub peristiwa / subkode, dan kemudian kelas lain yang ingin menawarkan acara dapat menggunakannya melalui komposisi, menentukan metode mereka sendiri untuk membungkus objek yang dikomposisikan, proksi panggilan metode untuk itu.
- Tentukan sifat dengan event pub / sub kode, dan kemudian kelas lain yang ingin menawarkan acara dapat
use
sifat tersebut, alias impor, untuk mendapatkan kemampuan.
Seberapa baik masing-masing bekerja?
# 1 Tidak bekerja dengan baik. Itu akan, sampai hari Anda menyadari Anda tidak dapat memperpanjang kelas dasar karena Anda sudah memperpanjang sesuatu yang lain. Saya tidak akan menunjukkan contoh ini karena harus jelas betapa terbatasnya untuk menggunakan warisan seperti ini.
# 2 & # 3 keduanya bekerja dengan baik. Saya akan menunjukkan contoh yang menyoroti beberapa perbedaan.
Pertama, beberapa kode yang akan sama antara kedua contoh:
Antarmuka
interface Observable {
function addEventListener($eventName, callable $listener);
function removeEventListener($eventName, callable $listener);
function removeAllEventListeners($eventName);
}
Dan beberapa kode untuk menunjukkan penggunaan:
$auction = new Auction();
// Add a listener, so we know when we get a bid.
$auction->addEventListener('bid', function($bidderName, $bidAmount){
echo "Got a bid of $bidAmount from $bidderName\n";
});
// Mock some bids.
foreach (['Moe', 'Curly', 'Larry'] as $name) {
$auction->addBid($name, rand());
}
Ok, sekarang mari kita tunjukkan bagaimana implementasi dari Auction
kelas akan berbeda ketika menggunakan ciri-ciri.
Pertama, beginilah penampilan # 2 (menggunakan komposisi):
class EventEmitter {
private $eventListenersByName = [];
function addEventListener($eventName, callable $listener) {
$this->eventListenersByName[$eventName][] = $listener;
}
function removeEventListener($eventName, callable $listener) {
$this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
return $existingListener === $listener;
});
}
function removeAllEventListeners($eventName) {
$this->eventListenersByName[$eventName] = [];
}
function triggerEvent($eventName, array $eventArgs) {
foreach ($this->eventListenersByName[$eventName] as $listener) {
call_user_func_array($listener, $eventArgs);
}
}
}
class Auction implements Observable {
private $eventEmitter;
public function __construct() {
$this->eventEmitter = new EventEmitter();
}
function addBid($bidderName, $bidAmount) {
$this->eventEmitter->triggerEvent('bid', [$bidderName, $bidAmount]);
}
function addEventListener($eventName, callable $listener) {
$this->eventEmitter->addEventListener($eventName, $listener);
}
function removeEventListener($eventName, callable $listener) {
$this->eventEmitter->removeEventListener($eventName, $listener);
}
function removeAllEventListeners($eventName) {
$this->eventEmitter->removeAllEventListeners($eventName);
}
}
Beginilah penampilan # 3 (ciri-ciri):
trait EventEmitterTrait {
private $eventListenersByName = [];
function addEventListener($eventName, callable $listener) {
$this->eventListenersByName[$eventName][] = $listener;
}
function removeEventListener($eventName, callable $listener) {
$this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
return $existingListener === $listener;
});
}
function removeAllEventListeners($eventName) {
$this->eventListenersByName[$eventName] = [];
}
protected function triggerEvent($eventName, array $eventArgs) {
foreach ($this->eventListenersByName[$eventName] as $listener) {
call_user_func_array($listener, $eventArgs);
}
}
}
class Auction implements Observable {
use EventEmitterTrait;
function addBid($bidderName, $bidAmount) {
$this->triggerEvent('bid', [$bidderName, $bidAmount]);
}
}
Perhatikan bahwa kode di dalam EventEmitterTrait
persis sama dengan apa yang ada di dalam EventEmitter
kelas kecuali sifat menyatakan triggerEvent()
metode yang dilindungi. Jadi, satu-satunya perbedaan yang perlu Anda perhatikan adalah implementasi Auction
kelas .
Dan perbedaannya besar. Saat menggunakan komposisi, kami mendapatkan solusi hebat, memungkinkan kami untuk menggunakan kembali EventEmitter
kelas kami sebanyak yang kami suka. Namun, kelemahan utama adalah kita memiliki banyak kode boilerplate yang perlu kita tulis dan pelihara karena untuk setiap metode yang didefinisikan dalam Observable
antarmuka, kita perlu mengimplementasikannya dan menulis kode boilerplate yang membosankan yang hanya meneruskan argumen ke metode yang sesuai di kami menyusun EventEmitter
objek. Menggunakan sifat dalam contoh ini memungkinkan kita menghindari itu , membantu kita mengurangi kode boilerplate dan meningkatkan rawatan .
Namun, mungkin ada saat-saat di mana Anda tidak ingin Auction
kelas Anda mengimplementasikan Observable
antarmuka penuh - mungkin Anda hanya ingin mengekspos 1 atau 2 metode, atau bahkan mungkin tidak ada sama sekali sehingga Anda dapat menentukan tanda tangan metode Anda sendiri. Dalam kasus seperti itu, Anda mungkin masih lebih suka metode komposisi.
Tapi, sifat ini sangat menarik di sebagian besar skenario, terutama jika antarmuka memiliki banyak metode, yang menyebabkan Anda menulis banyak boilerplate.
* Anda sebenarnya bisa melakukan keduanya - tentukan EventEmitter
kelas jika Anda ingin menggunakannya secara komposisional, dan tentukan EventEmitterTrait
sifatnya juga, menggunakan EventEmitter
implementasi kelas di dalam sifat tersebut :)