Thông báo dịch vụ công cộng:
Tôi muốn tuyên bố về hồ sơ mà tôi tin rằng các đặc điểm hầu như luôn luôn là mùi mã và nên tránh để ủng hộ thành phần. Theo ý kiến của tôi, việc thừa kế đơn lẻ thường bị lạm dụng đến mức trở thành một kiểu chống và nhiều kế thừa chỉ làm cho vấn đề này trở nên phức tạp. Bạn sẽ được phục vụ tốt hơn nhiều trong hầu hết các trường hợp bằng cách ưu tiên thành phần hơn thừa kế (có thể là đơn hoặc nhiều). Nếu bạn vẫn quan tâm đến các đặc điểm và mối quan hệ của chúng với các giao diện, hãy đọc tiếp ...
Hãy bắt đầu bằng cách nói điều này:
Lập trình hướng đối tượng (OOP) có thể là một mô hình khó nắm bắt. Chỉ vì bạn đang sử dụng các lớp không có nghĩa là mã của bạn là Hướng đối tượng (OO).
Để viết mã OO, bạn cần hiểu rằng OOP thực sự là về khả năng của các đối tượng của bạn. Bạn đã phải suy nghĩ về các lớp học về những gì họ có thể làm thay vì những gì họ thực sự làm . Điều này trái ngược hoàn toàn với lập trình thủ tục truyền thống, nơi tập trung vào việc tạo ra một chút mã "làm một cái gì đó".
Nếu mã OOP là về lập kế hoạch và thiết kế, một giao diện là bản thiết kế và một đối tượng là ngôi nhà được xây dựng đầy đủ. Trong khi đó, các đặc điểm chỉ đơn giản là một cách để giúp xây dựng ngôi nhà được đặt ra bởi bản thiết kế (giao diện).
Giao diện
Vậy, tại sao chúng ta nên sử dụng giao diện? Rất đơn giản, các giao diện làm cho mã của chúng tôi ít giòn hơn. Nếu bạn nghi ngờ tuyên bố này, hãy hỏi bất cứ ai bị buộc phải duy trì mã kế thừa không được viết trên giao diện.
Giao diện là một hợp đồng giữa lập trình viên và mã của anh ấy / cô ấy. Giao diện nói: "Miễn là bạn chơi theo luật của tôi, bạn có thể triển khai tôi theo cách bạn thích và tôi hứa tôi sẽ không phá mã khác của bạn."
Vì vậy, ví dụ, hãy xem xét một kịch bản trong thế giới thực (không có ô tô hoặc vật dụng):
Bạn muốn triển khai hệ thống lưu trữ cho ứng dụng web để giảm tải máy chủ
Bạn bắt đầu bằng cách viết một lớp để lưu các yêu cầu phản hồi bằng APC:
class ApcCacher
{
public function fetch($key) {
return apc_fetch($key);
}
public function store($key, $data) {
return apc_store($key, $data);
}
public function delete($key) {
return apc_delete($key);
}
}
Sau đó, trong đối tượng phản hồi HTTP, bạn kiểm tra lần truy cập bộ đệm trước khi thực hiện tất cả công việc để tạo phản hồi thực tế:
class Controller
{
protected $req;
protected $resp;
protected $cacher;
public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
$this->req = $req;
$this->resp = $resp;
$this->cacher = $cacher;
$this->buildResponse();
}
public function buildResponse() {
if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
$this->resp = $response;
} else {
// Build the response manually
}
}
public function getResponse() {
return $this->resp;
}
}
Cách tiếp cận này hoạt động tuyệt vời. Nhưng có thể vài tuần sau bạn quyết định bạn muốn sử dụng hệ thống bộ đệm dựa trên tệp thay vì APC. Bây giờ bạn phải thay đổi mã trình điều khiển của mình vì bạn đã lập trình bộ điều khiển của mình hoạt động với chức năng của ApcCacher
lớp thay vì giao diện thể hiện các khả năng của ApcCacher
lớp. Hãy nói thay vì ở trên, bạn đã làm cho Controller
lớp học dựa vào CacherInterface
thay vì cụ thể ApcCacher
như vậy:
// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)
Để đi cùng với đó, bạn xác định giao diện của mình như vậy:
interface CacherInterface
{
public function fetch($key);
public function store($key, $data);
public function delete($key);
}
Đổi lại, bạn có cả lớp và lớp ApcCacher
mới của bạn FileCacher
triển khai CacherInterface
và bạn lập trình Controller
lớp của mình để sử dụng các khả năng theo giao diện.
Ví dụ này (hy vọng) cho thấy cách lập trình với giao diện cho phép bạn thay đổi việc triển khai bên trong các lớp mà không phải lo lắng nếu các thay đổi sẽ phá vỡ mã khác của bạn.
Đặc điểm
Các đặc điểm, mặt khác, chỉ đơn giản là một phương pháp để sử dụng lại mã. Các giao diện không nên được coi là một thay thế loại trừ lẫn nhau cho các đặc điểm. Trong thực tế, việc tạo ra các đặc điểm đáp ứng các khả năng mà giao diện yêu cầu là trường hợp sử dụng lý tưởng .
Bạn chỉ nên sử dụng các đặc điểm khi nhiều lớp chia sẻ cùng một chức năng (có thể được quyết định bởi cùng một giao diện). Không có ý nghĩa trong việc sử dụng một đặc điểm để cung cấp chức năng cho một lớp duy nhất: điều đó chỉ làm xáo trộn những gì lớp đó làm và một thiết kế tốt hơn sẽ chuyển chức năng của tính trạng đó vào lớp có liên quan.
Xem xét việc thực hiện các đặc điểm sau:
interface Person
{
public function greet();
public function eat($food);
}
trait EatingTrait
{
public function eat($food)
{
$this->putInMouth($food);
}
private function putInMouth($food)
{
// Digest delicious food
}
}
class NicePerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Good day, good sir!';
}
}
class MeanPerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Your mother was a hamster!';
}
}
Một ví dụ cụ thể hơn: hãy tưởng tượng cả bạn FileCacher
và bạnApcCacher
từ cuộc thảo luận về giao diện đều sử dụng cùng một phương pháp để xác định xem một mục lưu trữ có bị cũ hay không và nên xóa (rõ ràng đây không phải là trường hợp thực tế, nhưng đi cùng với nó). Bạn có thể viết một đặc điểm và cho phép cả hai lớp sử dụng nó cho yêu cầu giao diện chung.
Một lời cảnh báo cuối cùng: hãy cẩn thận đừng quá nhiệt tình với những đặc điểm. Thông thường các đặc điểm được sử dụng như một cái nạng cho thiết kế kém khi việc triển khai lớp duy nhất sẽ đủ. Bạn nên hạn chế các đặc điểm để đáp ứng các yêu cầu giao diện để thiết kế mã tốt nhất.