การสืบทอดหลายรายการใน PHP


97

ฉันกำลังมองหาวิธีที่ดีและสะอาดเพื่อหลีกเลี่ยงข้อเท็จจริงที่ว่า PHP5 ยังไม่รองรับการสืบทอดหลายรายการ นี่คือลำดับชั้นของคลาส:

ข้อความ
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage

คลาส Invitation * ทั้งสองประเภทมีหลายอย่างที่เหมือนกัน ฉันชอบที่จะมีคลาสผู้ปกครองทั่วไปคำเชิญที่ทั้งคู่จะสืบทอดมา น่าเสียดายที่พวกเขามีหลายสิ่งที่เหมือนกันกับบรรพบุรุษปัจจุบันของพวกเขา ... TextMessage และ EmailMessage ความปรารถนาคลาสสิกสำหรับการสืบทอดหลายรายการที่นี่

วิธีใดที่มีน้ำหนักเบาที่สุดในการแก้ปัญหานี้

ขอบคุณ!


4
มีไม่กี่กรณีที่การถ่ายทอดทางพันธุกรรม (หรือแม้กระทั่งการรับมรดกหลายครั้ง) นั้นสมเหตุสมผล ดูที่หลักการ SOLID ชอบองค์ประกอบมากกว่าการถ่ายทอดทางพันธุกรรม
Ondřej Mirtes

2
@ OndřejMirtesคุณหมายถึงอะไร - "มีไม่กี่กรณีที่มรดกเป็นสิ่งที่สมเหตุสมผล"?
styler1972

12
ฉันหมายถึง - การสืบทอดก่อให้เกิดปัญหามากกว่าผลประโยชน์ (ดูที่หลักการทดแทน Liskov) คุณสามารถแก้ปัญหาได้เกือบทุกอย่างด้วยการจัดองค์ประกอบภาพและช่วยลดอาการปวดหัวได้มาก การสืบทอดยังคงเป็นแบบคงที่ซึ่งหมายความว่าคุณไม่สามารถเปลี่ยนแปลงสิ่งที่เขียนไว้แล้วในโค้ดได้ แต่คอมโพสิตสามารถใช้ได้ที่รันไทม์และคุณสามารถเลือกการนำไปใช้งานได้แบบไดนามิกเช่นใช้คลาสเดียวกันซ้ำกับกลไกการแคชที่ต่างกัน
Ondřej Mirtes

5
PHP 5.4 มี "traits": stackoverflow.com/a/13966131/492130
f.ardelian

1
ฉันขอแนะนำให้เริ่มต้นที่ไม่เคยใช้มรดก โดยทั่วไปมีเพียงสองสถานการณ์ที่อนุญาตให้สืบทอดคือ 1) เมื่อสร้างไลบรารีดังนั้นผู้ใช้จึงเขียนโค้ดน้อยลงและ 2) เมื่อหัวหน้าโครงการต้องการให้คุณใช้
gurghet

คำตอบ:


141

อเล็กซ์ส่วนใหญ่คุณต้องการการสืบทอดหลายครั้งเป็นสัญญาณว่าโครงสร้างวัตถุของคุณค่อนข้างไม่ถูกต้อง ในสถานการณ์ที่คุณระบุไว้ฉันเห็นว่าคุณมีความรับผิดชอบในชั้นเรียนกว้างเกินไป หากข้อความเป็นส่วนหนึ่งของรูปแบบธุรกิจแอปพลิเคชันไม่ควรดูแลเกี่ยวกับการแสดงผลเอาต์พุต แต่คุณสามารถแยกความรับผิดชอบและใช้ MessageDispatcher ที่ส่งข้อความที่ส่งผ่านโดยใช้แบ็กเอนด์ข้อความหรือ html ฉันไม่รู้รหัสของคุณ แต่ให้ฉันจำลองด้วยวิธีนี้:

$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);

ด้วยวิธีนี้คุณสามารถเพิ่มความเชี่ยวชาญบางอย่างให้กับคลาสข้อความ:

$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);

โปรดทราบว่า MessageDispatcher จะทำการตัดสินใจว่าจะส่งเป็น HTML หรือข้อความธรรมดาขึ้นอยู่กับtypeคุณสมบัติในวัตถุข้อความที่ส่งผ่าน

// 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");
    }
}

สรุปได้ว่าความรับผิดชอบแบ่งออกเป็นสองชั้น การกำหนดค่าข้อความเสร็จสิ้นในคลาส InvitationHTMLMessage / InvitationTextMessage และอัลกอริทึมการส่งจะถูกมอบหมายให้กับผู้มอบหมายงาน รูปแบบกลยุทธ์นี้เรียกว่าคุณสามารถอ่านเพิ่มเติมเกี่ยวกับมันนี่


