ในภาษาที่มุ่งเน้นวัตถุวัตถุควรทำการดำเนินการกับตัวเองเมื่อใดและควรจะดำเนินการกับวัตถุเมื่อใด


11

สมมติว่ามีPageคลาสซึ่งแสดงชุดคำสั่งสำหรับตัวแสดงผลหน้า และสมมติว่ามีRendererคลาสที่รู้วิธีแสดงหน้าบนหน้าจอ เป็นไปได้ที่จะจัดโครงสร้างโค้ดด้วยวิธีที่ต่างกันสองวิธี:

/*
 * 1) Page Uses Renderer internally,
 * or receives it explicitly
 */
$page->renderMe(); 
$page->renderMe($renderer); 

/*
 * 2) Page is passed to Renderer
 */
$renderer->renderPage($page);

ข้อดีและข้อเสียของแต่ละวิธีคืออะไร เมื่อใดจะดีกว่า คนอื่นจะดีกว่าเมื่อไหร่?


พื้นหลัง

เมื่อต้องการเพิ่มพื้นหลังเพิ่มเติมเล็กน้อย - ฉันค้นหาตัวเองโดยใช้วิธีการทั้งสองในรหัสเดียวกัน ฉันกำลังใช้ห้องสมุดของบุคคลที่ 3 รูปแบบไฟล์ PDF TCPDFที่เรียกว่า บางแห่งในรหัสของฉันฉันต้องมีสิ่งต่อไปนี้เพื่อให้การแสดงผล PDF ทำงานได้

$pdf = new TCPDF();
$html = "some text";
$pdf->writeHTML($html);

กล่าวว่าฉันต้องการสร้างการเป็นตัวแทนของหน้า ฉันสามารถสร้างเทมเพลตที่มีคำแนะนำในการแสดงตัวอย่างข้อมูลหน้า PDF เช่น:

/*
 * A representation of the PDF page snippet:
 * a template directing how to render a specific PDF page snippet
 */
class PageSnippet
{    
    function runTemplate(TCPDF $pdf, array $data = null): void
    {
        $pdf->writeHTML($data['html']);
    }
}

/* To be used like so */
$pdf = new TCPDF();
$data['html'] = "some text";
$snippet = new PageSnippet();
$snippet->runTemplate($pdf, $data);

1) สังเกตที่นี่ที่ $snippet ทำงานตัวเองเช่นในตัวอย่างรหัสแรกของฉัน นอกจากนี้ยังจำเป็นต้องรู้และคุ้นเคยกับ$pdfและ$dataเพื่อการทำงาน

แต่ฉันสามารถสร้างPdfRendererชั้นเรียนได้เช่น:

class PdfRenderer
{
    /**@var TCPDF */
    protected $pdf;

    function __construct(TCPDF $pdf)
    {
        $this->pdf = $pdf;
    }

    function runTemplate(PageSnippet $template, array $data = null): void
    {
        $template->runTemplate($this->pdf, $data);
    }
}

จากนั้นรหัสของฉันเปลี่ยนเป็น:

$renderer = new PdfRenderer(new TCPDF());
$renderer->runTemplate(new PageSnippet(), array('html' => 'some text'));

2) ที่นี่$rendererได้รับPageSnippetและ$dataสิ่งที่จำเป็นสำหรับการทำงาน นี่คล้ายกับตัวอย่างรหัสที่สองของฉัน

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


2
แต่น่าเสียดายที่คุณได้เดินเข้าไปในโลกของซอฟต์แวร์ "สงครามทางศาสนา" ที่นี่ตามแนวของว่าจะใช้ช่องว่างหรือแท็บที่สไตล์การรั้งที่จะใช้ ฯลฯ ไม่มี "ดีกว่า" ที่นี่เพียงความคิดเห็นที่แข็งแกร่งทั้งสองด้าน ค้นหาข้อมูลเกี่ยวกับประโยชน์และข้อเสียของทั้งโมเดลโดเมนและโดเมนโลหิตจางและค้นหาความคิดเห็นของคุณ
David Arno

7
@DavidArno ใช้ช่องว่างที่คุณไม่เชื่อ! :)
candied_orange

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

