Kế thừa nhiều trong PHP


97

Tôi đang tìm một cách tốt và rõ ràng để giải quyết thực tế là PHP5 vẫn không hỗ trợ đa kế thừa. Đây là phân cấp lớp:

Tin nhắn
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage

Hai loại lớp của Lời mời * có rất nhiều điểm chung; tôi muốn có một lớp cha chung, Invitation, mà cả hai đều sẽ kế thừa. Thật không may, chúng cũng có nhiều điểm chung với tổ tiên hiện tại của chúng ... TextMessage và EmailMessage. Mong muốn cổ điển cho đa kế thừa ở đây.

Cách tiếp cận nhẹ nhàng nhất để giải quyết vấn đề là gì?

Cảm ơn!


4
Không có nhiều trường hợp thừa kế (hoặc thậm chí đa thừa kế) là chính đáng. Nhìn vào các nguyên tắc SOLID. Thích thành phần hơn thừa kế.
Ondřej Mirtes

2
@ OndřejMirtes ý bạn là gì - "không nhiều trường hợp thừa kế là chính đáng."?
styler1972

12
Ý tôi là - kế thừa mang lại nhiều vấn đề hơn là lợi ích (hãy nhìn vào nguyên tắc thay thế Liskov). Bạn có thể giải quyết hầu hết mọi thứ với bố cục và tiết kiệm rất nhiều đau đầu. Kế thừa cũng là tĩnh - có nghĩa là bạn không thể thay đổi những gì đã được viết trong mã. Nhưng kết hợp có thể được sử dụng trong thời gian chạy và bạn có thể chọn triển khai động - ví dụ: sử dụng lại cùng một lớp với các cơ chế bộ nhớ đệm khác nhau.
Ondřej Mirtes 21/10/12

5
PHP 5.4 có "đặc điểm": stackoverflow.com/a/13966131/492130
f.ardelian

1
Tôi sẽ đề nghị những người mới bắt đầu không bao giờ sử dụng kế thừa . Nhìn chung, chỉ có hai tình huống trong đó thừa kế được cho phép là: 1) khi xây dựng một thư viện, vì vậy người sử dụng viết mã ít hơn và 2) khi dẫn đầu dự án đòi hỏi bạn sử dụng nó
gurghet

Câu trả lời:


141

Alex, hầu hết các trường hợp bạn cần đa kế thừa là một dấu hiệu cho thấy cấu trúc đối tượng của bạn có phần không chính xác. Trong tình huống bạn nêu ra, tôi thấy bạn có trách nhiệm trong lớp đơn giản là quá rộng. Nếu Message là một phần của mô hình kinh doanh ứng dụng, thì nó không nên quan tâm đến kết xuất đầu ra. Thay vào đó, bạn có thể phân chia trách nhiệm và sử dụng MessageDispatcher để gửi Thông báo được chuyển bằng văn bản hoặc chương trình phụ trợ html. Tôi không biết mã của bạn, nhưng hãy để tôi mô phỏng nó theo cách này:

$m = new Message();
$m->type = 'text/html';
$m->from = 'John Doe <jdoe@yahoo.com>';
$m->to = 'Random Hacker <rh@gmail.com>';
$m->subject = 'Invitation email';
$m->importBody('invitation.html');

$d = new MessageDispatcher();
$d->dispatch($m);

Bằng cách này, bạn có thể thêm một số chuyên môn vào lớp Message:

$htmlIM = new InvitationHTMLMessage(); // html type, subject and body configuration in constructor
$textIM = new InvitationTextMessage(); // text type, subject and body configuration in constructor

$d = new MessageDispatcher();
$d->dispatch($htmlIM);
$d->dispatch($textIM);

Lưu ý rằng MessageDispatcher sẽ đưa ra quyết định gửi dưới dạng HTML hay văn bản thuần túy tùy thuộc vào thuộc typetính trong đối tượng Message được truyền.