13
คำตอบที่กว้างขวางอย่างน่าอัศจรรย์ขอบคุณ! วันนี้ฉันได้เรียนรู้อะไรบางอย่าง!
Alex Weinstein

26
... ฉันรู้ว่านี่มันเก่าไปหน่อย (ฉันกำลังค้นหาเพื่อดูว่า PHP มี MI หรือไม่ ... เพื่อความอยากรู้) ฉันไม่คิดว่านี่เป็นตัวอย่างที่ดีของ Strategy Pattern รูปแบบกลยุทธ์ได้รับการออกแบบมาเพื่อให้คุณสามารถใช้ "กลยุทธ์" ใหม่ได้ตลอดเวลา การใช้งานที่คุณให้มาไม่มีคุณสมบัติดังกล่าว แต่ข้อความควรมีฟังก์ชัน "ส่ง" ซึ่งเรียกใช้ MessageDispatcher-> dispatch () (Dispatcher ไม่ว่าจะเป็นพารามิเตอร์หรือสมาชิก var) และคลาสใหม่ HTMLDispatcher & TextDispatcher จะใช้ "dispatch" ตามลำดับ (ซึ่งช่วยให้ผู้มอบหมายงานรายอื่นทำ งานอื่น ๆ )
Terence Honles

12
น่าเสียดายที่ PHP ไม่เหมาะสำหรับการนำรูปแบบกลยุทธ์ไปใช้ ภาษาที่สนับสนุนวิธีการทำงานมากเกินไปจะทำงานได้ดีกว่าที่นี่ - ลองนึกภาพคุณมีสองวิธีที่ใช้ชื่อเดียวกัน: dispatch (HTMLMessage $ m) และ dispatch (TextMessage $) - ขณะนี้อยู่ในคอมไพเลอร์ / ล่ามภาษาที่พิมพ์มากเกินไปจะใช้ "กลยุทธ์" ที่ถูกต้องโดยอัตโนมัติตาม ประเภทของพารามิเตอร์ นอกเหนือจากนั้นฉันไม่คิดว่าการเปิดกว้างสำหรับการใช้กลยุทธ์ใหม่เป็นสาระสำคัญของรูปแบบกลยุทธ์ แน่นอนว่ามันเป็นสิ่งที่ดี แต่มักจะไม่ใช่ข้อกำหนด
Michał Rudnicki

2
สมมติว่าคุณมีคลาสTracing(นี่เป็นเพียงตัวอย่าง) ที่คุณต้องการมีสิ่งทั่วไปเช่นการดีบักลงในไฟล์ส่ง SMS สำหรับปัญหาร้ายแรงเป็นต้น ชั้นเรียนทั้งหมดของคุณเป็นลูกของชั้นเรียนนี้ ตอนนี้สมมติว่าคุณต้องการสร้างคลาสExceptionที่ควรมีฟังก์ชันเหล่านั้น (= ลูกของTracing) Exceptionระดับนี้จะต้องเป็นลูกของ คุณออกแบบสิ่งเหล่านี้โดยไม่มีการสืบทอดหลายอย่างได้อย่างไร? ใช่คุณอาจมีวิธีแก้ปัญหาเสมอ แต่คุณมักจะเข้าใกล้การแฮ็ก และการแฮ็ก = วิธีแก้ปัญหาราคาแพงในระยะยาว ตอนจบของเรื่อง.
Olivier Pons

1
Olivier Pons ฉันไม่คิดว่า Subclassing Tracing จะเป็นวิธีแก้ปัญหาที่ถูกต้องสำหรับกรณีการใช้งานของคุณ สิ่งที่ง่ายพอ ๆ กับการมีคลาส Tracing นามธรรมที่มีเมธอดแบบคงที่ Debug, SendSMS และอื่น ๆ ซึ่งสามารถเรียกได้จากภายในคลาสอื่น ๆ ด้วย Tracing :: SendSMS () เป็นต้นคลาสอื่น ๆ ของคุณไม่ใช่ 'ประเภท' ของ Tracing, พวกเขา 'ใช้' การติดตาม หมายเหตุ: บางคนอาจชอบซิงเกิลตันมากกว่าวิธีการคงที่ ฉันชอบใช้วิธีการคงที่มากกว่าเสื้อกล้ามถ้าเป็นไปได้

15

บางทีคุณอาจแทนที่ความสัมพันธ์ 'is-a' ด้วยความสัมพันธ์ 'has-a'? คำเชิญอาจมีข้อความ แต่ไม่จำเป็นต้องเป็นข้อความ "is-a" คำเชิญอาจได้รับการยืนยันซึ่งไม่เข้ากันได้ดีกับโมเดลข้อความ