@ Erik Eidt คุณสามารถยกเลิกการลบคำตอบของคุณได้ไหมเพราะฉันรู้สึกว่ามันเป็นคำตอบที่ "ดีกว่าตัวเลือก" ที่ดีมาก
David Arno

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

คำตอบ:


13

นี้ขึ้นอยู่กับสิ่งที่คุณคิดว่าเป็น OO

สำหรับ OOP = SOLID การดำเนินการควรเป็นส่วนหนึ่งของคลาสหากเป็นส่วนหนึ่งของ Single Responsibility ของคลาส

สำหรับ OO = virtual dispatch / polymorphism การดำเนินการควรเป็นส่วนหนึ่งของวัตถุถ้ามันควรถูกส่งแบบไดนามิกเช่นถ้ามันถูกเรียกผ่านอินเตอร์เฟส

สำหรับ OO = encapsulation การดำเนินการควรเป็นส่วนหนึ่งของคลาสหากใช้สถานะภายในที่คุณไม่ต้องการเปิดเผย

สำหรับ OO =“ ฉันชอบอินเทอร์เฟซที่คล่องแคล่ว” คำถามคือตัวแปรใดที่อ่านได้โดยธรรมชาติมากขึ้น

สำหรับ OO = การสร้างโมเดลเอนทิตีในโลกแห่งความเป็นจริงเอนทิตีในโลกแห่งความจริงใดที่ดำเนินการนี้


มุมมองเหล่านั้นทั้งหมดมักจะแยกผิด แต่บางครั้งมุมมองเหล่านี้อย่างน้อยหนึ่งมุมมองจะเป็นประโยชน์ในการตัดสินใจออกแบบ

เช่นใช้มุมมองแบบพหุสัณฐาน: หากคุณมีกลยุทธ์การแสดงผลที่แตกต่างกัน (เช่นรูปแบบเอาต์พุตที่แตกต่างกันหรือเอนจิ้นการเรนเดอร์ที่แตกต่างกัน) ก็$renderer->render($page)สมเหตุสมผลดี แต่หากคุณมีประเภทเพจที่แตกต่างกันซึ่งควรแสดงผลแตกต่างกัน$page->render()อาจจะดีกว่า หากผลลัพธ์ขึ้นอยู่กับประเภทหน้าเว็บและกลยุทธ์การแสดงผลคุณสามารถส่งสองครั้งผ่านรูปแบบผู้เข้าชม

อย่าลืมว่าในหลาย ๆ ภาษาฟังก์ชั่นไม่จำเป็นต้องเป็นวิธีการ ฟังก์ชั่นที่เรียบง่ายเช่นrender($page)ถ้ามักจะเป็นทางออกที่ดี (และเรียบง่ายอย่างน่าพิศวง)


รอเดี๋ยวก่อน ฉันยังคงสามารถรับการเรนเดอร์ polymorphic ได้ถ้าหน้านั้นมีการอ้างอิงไปยัง renderer แต่ไม่มีความคิดว่ามันเป็น renderer ใด มันหมายถึงว่า polymorphism นั้นอยู่ห่างจากรูกระต่ายเล็กน้อย ฉันยังสามารถเลือกและเลือกสิ่งที่จะส่งผ่านไปยังโหมดแสดงภาพ ฉันไม่ต้องผ่านหน้าทั้งหมด
candied_orange

@CandiedOrange นั่นเป็นจุดที่ดี แต่ฉันจะจองข้อโต้แย้งของคุณภายใต้ SRP: มันจะเป็นหน้าที่รับผิดชอบ capital-R ของหน้าเพื่อตัดสินใจว่าจะแสดงผลอย่างไรโดยใช้กลยุทธ์การเรนเดอร์ polymorphic
amon

ฉันคิดว่า$rendererกำลังจะตัดสินใจว่าจะทำให้ เมื่อ$pageพูดถึง$rendererทุกสิ่งที่มันบอกว่าเป็นสิ่งที่จะทำให้ ไม่ใช่วิธี $pageมีความคิดว่า นั่นทำให้ฉันประสบปัญหา SRP?
candied_orange

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

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

2