// in MessageDispatcher class
public function dispatch(Message $m) {
    if ($m->type == 'text/plain') {
        $this->sendAsText($m);
    } elseif ($m->type == 'text/html') {
        $this->sendAsHTML($m);
    } else {
        throw new Exception("MIME type {$m->type} not supported");
    }
}

Tóm lại, trách nhiệm được phân chia giữa hai lớp. Cấu hình thông báo được thực hiện trong lớp InvitationHTMLMessage / InvitationTextMessage và thuật toán gửi được ủy quyền cho người điều phối. Đây được gọi là Mô hình Chiến lược, bạn có thể đọc thêm tại đây .


13
Câu trả lời mở rộng đáng kinh ngạc, cảm ơn bạn! Tôi đã học được vài điều hôm nay!
Alex Weinstein

26
... Tôi biết điều này hơi cũ (tôi đã tìm kiếm để xem liệu PHP có MI không ... chỉ vì tò mò) Tôi không nghĩ đây là một ví dụ điển hình về Mô hình chiến lược. Mô hình Chiến lược được thiết kế để bạn có thể thực hiện "chiến lược" mới bất kỳ lúc nào. Việc triển khai bạn đã cung cấp không có khả năng như vậy. Thay vào đó, Message phải có chức năng "send" gọi MessageDispatcher-> send () (Dispatcher hoặc là param hoặc thành viên var) và các lớp mới HTMLDispatcher & TextDispatcher sẽ triển khai "send" theo các cách tương ứng của chúng (điều này cho phép các Điều phối viên khác thực hiện công việc khác)
Terence Honles

12
Thật không may, PHP không tuyệt vời để thực hiện mô hình Chiến lược. Các ngôn ngữ hỗ trợ nạp chồng phương thức hoạt động tốt hơn ở đây - hãy tưởng tượng bạn có hai phương thức cùng tên: công văn (HTMLMessage $ m) và công văn (TextMessage $) - giờ đây trong trình biên dịch / thông dịch ngôn ngữ được đánh máy mạnh sẽ tự động sử dụng "chiến lược" phù hợp dựa trên loại tham số. Bên cạnh đó, tôi không nghĩ rằng cởi mở cho việc thực hiện chiến lược mới là bản chất của Mô hình chiến lược. Chắc chắn đó là một điều tốt đẹp để có, nhưng thường không phải là một yêu cầu.
Michał Rudnicki

2
Giả sử bạn có một lớp học Tracing(đây chỉ là một mẫu) nơi bạn muốn có những thứ chung chung như gỡ lỗi vào tệp, gửi SMS cho sự cố nghiêm trọng, v.v. Tất cả các lớp của bạn đều là con của lớp này. Bây giờ, giả sử bạn muốn tạo một lớp Exceptioncó các hàm đó (= con của Tracing). Lớp này phải là trẻ em của Exception. Làm thế nào để bạn thiết kế những thứ như vậy mà không có đa kế thừa? Có, bạn luôn có thể có giải pháp, nhưng bạn sẽ luôn cận kề với hack. Và hack = giải pháp tốn kém về lâu dài. Kết thúc câu chuyện.
Olivier Pons

1
Olivier Pons, tôi không nghĩ rằng Tracing phân lớp sẽ là giải pháp chính xác cho trường hợp sử dụng của bạn. Một cái gì đó đơn giản như có một lớp Truy tìm trừu tượng với các phương thức tĩnh Debug, SendSMS, v.v., sau đó có thể được gọi từ bên trong bất kỳ lớp nào khác với Tracing :: SendSMS (), v.v. Các lớp khác của bạn không phải là 'loại' Truy tìm, họ 'sử dụng' Tracing. Lưu ý: một số người có thể thích một singleton hơn các phương thức tĩnh; Tôi thích sử dụng các phương thức tĩnh hơn các singleton nếu có thể.

15