ค้นหา 'องค์ประกอบเทียบกับการถ่ายทอดทางพันธุกรรม' หากคุณต้องการทราบข้อมูลเพิ่มเติม


9

ถ้าจะอ้างฟิลในกระทู้นี้ ...

PHP เช่น Java ไม่รองรับการสืบทอดหลายรายการ

การมาใน PHP 5.4 จะเป็นลักษณะที่พยายามหาวิธีแก้ปัญหานี้

ในระหว่างนี้คุณควรคิดใหม่ในการออกแบบชั้นเรียน คุณสามารถใช้หลายอินเทอร์เฟซได้หากคุณใช้ API แบบขยายไปยังคลาสของคุณ

และคริส ....

PHP ไม่รองรับการสืบทอดหลาย ๆ อย่าง แต่มีวิธีการ (ค่อนข้างยุ่ง) ในการนำไปใช้ ดู URL นี้สำหรับตัวอย่างบางส่วน:

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

คิดว่าทั้งคู่มีลิงค์ที่เป็นประโยชน์ แทบรอไม่ไหวที่จะลองลักษณะหรืออาจจะเป็นส่วนผสม ...


1
ลักษณะเป็นหนทางไป
โจนาธาน

6

เฟรมเวิร์ก Symfony มีปลั๊กอินมิกซ์อินสำหรับสิ่งนี้คุณอาจต้องการตรวจสอบ - แม้เพียงเพื่อความคิดหากไม่ต้องการใช้

คำตอบของ "รูปแบบการออกแบบ" คือการแยกฟังก์ชันการทำงานที่ใช้ร่วมกันออกเป็นส่วนประกอบที่แยกจากกันและเขียนในรันไทม์ ลองนึกถึงวิธีแยกฟังก์ชันการเชิญออกเป็นคลาสที่เชื่อมโยงกับคลาส Message ของคุณด้วยวิธีอื่นที่ไม่ใช่การสืบทอด


4

ฉันใช้ลักษณะใน PHP 5.4 เป็นวิธีแก้ปัญหานี้ http://php.net/manual/th/language.oop5.traits.php

สิ่งนี้ช่วยให้สามารถสืบทอดคลาสสิกด้วยการขยาย แต่ยังให้ความเป็นไปได้ในการวางฟังก์ชันและคุณสมบัติทั่วไปลงใน 'ลักษณะ' ตามที่คู่มือกล่าวไว้:

ลักษณะเป็นกลไกในการใช้รหัสซ้ำในภาษาที่สืบทอดเดียวเช่น PHP Trait มีจุดมุ่งหมายเพื่อลดข้อ จำกัด บางประการของการสืบทอดเพียงครั้งเดียวโดยช่วยให้นักพัฒนาสามารถใช้ชุดวิธีการซ้ำได้อย่างอิสระในคลาสอิสระหลายคลาสที่อาศัยอยู่ในลำดับชั้นที่แตกต่างกัน



3

นี่เป็นทั้งคำถามและทางออก ....

แล้วของวิเศษ _ call () ล่ะ_get (), __set () วิธีการ? ฉันยังไม่ได้ทดสอบวิธีนี้ แต่ถ้าคุณสร้างคลาส multiInherit ตัวแปรที่ได้รับการป้องกันในคลาสลูกอาจมีอาร์เรย์ของคลาสที่จะสืบทอด คอนสตรัคเตอร์ในคลาสมัลติอินเทอร์เฟซสามารถสร้างอินสแตนซ์ของแต่ละคลาสที่กำลังสืบทอดและเชื่อมโยงเข้ากับคุณสมบัติส่วนตัวพูด _ext เมธอด __call () สามารถใช้ฟังก์ชัน method_exists () ในแต่ละคลาสในอาร์เรย์ _ext เพื่อค้นหาวิธีการเรียกที่ถูกต้อง __get () และ __set สามารถใช้เพื่อค้นหาคุณสมบัติภายในหรือหากผู้เชี่ยวชาญของคุณที่มีการอ้างอิงคุณสามารถทำให้คุณสมบัติของคลาสย่อยและคลาสที่สืบทอดมาเป็นการอ้างถึงข้อมูลเดียวกัน การสืบทอดหลายวัตถุของคุณจะโปร่งใสในการเขียนโค้ดโดยใช้วัตถุเหล่านั้น นอกจากนี้ ออบเจ็กต์ภายในสามารถเข้าถึงอ็อบเจ็กต์ที่สืบทอดโดยตรงหากต้องการตราบเท่าที่อาร์เรย์ _ext ถูกสร้างดัชนีตามชื่อคลาส ฉันจินตนาการถึงการสร้างซูเปอร์คลาสนี้และยังไม่ได้นำไปใช้เนื่องจากฉันรู้สึกว่าถ้ามันใช้งานได้ดีกว่านั้นอาจนำไปสู่การพัฒนานิสัยการเขียนโปรแกรมที่ไม่ดี