อลันเคย์อ้างว่าวัตถุมีความพอเพียง "ผู้ใหญ่" และสิ่งมีชีวิตที่รับผิดชอบ ผู้ใหญ่ทำสิ่งต่าง ๆ พวกเขาไม่ได้ดำเนินการ นั่นคือธุรกรรมทางการเงินมีหน้าที่บันทึกตัวเองหน้าที่รับผิดชอบในการเรนเดอร์ตัวเองฯลฯ มีความชัดเจนมากยิ่งขึ้นการห่อหุ้มเป็นเรื่องใหญ่ใน OOP โดยเฉพาะอย่างยิ่งแสดงออกผ่านการบอกเล่าที่มีชื่อเสียงไม่ได้ถามหลักการ (ที่ @CandiedOrange ชอบที่จะพูดถึงตลอดเวลา :)) และตำหนิของประชาชนgetters และ setters

ในทางปฏิบัติจะส่งผลให้วัตถุมีทรัพยากรที่จำเป็นทั้งหมดในการทำงานเช่นฐานข้อมูลสิ่งอำนวยความสะดวกการแสดงผล ฯลฯ

ดังนั้นเมื่อพิจารณาตัวอย่างของคุณเวอร์ชัน OOP ของฉันจะมีลักษณะดังนี้:

class Page
{
    private $data;
    private $renderer;

    public function __construct(ICanRender $renderer, $data)
    {
        $this->renderer = $renderer;
        $this->data = $data;
    }

    public function render()
    {
        $this->renderer->render($this->data);
    }
}

ในกรณีที่คุณสนใจเดวิดเวสต์พูดคุยเกี่ยวกับหลักการ OOP เดิมในหนังสือของเขาคิดวัตถุ


1
ใครสนใจสิ่งที่ใครบางคนพูดเกี่ยวกับการพัฒนาซอฟต์แวร์เมื่อ 15 ปีที่แล้วยกเว้นเนื่องจากความสนใจในอดีต
David Arno

1
" ฉันสนใจสิ่งที่ชายผู้คิดค้นแนวคิดเชิงวัตถุกล่าวถึงสิ่งที่เป็นวัตถุ " ทำไม? นอกเหนือจากการกล่อมให้คุณใช้ความล้มเหลวใน "การอุทธรณ์ต่ออำนาจ" ในการโต้แย้งของคุณความคิดที่เป็นไปได้ของความคิดของนักประดิษฐ์ของคำที่มีต่อการประยุกต์ใช้คำว่า 15 ปีต่อมาคืออะไร?
David Arno

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

1
" อุทธรณ์ไปยังผู้มีอำนาจเข้าใจผิดไม่สามารถนำมาใช้ที่นี่ " ... " ดังนั้นชุดของแนวคิดที่ในความคิดของคุณแสดงถึง OOP เป็นจริงผิด [เพราะมันเป็นความผิดเพี้ยนของคำนิยามเดิม] " ฉันเอาไปคุณไม่ทราบว่าสิ่งที่ดึงดูดผู้มีอำนาจเข้าใจผิดคืออะไร? เบาะแส: คุณใช้ที่นี่ :)
David Arno

1
@ David Arno ดังนั้นผู้อุทธรณ์ถึงอำนาจผิดทุกคนเหรอ? คุณต้องการ "ดึงดูดความคิดเห็นของฉันหรือไม่" ใครสักคนทุกอ้างอิงลุง Bobism คุณจะบ่นเกี่ยวกับการอุทธรณ์ไปยังผู้มีอำนาจ Zapadio เป็นแหล่งที่นับหน้าถือตาคุณสามารถไม่เห็นด้วยหรืออ้างอิงแหล่งที่มาของความขัดแย้ง แต่ repeatefly บ่นคนที่ได้ให้การอ้างอิงไม่สร้างสรรค์?..
user949300

2

$page->renderMe();

ที่นี่เรามีpageความรับผิดชอบอย่างสมบูรณ์สำหรับการแสดงผลของตัวเอง มันอาจจะมาพร้อมกับการแสดงผลผ่านตัวสร้างหรือมันอาจมีฟังก์ชั่นที่สร้างขึ้นมา

ฉันจะละเว้นกรณีแรก (มาพร้อมกับการแสดงผลผ่านตัวสร้าง) ที่นี่เพราะมันค่อนข้างคล้ายกับการผ่านมันเป็นพารามิเตอร์ แต่ฉันจะดูข้อดีข้อเสียของฟังก์ชั่นที่สร้างขึ้น