Có thể bạn có thể thay thế một quan hệ 'is-a' bằng một quan hệ 'has-a'? Lời mời có thể có Tin nhắn, nhưng nó không nhất thiết phải là tin nhắn 'is-a'. Fe Lời mời có thể được xác nhận, điều này không phù hợp với mô hình Tin nhắn.

Tìm kiếm 'thành phần so với kế thừa' nếu bạn cần biết thêm về điều đó.


9

Nếu tôi có thể trích dẫn Phil trong chủ đề này ...

PHP, giống như Java, không hỗ trợ đa kế thừa.

Phiên bản PHP 5.4 sẽ là những đặc điểm cố gắng cung cấp giải pháp cho vấn đề này.

Trong thời gian chờ đợi, tốt nhất bạn nên suy nghĩ lại thiết kế lớp học của mình. Bạn có thể triển khai nhiều giao diện nếu bạn đang theo đuổi một API mở rộng cho các lớp của mình.

Và Chris ...

PHP không thực sự hỗ trợ đa kế thừa, nhưng có một số cách (hơi lộn xộn) để triển khai nó. Kiểm tra URL này để biết một số ví dụ:

http://www.jasny.net/articles/how-i-php-multiple-inheritance/

Nghĩ rằng cả hai đều có liên kết hữu ích. Nóng lòng muốn thử các đặc điểm hoặc có thể là một số kết hợp ...


1
Đặc điểm là cách để đi
Jonathan

6

Khung công tác Symfony có một plugin mixin cho việc này , bạn có thể muốn kiểm tra nó - thậm chí chỉ để biết ý tưởng, nếu không muốn sử dụng nó.

Câu trả lời "mẫu thiết kế" là trừu tượng hóa chức năng được chia sẻ thành một thành phần riêng biệt và soạn trong thời gian chạy. Hãy nghĩ về một cách để trừu tượng hóa chức năng Lời mời dưới dạng một lớp được liên kết với các lớp Thông báo của bạn theo một cách nào đó khác với tính kế thừa.


4

Tôi đang sử dụng các đặc điểm trong PHP 5.4 để giải quyết vấn đề này. http://php.net/manual/en/language.oop5.traits.php

Điều này cho phép kế thừa cổ điển với mở rộng, nhưng cũng có thể đặt chức năng và thuộc tính chung thành một 'đặc điểm'. Như hướng dẫn sử dụng nói:

Đặc điểm là một cơ chế để sử dụng lại mã trong các ngôn ngữ kế thừa đơn như PHP. Một đặc điểm nhằm giảm một số hạn chế của thừa kế đơn bằng cách cho phép nhà phát triển sử dụng lại các bộ phương thức một cách tự do trong một số lớp độc lập sống trong các phân cấp lớp khác nhau.


3

Nghe có vẻ như mẫu trang trí có thể phù hợp, nhưng thật khó để nói nếu không biết thêm chi tiết.


Câu trả lời hay nhất IMO.
l00k

3

Đây vừa là câu hỏi vừa là giải pháp….

Còn điều kỳ diệu _ call () thì sao,_get (), __set () phương thức? Tôi vẫn chưa thử nghiệm giải pháp này nhưng nếu bạn tạo một lớp MultiInherit thì sao. Một biến được bảo vệ trong một lớp con có thể chứa một mảng các lớp để kế thừa. Hàm khởi tạo trong lớp đa giao diện có thể tạo các thể hiện của mỗi lớp đang được kế thừa và liên kết chúng với một thuộc tính riêng, chẳng hạn như _ext. Phương thức __call () có thể sử dụng hàm method_exists () trên mỗi lớp trong mảng _ext để định vị phương thức chính xác cần gọi. __get () và __set có thể được sử dụng để định vị các thuộc tính bên trong hoặc nếu một chuyên gia của bạn với các tham chiếu, bạn có thể đặt các thuộc tính của lớp con và các lớp được kế thừa là các tham chiếu đến cùng một dữ liệu. Đa kế thừa của đối tượng của bạn sẽ minh bạch đối với mã sử dụng các đối tượng đó. Cũng thế, các đối tượng bên trong có thể truy cập trực tiếp các đối tượng kế thừa nếu cần, miễn là mảng _ext được lập chỉ mục theo tên lớp. Tôi đã hình dung ra việc tạo ra siêu lớp này và chưa thực hiện nó vì tôi cảm thấy rằng nếu nó hoạt động tốt hơn nó có thể dẫn đến phát triển một số thói quen lập trình xấu khác nhau.