ฉันคิดว่านี่เป็นไปได้ มันจะรวมฟังก์ชันการทำงานของหลาย ๆ คลาส แต่จะไม่สืบทอดมา (ในแง่ของinstanceof)
user102008

และสิ่งนี้จะล้มเหลวอย่างแน่นอนในการอนุญาตให้มีการลบล้างทันทีที่คนชั้นในเรียกตัวเอง :: <สิ่งใด ๆ >
ฟิลเลลโล

1

ฉันมีคำถามสองสามข้อที่จะขอให้ชี้แจงสิ่งที่คุณกำลังทำ:

1) วัตถุข้อความของคุณมีเพียงข้อความเช่นเนื้อความผู้รับกำหนดเวลาหรือไม่? 2) คุณตั้งใจจะทำอะไรกับวัตถุคำเชิญของคุณ? จำเป็นต้องได้รับการปฏิบัติเป็นพิเศษเมื่อเทียบกับ EmailMessage หรือไม่? 3) ถ้าเป็นเช่นนั้นมีอะไรพิเศษเกี่ยวกับเรื่องนี้? 4) หากเป็นเช่นนั้นเหตุใดประเภทข้อความจึงต้องการการจัดการที่แตกต่างกันสำหรับคำเชิญ 5) ถ้าคุณต้องการส่งข้อความต้อนรับหรือข้อความตกลงล่ะ? พวกเขาเป็นวัตถุใหม่ด้วยหรือไม่?

ดูเหมือนว่าคุณกำลังพยายามรวมฟังก์ชันการทำงานที่มากเกินไปไว้ในชุดของวัตถุที่ควรเกี่ยวข้องกับการเก็บเนื้อหาข้อความเท่านั้นไม่ใช่วิธีการจัดการ สำหรับฉันคุณเห็นว่าไม่มีความแตกต่างระหว่างคำเชิญหรือข้อความมาตรฐาน หากคำเชิญต้องการการจัดการพิเศษนั่นหมายถึงตรรกะของแอปพลิเคชันไม่ใช่ประเภทข้อความ

ตัวอย่างเช่นระบบที่ฉันสร้างขึ้นมีออบเจ็กต์ข้อความพื้นฐานที่ใช้ร่วมกันซึ่งขยายไปยัง SMS อีเมลและประเภทข้อความอื่น ๆ อย่างไรก็ตาม: สิ่งเหล่านี้ไม่ได้ขยายเพิ่มเติม - ข้อความเชิญเป็นเพียงข้อความที่กำหนดไว้ล่วงหน้าที่จะส่งผ่านข้อความประเภทอีเมล แอปพลิเคชันคำเชิญเฉพาะจะเกี่ยวข้องกับการตรวจสอบความถูกต้องและข้อกำหนดอื่น ๆ สำหรับคำเชิญ ท้ายที่สุดสิ่งที่คุณต้องทำคือส่งข้อความ X ไปยังผู้รับ Y ซึ่งควรเป็นระบบที่ไม่ต่อเนื่องในตัวของมันเอง


0

ปัญหาเดียวกันเช่น Java ลองใช้อินเทอร์เฟซที่มีฟังก์ชันนามธรรมเพื่อแก้ปัญหานั้น


0

PHP รองรับอินเทอร์เฟซ นี่อาจเป็นทางออกที่ดีขึ้นอยู่กับกรณีการใช้งานของคุณ


5
อินเทอร์เฟซไม่อนุญาตให้ใช้ฟังก์ชันที่เป็นรูปธรรมดังนั้นจึงไม่เป็นประโยชน์ที่นี่
Alex Weinstein

1
อินเทอร์เฟซรองรับการสืบทอดหลายรายการซึ่งแตกต่างจากคลาส
Craig Lewis

-1

แล้วคลาส Invitation ด้านล่างคลาส Message ล่ะ

ลำดับชั้นจึงไป:

ข้อความ
--- คำเชิญ
------ ข้อความ
อีเมล

และในคลาส Invitation ให้เพิ่มฟังก์ชันที่อยู่ใน InvitationTextMessage และ InvitationEmailMessage

ฉันรู้ว่าคำเชิญไม่ใช่ข้อความประเภทหนึ่ง แต่เป็นการใช้งานข้อความมากกว่า ดังนั้นฉันจึงไม่แน่ใจว่านี่เป็นการออกแบบ OO ที่ดีหรือไม่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.