มืออาชีพคือมันช่วยให้ encapsulation ในระดับสูงมาก หน้าเว็บไม่จำเป็นต้องเปิดเผยอะไรเลยเกี่ยวกับสถานะด้านในโดยตรง มันเปิดเผยผ่านการแสดงผลของตัวเองเท่านั้น

คอนดิชั่นคือมันทำลายหลักการความรับผิดชอบเดียว (SRP) เรามีคลาสที่รับผิดชอบในการห่อหุ้มสถานะของเพจและยังมีการกำหนดรหัสอย่างเข้มงวดเกี่ยวกับวิธีการแสดงผลตัวเองและน่าจะเป็นหน้าที่ความรับผิดชอบอื่น ๆ อีกมากมายเนื่องจากวัตถุควร "ทำสิ่งต่าง ๆ ให้กับตัวเอง "

$page->renderMe($renderer);

ที่นี่เรายังคงต้องการหน้าเพื่อให้สามารถแสดงผลได้เอง แต่เรากำลังจัดหาหน้าที่มีออบเจกต์ตัวช่วยที่สามารถทำการเรนเดอร์จริง สองสถานการณ์สามารถเกิดขึ้นได้ที่นี่:

  1. หน้าเพียงแค่ต้องรู้กฎการแสดงผล (วิธีการที่จะเรียกตามลำดับ) เพื่อสร้างการแสดงผลที่ Encapsulation ถูกเก็บรักษาไว้ แต่ SRP ยังคงใช้งานไม่ได้เนื่องจากหน้ายังคงต้องดูแลกระบวนการแสดงผลหรือ
  2. หน้าเรียกวิธีการเดียวบนวัตถุของ renderer ส่งผ่านรายละเอียดมาเราเข้าใกล้การเคารพ SRP มากขึ้น แต่ตอนนี้เราได้ลดการห่อหุ้ม

$renderer->renderPage($page);

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

นอกจากนี้เราได้สร้างปัญหาใหม่: โปรแกรมแสดงภาพอยู่ในระดับเดียวกันกับคลาสของหน้าเว็บอย่างแน่นหนา จะเกิดอะไรขึ้นเมื่อเราต้องการแสดงสิ่งที่แตกต่างจากหน้า

ไหนดีที่สุด ไม่มีของพวกเขา พวกเขามีข้อบกพร่อง


ไม่เห็นด้วยที่ V3 เคารพ SRP Renderer มีเหตุผลในการเปลี่ยนแปลงอย่างน้อย 2 ประการ: หากหน้ามีการเปลี่ยนแปลงหรือหากวิธีที่คุณแสดงนั้นเปลี่ยนไป และหนึ่งในสามซึ่งคุณครอบคลุมถ้า Renderer ต้องการแสดงวัตถุอื่นนอกเหนือจากหน้า มิฉะนั้นการวิเคราะห์ที่ดี
949300

2

คำตอบสำหรับคำถามนี้ชัดเจน มันคือ$renderer->renderPage($page);การดำเนินการที่ถูกต้อง เพื่อให้เข้าใจว่าเรามาถึงข้อสรุปนี้เราต้องเข้าใจการห่อหุ้ม

หน้าคืออะไร? มันเป็นตัวแทนของจอแสดงผลที่ใครบางคนจะใช้ "คน" นั้นอาจเป็นมนุษย์หรือบอท โปรดทราบว่าการPageเป็นตัวแทนและไม่แสดงตัวเอง มีตัวแทนอยู่โดยไม่มีตัวแทนหรือไม่? เป็นหน้าเว็บที่ไม่มีตัวเรนเดอร์หรือไม่ คำตอบคือใช่การเป็นตัวแทนสามารถอยู่ได้โดยไม่ต้องเป็นตัวแทน เพื่อเป็นตัวแทนของเวทีต่อมา

renderer ที่ไม่มีหน้าคืออะไร? renderer สามารถแสดงโดยไม่มีหน้าได้หรือไม่? ไม่ดังนั้นอินเทอร์เฟซ Renderer จึงต้องการrenderPage($page);วิธีการ

