Các câu trả lời khác đã làm một công việc tuyệt vời để giải thích sự khác biệt giữa các giao diện và đặc điểm. Tôi sẽ tập trung vào một ví dụ trong thế giới thực hữu ích, đặc biệt là chứng minh rằng các đặc điểm có thể sử dụng các biến thể hiện - cho phép bạn thêm hành vi vào một lớp với mã nồi hơi tối thiểu.
Một lần nữa, như được đề cập bởi những người khác, các đặc điểm kết hợp tốt với các giao diện, cho phép giao diện chỉ định hợp đồng hành vi và đặc điểm để thực hiện việc thực hiện.
Thêm khả năng xuất bản / đăng ký sự kiện vào một lớp có thể là một kịch bản phổ biến trong một số cơ sở mã. Có 3 giải pháp phổ biến:
- Xác định một lớp cơ sở với Pub/Sub Code, và sau đó các lớp muốn cung cấp các sự kiện có thể mở rộng nó để đạt được các khả năng.
- Xác định một lớp với Pub/Sub Code, và sau đó các lớp khác muốn cung cấp các sự kiện có thể sử dụng nó thông qua bố cục, xác định các phương thức của riêng chúng để bọc đối tượng được sáng tác, ủy quyền cho phương thức gọi cho nó.
- Xác định một đặc điểm với sự kiện Pub/Code Sub, và sau đó các lớp khác muốn cung cấp các sự kiện có thể
use
Đặc điểm, hay còn gọi là nhập khẩu, để đạt được các khả năng.
Mỗi công việc tốt như thế nào?
#1 không hoạt động tốt. Cho đến ngày bạn nhận ra bạn không thể mở rộng lớp cơ sở vì bạn đã mở rộng một thứ khác. Tôi sẽ không chỉ ra một ví dụ về điều này bởi vì rõ ràng việc sử dụng kế thừa như thế này là như thế nào.
#2 & #3 Cả hai đều hoạt động tốt. Tôi sẽ trình bày một ví dụ làm nổi bật một số khác biệt.
Đầu tiên, một số mã sẽ giống nhau giữa cả hai ví dụ:
Một giao diện
interface Observable {
function addEventListener[$eventName, callable $listener];
function removeEventListener[$eventName, callable $listener];
function removeAllEventListeners[$eventName];
}
Và một số mã để chứng minh việc sử dụng:
$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, bây giờ hãy cho thấy việc triển khai lớp Auction
sẽ khác nhau như thế nào khi sử dụng các đặc điểm.
Đầu tiên, đây là cách số 2 [sử dụng thành phần] sẽ giống như:
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];
}
}
Đây là cách số 3 [đặc điểm] sẽ giống như:
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]];
}
}
Lưu ý rằng mã bên trong EventEmitterTrait
hoàn toàn giống với những gì bên trong lớp EventEmitter
ngoại trừ tính trạng tuyên bố phương thức triggerEvent[]
được bảo vệ. Vì vậy, sự khác biệt duy nhất bạn cần xem xét là việc thực hiện lớp Auction
.the only difference you need to look at is the implementation of the Auction
class.
Và sự khác biệt là lớn. Khi sử dụng sáng tác, chúng tôi có được một giải pháp tuyệt vời, cho phép chúng tôi sử dụng lại EventEmitter
của chúng tôi bằng nhiều lớp như chúng tôi muốn. Nhưng, nhược điểm chính là chúng tôi có rất nhiều mã nồi hơi mà chúng tôi cần viết và duy trì bởi vì đối với mỗi phương thức được xác định trong giao diện
$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[]];
}
1, chúng tôi cần thực hiện nó và viết mã Boing Boilerplate chỉ chuyển tiếp các đối số vào phương thức tương ứng Trong sáng tác của chúng tôi đối tượng EventEmitter
. Sử dụng đặc điểm trong ví dụ này cho phép chúng tôi tránh điều đó, giúp chúng tôi giảm mã nồi hơi và cải thiện khả năng bảo trì.the trait in this example lets us avoid that, helping us reduce boilerplate code and improve maintainability. Tuy nhiên, có thể có những lúc bạn không muốn lớp Auction
của mình triển khai giao diện
$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[]];
}
1 đầy đủ - có thể bạn chỉ muốn phơi bày 1 hoặc 2 phương thức, hoặc thậm chí có thể không có gì để bạn có thể xác định chữ ký phương thức của riêng mình. Trong trường hợp như vậy, bạn vẫn có thể thích phương pháp thành phần.Nhưng, đặc điểm này rất hấp dẫn trong hầu hết các tình huống, đặc biệt là nếu giao diện có rất nhiều phương thức, điều này khiến bạn viết nhiều nồi hơi.
* Bạn thực sự có thể thực hiện cả hai - Xác định lớp EventEmitter
trong trường hợp bạn muốn sử dụng nó theo thành phần và xác định tính trạng EventEmitterTrait
, sử dụng triển khai lớp EventEmitter
bên trong đặc điểm :]