Tôi nghĩ rằng điều này là khả thi. Nó sẽ kết hợp chức năng của nhiều lớp, nhưng sẽ không thực sự kế thừa cho họ (theo nghĩa instanceof)
user102008

Và điều này chắc chắn sẽ thất bại để cho phép ghi đè càng sớm càng trong lớp bên trong thực hiện cuộc gọi đến tự :: <whatever>
Phil Lello

1

Tôi có một số câu hỏi muốn hỏi để làm rõ những gì bạn đang làm:

1) Đối tượng tin nhắn của bạn có chỉ chứa một tin nhắn như nội dung, người nhận, lịch trình thời gian không? 2) Bạn định làm gì với đối tượng Lời mời của mình? Nó có cần được đối xử đặc biệt so với EmailMessage không? 3) Nếu vậy thì CÓ GÌ đặc biệt về nó? 4) Nếu đúng như vậy, tại sao các loại thông báo cần xử lý khác nhau cho một lời mời? 5) Nếu bạn muốn gửi một tin nhắn chào mừng hoặc một tin nhắn OK thì sao? Họ cũng là những đối tượng mới?

Có vẻ như bạn đang cố gắng kết hợp quá nhiều chức năng vào một tập hợp các đối tượng chỉ quan tâm đến việc lưu giữ nội dung thư - chứ không phải xử lý nó như thế nào. Đối với tôi, bạn thấy, không có sự khác biệt giữa một lời mời hay một tin nhắn tiêu chuẩn. Nếu lời mời yêu cầu xử lý đặc biệt, thì điều đó có nghĩa là logic ứng dụng chứ không phải loại thông báo.

Ví dụ: một hệ thống tôi xây dựng có một đối tượng tin nhắn cơ sở dùng chung được mở rộng thành SMS, Email và các loại tin nhắn khác. Tuy nhiên: những điều này không được mở rộng thêm - một tin nhắn mời chỉ là văn bản được xác định trước để được gửi qua một tin nhắn kiểu Email. Một ứng dụng Lời mời cụ thể sẽ liên quan đến việc xác nhận và các yêu cầu khác cho lời mời. Sau cùng, tất cả những gì bạn muốn làm là gửi tin nhắn X tới người nhận Y, đây phải là một hệ thống rời rạc theo đúng nghĩa của nó.


0

Vấn đề tương tự như Java. Hãy thử sử dụng các giao diện với các hàm trừu tượng để giải quyết vấn đề đó


0

PHP hỗ trợ các giao diện. Đây có thể là một đặt cược tốt, tùy thuộc vào trường hợp sử dụng của bạn.


5
Các giao diện không cho phép triển khai chức năng cụ thể, vì vậy chúng không hữu ích ở đây.
Alex Weinstein

1
Các giao diện hỗ trợ Nhiều kế thừa, không giống như các lớp.
Craig Lewis

-1

Làm thế nào về một lớp Lời mời ngay bên dưới lớp Tin nhắn?

vì vậy hệ thống phân cấp sẽ:

Tin nhắn
--- Lời mời
------ TextMessage
------ EmailMessage

Và trong lớp Lời mời, hãy thêm chức năng có trong InvitationTextMessage và InvitationEmailMessage.

Tôi biết rằng Lời mời thực sự không phải là một loại Thông điệp, nó là một chức năng của Thông điệp. Vì vậy, tôi không chắc liệu đây có phải là thiết kế OO tốt hay không.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.