มีอะไรผิดปกติกับ$page->renderMe($renderer);?

มันเป็นความจริงที่ว่าจะยังคงมีการเรียกร้องภายในrenderMe($renderer) $renderer->renderPage($page);สิ่งนี้ละเมิดกฎหมายของ Demeterซึ่งระบุ

แต่ละหน่วยควรมีความรู้ จำกัด เกี่ยวกับหน่วยอื่น ๆ เท่านั้น

Pageชั้นไม่สนใจว่ามีอยู่Rendererในจักรวาล มันสนใจเพียงแค่การเป็นตัวแทนของหน้า ดังนั้นชั้นเรียนหรืออินเตอร์เฟซที่ไม่ควรจะกล่าวถึงภายในRendererPage


ปรับปรุงคำตอบ

หากฉันได้รับคำถามของคุณถูกต้องPageSnippetชั้นควรจะเกี่ยวข้องกับการเป็นตัวอย่างของหน้า

class PageSnippet
{    
    /** string */
    private $html;

    function __construct($data = ['html' => '']): void
    {
        $this->html = $data['html'];
    }

   public function getHtml()
   {
       return $this->html;
   }
}

PdfRenderer เกี่ยวข้องกับการแสดงผล

class PdfRenderer
{
    /**@var TCPDF */
    protected $pdf;

    function __construct(TCPDF $pdf = new TCPDF())
    {
        $this->pdf = $pdf;
    }

    function runTemplate(string $html): void
    {
        $this->pdf->writeHTML($html);
    }
}

การใช้งานของลูกค้า

$renderer = new PdfRenderer();
$snippet = new PageSnippet(['html' => '<html />']);
$renderer->runTemplate($snippet->getHtml());

ข้อควรพิจารณา:

  • การปฏิบัติที่ไม่ดีในการผ่าน$dataเป็นอาร์เรย์เชื่อมโยง ควรเป็นตัวอย่างของคลาส
  • ข้อเท็จจริงที่ว่ารูปแบบหน้ามีอยู่ภายในhtmlคุณสมบัติของ$dataอาร์เรย์นั้นเป็นรายละเอียดเฉพาะสำหรับโดเมนของคุณและPageSnippetรับทราบรายละเอียดนี้

แต่ถ้านอกเหนือไปจากหน้าคุณมีรูปภาพบทความและ Triptichs? ในรูปแบบของคุณ Renderer จะต้องรู้เกี่ยวกับพวกเขาทั้งหมด มันรั่วเยอะมาก แค่คิดอาหาร
user949300

@ user949300: ถ้า Renderer จำเป็นต้องสามารถแสดงรูปภาพ ฯลฯ ได้อย่างชัดเจนก็ต้องรู้เกี่ยวกับพวกเขา
JacquesB

1
รูปแบบการปฏิบัติที่ดีที่สุด Smalltalk โดย Kent Beck แนะนำรูปแบบวิธีการกลับรายการซึ่งสนับสนุนทั้งสองอย่างนี้ บทความที่เชื่อมโยงแสดงให้เห็นว่าวัตถุรองรับprintOn:aStreamวิธีการ แต่สิ่งที่มันทำคือบอกกระแสที่จะพิมพ์วัตถุ การเปรียบเทียบกับคำตอบของคุณคือไม่มีเหตุผลที่คุณไม่สามารถมีทั้งหน้าที่สามารถเรนเดอร์ไปยัง renderer และ renderer ที่สามารถเรนเดอร์เพจได้ด้วยการติดตั้งเพียงครั้งเดียว
เกรแฮมลี

2
คุณจะต้องทำลาย / เหลวไหล SRP ไม่ว่าในกรณีใด ๆ แต่ถ้า Renderer ต้องการทราบวิธีการแสดงสิ่งต่าง ๆ มากมายนั่นคือ "ความรับผิดชอบหลายอย่าง" และถ้าเป็นไปได้ต้องหลีกเลี่ยง
user949300

1
ฉันชอบคำตอบของคุณ แต่ฉันถูกล่อลวงให้คิดว่าการPageไม่ตระหนักถึง $ renderer นั้นเป็นไปไม่ได้ ฉันเพิ่มรหัสลงในคำถามของฉันดูPageSnippetชั้นเรียน เป็นหน้าได้อย่างมีประสิทธิภาพ แต่ไม่สามารถอยู่ได้โดยไม่ต้องมีการอ้างอิงถึง$pdfซึ่งในความเป็นจริงเป็นตัวแสดงผล PDF บุคคลที่สามในกรณีนี้ .. อย่างไรก็ตามฉันคิดว่าฉันสามารถสร้างPageSnippetคลาสที่เก็บคำสั่งข้อความไว้ใน PDF ได้เท่านั้นและให้คลาสอื่นตีความคำแนะนำเหล่านั้น วิธีที่ฉันสามารถหลีกเลี่ยงการฉีด$pdfเข้าไปในPageSnippetที่ค่าใช้จ่ายของความซับซ้อนเป็นพิเศษ
เดนนิส

1

เป็นการดีที่คุณต้องการการพึ่งพาระหว่างคลาสน้อยที่สุดเท่าที่จะทำได้เนื่องจากมันลดความซับซ้อน คลาสควรมีการขึ้นต่อกันของคลาสอื่นหากจำเป็นจริงๆ

คุณระบุว่าPageมี "ชุดคำสั่งสำหรับตัวแสดงหน้า" ฉันจินตนาการถึงสิ่งนี้:

renderer.renderLine(x, y, w, h, Color.Black)
renderer.renderText(a, b, Font.Helvetica, Color.Black, "bla bla...")
etc...

ดังนั้นจึงเป็น$page->renderMe($renderer)เพราะเพจต้องการการอ้างอิงไปยังโหมดแสดงภาพ

แต่คำแนะนำในการเรนเดอร์ก็สามารถแสดงเป็นโครงสร้างข้อมูลแทนการโทรโดยตรงเช่น

[
  Line(x, y, w, h, Color.Black), 
  Text(a, b, Font.Helvetica, Color.Black, "bla bla...")
]

ในกรณีนี้ Renderer จริงจะได้รับโครงสร้างข้อมูลนี้จากหน้าและประมวลผลโดยดำเนินการตามคำแนะนำการแสดงผลที่สอดคล้องกัน ด้วยวิธีการดังกล่าวการพึ่งพาจะถูกย้อนกลับ - หน้าไม่จำเป็นต้องรู้เกี่ยวกับ Renderer แต่ Renderer ควรให้เพจที่สามารถแสดงผลได้ ดังนั้นตัวเลือกที่สอง:$renderer->renderPage($page);

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

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

renderPage($page, $renderer)

วิธีเดียวที่ฉันจะไม่แนะนำคือ$page->renderMe()เพราะมันแนะนำว่าหน้าเว็บสามารถมีโหมดแสดงภาพเดียวได้ แต่ถ้าคุณมีScreenRendererและเพิ่มPrintRenderer? หน้าเดียวกันอาจแสดงผลโดยทั้งคู่


ในบริบทของ EPUB หรือ HTML แนวคิดของหน้าเว็บไม่มีอยู่หากไม่มีตัวแสดงผล
mouviciel

1
@mouviciel: ฉันไม่แน่ใจว่าฉันเข้าใจสิ่งที่คุณหมายถึง แน่นอนว่าคุณสามารถมีหน้า HTML โดยไม่แสดงผลได้ เช่นหน้ากระบวนการรวบรวมข้อมูลของ Google โดยไม่แสดงผล
JacquesB

2
มีความคิดที่แตกต่างกันของหน้าคำ: ผลลัพธ์ของกระบวนการแบ่งหน้าเมื่อจัดรูปแบบหน้า HTML เพื่อพิมพ์บางทีสิ่งที่ @mouviciel มีอยู่ในใจ อย่างไรก็ตามในคำถามpageนี้กเป็นการป้อนข้อมูลอย่างชัดเจนไปยังโหมดแสดงภาพไม่ใช่เอาต์พุตเพื่อความคิดนั้นไม่เหมาะสม
Doc Brown

1

D ส่วนหนึ่งของ SOLIDกล่าวว่า

"Abstractions ไม่ควรขึ้นอยู่กับรายละเอียดรายละเอียดควรขึ้นอยู่กับ abstractions"

ดังนั้นระหว่างหน้าและ Renderer ซึ่งมีแนวโน้มที่จะเป็นนามธรรมที่มั่นคงมีโอกาสน้อยที่จะเปลี่ยนแปลงอาจเป็นตัวแทนของอินเตอร์เฟซ? ตรงกันข้าม "รายละเอียด" คืออะไร?

จากประสบการณ์ของฉันสิ่งที่เป็นนามธรรมมักจะเป็น Renderer ตัวอย่างเช่นมันอาจเป็น Stream หรือ XML แบบนามธรรมและเสถียร หรือบางรูปแบบมาตรฐานค่อนข้าง หน้าของคุณมีแนวโน้มที่จะเป็นวัตถุทางธุรกิจที่กำหนดเองซึ่งเป็น "รายละเอียด" และคุณมีวัตถุทางธุรกิจอื่น ๆ ที่จะแสดงผลเช่น "รูปภาพ", "รายงาน", "แผนภูมิ" ฯลฯ ... (อาจไม่ใช่ "tryptich" ดังที่แสดงความคิดเห็นของฉัน)

แต่เห็นได้ชัดว่ามันขึ้นอยู่กับการออกแบบของคุณ หน้าอาจเป็นนามธรรมตัวอย่างเช่นเทียบเท่าของ<article>แท็กHTML ที่มีส่วนย่อยมาตรฐาน และคุณมี "renderers" การรายงานทางธุรกิจที่กำหนดเองต่าง ๆ มากมาย ในกรณีนั้น Renderer ควรขึ้นอยู่กับหน้า


0

ฉันคิดว่าคลาสส่วนใหญ่สามารถแบ่งออกเป็นสองประเภท:

  • คลาสที่มีข้อมูล (ไม่แน่นอนหรือไม่เปลี่ยนรูปไม่สำคัญ)

เหล่านี้เป็นชั้นเรียนที่แทบไม่มีการพึ่งพากับสิ่งอื่นใด โดยทั่วไปจะเป็นส่วนหนึ่งของโดเมนของคุณ พวกเขาไม่ควรมีตรรกะหรือตรรกะเท่านั้นที่สามารถได้รับโดยตรงจากรัฐ คลาส Employee สามารถมีฟังก์ชั่นisAdultที่สามารถรับโดยตรงจากมันbirthDateแต่ไม่ใช่ฟังก์ชั่นhasBirthDayที่ต้องใช้ข้อมูลภายนอก (วันที่ปัจจุบัน)

  • คลาสที่ให้บริการ

คลาสเหล่านี้ทำงานกับคลาสอื่นที่มีข้อมูล โดยทั่วไปจะมีการกำหนดค่าครั้งเดียวและไม่เปลี่ยนรูป (ดังนั้นพวกเขาจึงทำหน้าที่ชนิดเดียวกันเสมอ) คลาสเหล่านี้อาจยังให้อินสแตนซ์ตัวช่วยอายุสั้น stateful เพื่อทำการดำเนินการที่ซับซ้อนมากขึ้นที่ต้องการรักษาสถานะบางอย่างในช่วงเวลาสั้น ๆ (เช่นคลาสตัวสร้าง)

ตัวอย่างของคุณ

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

ข้อมูลหรือในกรณีนี้คุณPageสามารถแสดงได้หลายวิธี มันสามารถแสดงผลเป็นหน้าเว็บเขียนลงดิสก์เก็บไว้ในฐานข้อมูลแปลงเป็น JSON อะไรก็ตาม คุณไม่ต้องการเพิ่มวิธีการในชั้นเรียนดังกล่าวสำหรับแต่ละกรณี (และสร้างการอ้างอิงในชั้นเรียนอื่น ๆ ทุกประเภทแม้ว่าชั้นเรียนของคุณควรจะมีเพียงข้อมูล)

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

ตัวอย่างเช่นคุณอาจมี a MobileRendererและ a StandardRendererทั้งการปรับใช้Rendererคลาส แต่มีการตั้งค่าที่แตกต่างกัน

เพื่อให้Pageมีข้อมูลและควรเก็บเป็นใบ้โซลูชันที่สะอาดที่สุดในกรณีนี้จะส่งผ่านPageไปยัง a Renderer:

$renderer->renderPage($page)

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