Model-View-Presenter ใน WinForms


91

ฉันพยายามใช้วิธี MVP เป็นครั้งแรกโดยใช้ WinForms

ฉันพยายามเข้าใจการทำงานของแต่ละเลเยอร์

ในโปรแกรมของฉันฉันมีปุ่ม GUI ที่เมื่อคลิกแล้วจะเปิดหน้าต่าง openfiledialog

ดังนั้นการใช้ MVP GUI จะจัดการกับเหตุการณ์การคลิกปุ่มแล้วเรียก Presenter.openfile ();

ภายใน Presenter.openfile () ควรมอบหมายการเปิดไฟล์นั้นให้กับเลเยอร์โมเดลหรือเนื่องจากไม่มีข้อมูลหรือตรรกะในการประมวลผลควรดำเนินการตามคำขอและเปิดหน้าต่าง openfiledialog หรือไม่

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

เอาล่ะหลังจากอ่าน MVP แล้วฉันได้ตัดสินใจที่จะใช้ Passive View อย่างมีประสิทธิภาพฉันจะมีการควบคุมมากมายบน Winform ซึ่งจะจัดการโดยผู้นำเสนอและจากนั้นงานที่มอบหมายให้กับ Model (s) ประเด็นเฉพาะของฉันอยู่ด้านล่าง:

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

  2. สิ่งนี้จะเหมือนกันสำหรับการควบคุมข้อมูลใด ๆ บน Winform เนื่องจากฉันมี datagridview ด้วยหรือไม่

  3. แอพของฉันมีคลาสโมเดลจำนวนมากที่มีแอสเซมบลีเดียวกัน นอกจากนี้ยังรองรับสถาปัตยกรรมปลั๊กอินพร้อมปลั๊กอินที่ต้องโหลดเมื่อเริ่มต้น มุมมองจะเรียกเมธอดผู้นำเสนอหรือไม่ซึ่งจะเรียกวิธีที่โหลดปลั๊กอินและแสดงข้อมูลในมุมมองหรือไม่ ระดับใดที่จะควบคุมการอ้างอิงปลั๊กอิน มุมมองจะมีการอ้างอิงถึงพวกเขาหรือผู้นำเสนอหรือไม่?

  4. ฉันคิดถูกหรือไม่ว่ามุมมองควรจัดการทุกสิ่งเกี่ยวกับการนำเสนอตั้งแต่สีโหนดทรีวิวไปจนถึงขนาดดาต้ากริด ฯลฯ

ฉันคิดว่าสิ่งเหล่านี้เป็นข้อกังวลหลักของฉันและถ้าฉันเข้าใจว่ากระแสควรเป็นอย่างไรสำหรับสิ่งเหล่านี้ฉันคิดว่าฉันจะโอเค


ลิงค์นี้lostechies.com/derekgreer/2008/11/23/…อธิบายรูปแบบบางส่วนของ MVP มันสามารถพิสูจน์ได้ว่ามีประโยชน์นอกเหนือจากคำตอบที่ยอดเยี่ยมของ Johann
ak3nat0n

คำตอบ:


125

นี่คือความถ่อมตัวของฉันใน MVP และประเด็นเฉพาะของคุณ

ครั้งแรกสิ่งที่ผู้ใช้สามารถโต้ตอบกับหรือเพียงแค่แสดงเป็นมุมมอง กฎหมายพฤติกรรมและลักษณะของมุมมองดังกล่าวจะอธิบายโดยอินเตอร์เฟซ อินเทอร์เฟซนั้นสามารถใช้งานได้โดยใช้ WinForms UI, คอนโซล UI, เว็บ UI หรือแม้กระทั่งไม่มี UI เลย (โดยปกติเมื่อทดสอบผู้นำเสนอ) - การใช้งานที่เป็นรูปธรรมไม่สำคัญตราบเท่าที่เป็นไปตามกฎหมายของอินเทอร์เฟซมุมมอง .

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

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

ผลกระทบของประการที่สามคือ:

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

สำหรับปัญหาของคุณข้างต้นอาจมีลักษณะเช่นนี้ในโค้ดที่ค่อนข้างง่าย:

interface IConfigurationView
{
    event EventHandler SelectConfigurationFile;

    void SetConfigurationFile(string fullPath);
    void Show();
}

class ConfigurationView : IConfigurationView
{
    Form form;
    Button selectConfigurationFileButton;
    Label fullPathLabel;

    public event EventHandler SelectConfigurationFile;

    public ConfigurationView()
    {
        // UI initialization.

        this.selectConfigurationFileButton.Click += delegate
        {
            var Handler = this.SelectConfigurationFile;

            if (Handler != null)
            {
                Handler(this, EventArgs.Empty);
            }
        };
    }

    public void SetConfigurationFile(string fullPath)
    {
        this.fullPathLabel.Text = fullPath;
    }

    public void Show()
    {
        this.form.ShowDialog();        
    }
}

interface IConfigurationPresenter
{
    void ShowView();
}

class ConfigurationPresenter : IConfigurationPresenter
{
    Configuration configuration = new Configuration();
    IConfigurationView view;

    public ConfigurationPresenter(IConfigurationView view)
    {
        this.view = view;            
        this.view.SelectConfigurationFile += delegate
        {
            // The ISelectFilePresenter and ISelectFileView behaviors
            // are implicit here, but in a WinForms case, a call to
            // OpenFileDialog wouldn't be too far fetched...
            var selectFilePresenter = Gimme.The<ISelectFilePresenter>();
            selectFilePresenter.ShowView();
            this.configuration.FullPath = selectFilePresenter.FullPath;
            this.view.SetConfigurationFile(this.configuration.FullPath);
        };
    }

    public void ShowView()
    {
        this.view.SetConfigurationFile(this.configuration.FullPath);
        this.view.Show();
    }
}

นอกเหนือจากข้างต้นฉันมักจะมีIViewอินเทอร์เฟซพื้นฐานที่ฉันซ่อนShow()และมุมมองของเจ้าของหรือชื่อมุมมองใด ๆ ที่มุมมองของฉันมักจะได้รับประโยชน์

สำหรับคำถามของคุณ:

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

ฉันจะโทรIConfigurationView.SetTreeData(...)จากIConfigurationPresenter.ShowView()ก่อนที่จะโทรไปIConfigurationView.Show()

2. สิ่งนี้จะเหมือนกันสำหรับการควบคุมข้อมูลใด ๆ บน Winform เนื่องจากฉันมี datagridview ด้วยหรือไม่

ใช่ฉันจะเรียกIConfigurationView.SetTableData(...)มันว่า มันขึ้นอยู่กับมุมมองที่จะจัดรูปแบบข้อมูลที่กำหนดให้ ผู้นำเสนอเพียงแค่ปฏิบัติตามสัญญาของมุมมองที่ต้องการข้อมูลแบบตาราง

3. แอพของฉันมีคลาสโมเดลจำนวนมากที่มีแอสเซมบลีเดียวกัน นอกจากนี้ยังรองรับสถาปัตยกรรมปลั๊กอินพร้อมปลั๊กอินที่ต้องโหลดเมื่อเริ่มต้น มุมมองจะเรียกเมธอดผู้นำเสนอหรือไม่ซึ่งจะเรียกวิธีที่โหลดปลั๊กอินและแสดงข้อมูลในมุมมองหรือไม่ ระดับใดที่จะควบคุมการอ้างอิงปลั๊กอิน มุมมองจะมีการอ้างอิงถึงพวกเขาหรือผู้นำเสนอหรือไม่?

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

4. ฉันคิดถูกต้องหรือไม่ว่ามุมมองควรจัดการทุกอย่างเกี่ยวกับการนำเสนอตั้งแต่สีโหนดทรีวิวไปจนถึงขนาดดาต้ากริด

ใช่. ลองนึกถึงว่าผู้นำเสนอเป็นผู้นำเสนอ XML ที่อธิบายข้อมูลและมุมมองที่รับข้อมูลและนำสไตล์ชีต CSS ไปใช้ ในแง่ที่เป็นรูปธรรมผู้นำเสนออาจเรียกIRoadMapView.SetRoadCondition(RoadCondition.Slippery)และมุมมองนั้นทำให้ถนนเป็นสีแดง

แล้วข้อมูลสำหรับโหนดที่คลิกล่ะ?

5. ถ้าเมื่อฉันคลิกที่ treenodes ฉันควรส่งผ่านโหนดเฉพาะไปยังผู้นำเสนอจากนั้นผู้นำเสนอจะหาข้อมูลที่ต้องการจากนั้นถามโมเดลสำหรับข้อมูลนั้นก่อนที่จะนำเสนอกลับไปยังมุมมองหรือไม่

ถ้าเป็นไปได้ฉันจะส่งผ่านข้อมูลทั้งหมดที่จำเป็นเพื่อนำเสนอต้นไม้ในมุมมองในภาพเดียว แต่ถ้าข้อมูลบางส่วนมีขนาดใหญ่เกินไปที่จะส่งผ่านตั้งแต่เริ่มต้นหรือหากเป็นแบบไดนามิกและต้องการ "สแนปชอตล่าสุด" จากแบบจำลอง (ผ่านผู้นำเสนอ) ฉันจะเพิ่มสิ่งที่ต้องการevent LoadNodeDetailsEventHandler LoadNodeDetailsในอินเทอร์เฟซมุมมองเพื่อให้ ผู้นำเสนอสามารถสมัครสมาชิกดึงรายละเอียดของโหนดในLoadNodeDetailsEventArgs.Node(อาจจะผ่าน ID ของบางชนิด) จากโมเดลเพื่อให้มุมมองสามารถอัปเดตรายละเอียดโหนดที่แสดงเมื่อผู้รับมอบสิทธิ์ตัวจัดการเหตุการณ์กลับมา โปรดทราบว่าอาจจำเป็นต้องใช้รูปแบบ async หากการดึงข้อมูลอาจช้าเกินไปสำหรับประสบการณ์ของผู้ใช้ที่ดี


3
ฉันไม่คิดว่าคุณจำเป็นต้องแยกมุมมองและผู้นำเสนอ โดยปกติฉันจะแยกตัวแบบและผู้นำเสนอออกโดยให้ผู้นำเสนอฟังเหตุการณ์ของโมเดลและดำเนินการตามนั้น (อัปเดตมุมมอง) การมีผู้นำเสนอในมุมมองช่วยลดการสื่อสารระหว่างมุมมองและผู้นำเสนอ
kasperhj

11
@lejon: คุณบอกว่าการมีผู้นำเสนอในมุมมองช่วยลดการสื่อสารระหว่างมุมมองและผู้นำเสนอแต่ฉันไม่เห็นด้วยอย่างยิ่ง จุดยืนของฉันคือ: เมื่อมุมมองรู้เกี่ยวกับผู้นำเสนอดังนั้นสำหรับแต่ละเหตุการณ์มุมมองมุมมองจะต้องตัดสินใจว่าวิธีใดของผู้นำเสนอเป็นวิธีที่เหมาะสมในการเรียก นั่นคือ "2 จุดของความซับซ้อน" ตั้งแต่มุมมองที่ไม่ทราบจริงๆที่ดูเหตุการณ์ที่สอดคล้องกับที่วิธีการพรีเซนเตอร์ สัญญาไม่ได้ระบุอย่างนั้น
Johann Gerell

5
@lejon: ในทางกลับกันหากมุมมองดังกล่าวเปิดเผยเฉพาะเหตุการณ์ที่เกิดขึ้นจริงจากนั้นผู้นำเสนอเอง (ผู้ที่รู้ว่าต้องการทำอะไรเมื่อมีการดูเหตุการณ์) เพียงแค่สมัครสมาชิกเพื่อทำสิ่งที่ถูกต้อง นั่นเป็นเพียง "1 จุดของความซับซ้อน" ซึ่งในหนังสือของฉันดีกว่า "2 จุดของความซับซ้อน" ถึงสองเท่า โดยทั่วไปการมีเพศสัมพันธ์ที่น้อยลงหมายถึงต้นทุนการบำรุงรักษาที่น้อยลงตลอดการดำเนินโครงการ
Johann Gerell

9
ฉันก็มักจะใช้พรีเซนเตอร์ห่อหุ้มตามที่อธิบายไว้ในลิงค์นี้lostechies.com/derekgreer/2008/11/23/...ซึ่งในมุมมองที่เป็นเจ้าของ แต่เพียงผู้เดียวของพรีเซนเตอร์
ak3nat0n

3
@ ak3nat0n: สำหรับ MVP ทั้งสามรูปแบบที่อธิบายไว้ในลิงก์ที่คุณให้มาฉันเชื่อว่าคำตอบของ Johann นี้อาจสอดคล้องกับสไตล์ที่สามซึ่งมีชื่อว่าObservation Presenter Style : "ประโยชน์ของสไตล์ผู้นำเสนอแบบสังเกตการณ์ก็คือ มันแยกความรู้ของผู้นำเสนอออกจากมุมมองโดยสิ้นเชิงทำให้มุมมองไม่ไวต่อการเปลี่ยนแปลงภายในผู้นำเสนอ "
DavidRR

11

พรีเซนเตอร์ซึ่งมีทั้งหมดตรรกะในมุมมองที่ควรจะตอบสนองกับปุ่มถูกคลิกเป็น @JochemKempe กล่าวว่า presenter.OpenFile()ในแง่การปฏิบัติปุ่มคลิกเหตุการณ์โทรจัดการ ผู้นำเสนอจะสามารถกำหนดสิ่งที่ควรทำ

หากตัดสินใจว่าผู้ใช้ต้องเลือกไฟล์ผู้ใช้จะเรียกกลับเข้าสู่มุมมอง (ผ่านอินเทอร์เฟซมุมมอง) และให้มุมมองซึ่งมีเทคนิค UI ทั้งหมดแสดงไฟล์OpenFileDialog. นี่เป็นความแตกต่างที่สำคัญมากที่ผู้นำเสนอไม่ควรได้รับอนุญาตให้ดำเนินการที่เชื่อมโยงกับเทคโนโลยี UI ที่ใช้อยู่

จากนั้นไฟล์ที่เลือกจะถูกส่งกลับไปยังผู้นำเสนอซึ่งยังคงตรรกะของมัน สิ่งนี้อาจเกี่ยวข้องกับโมเดลหรือบริการใด ๆ ที่ควรจัดการกับการประมวลผลไฟล์

เหตุผลหลักในการใช้รูปแบบ MVP imo คือการแยกเทคโนโลยี UI ออกจากตรรกะมุมมอง ดังนั้นผู้นำเสนอจึงจัดระเบียบตรรกะทั้งหมดในขณะที่มุมมองทำให้แยกออกจากตรรกะ UI สิ่งนี้มีผลข้างเคียงที่ดีมากในการทำให้ผู้นำเสนอหน่วยทดสอบได้อย่างสมบูรณ์

อัปเดต:เนื่องจากผู้นำเสนอเป็นศูนย์รวมของตรรกะที่พบในมุมมองเฉพาะความสัมพันธ์มุมมองผู้นำเสนอจึงเป็น IMO ซึ่งเป็นความสัมพันธ์แบบหนึ่งต่อหนึ่ง และเพื่อวัตถุประสงค์ในทางปฏิบัติทั้งหมดอินสแตนซ์มุมมองเดียว (พูดแบบฟอร์ม) โต้ตอบกับอินสแตนซ์ผู้นำเสนอหนึ่งอินสแตนซ์ผู้นำเสนอหนึ่งอินสแตนซ์โต้ตอบกับอินสแตนซ์มุมมองเดียวเท่านั้น

ที่กล่าวว่าในการใช้ MVP กับ WinForms ผู้นำเสนอมักจะโต้ตอบกับมุมมองผ่านอินเทอร์เฟซที่แสดงถึงความสามารถ UI ของมุมมอง ไม่มีข้อ จำกัด ว่ามุมมองใดที่ใช้อินเทอร์เฟซนี้ดังนั้น "วิดเจ็ต" ที่แตกต่างกันอาจใช้อินเทอร์เฟซมุมมองเดียวกันและนำคลาสผู้นำเสนอกลับมาใช้ใหม่


ขอบคุณ. ดังนั้นใน Presenter.OpenFile () เมธอดมันไม่ควรมีรหัสที่จะแสดง openfiledialog? แต่ควรกลับไปที่มุมมองเพื่อแสดงหน้าต่างนั้น?
Darren Young

4
ใช่ฉันจะไม่ปล่อยให้ผู้นำเสนอเปิดกล่องโต้ตอบโดยตรงเพราะจะทำให้การทดสอบของคุณเสียหาย ไม่ว่าจะยกเลิกการโหลดไปที่มุมมองหรืออย่างที่ฉันเคยทำในบางสถานการณ์มีคลาส "FileOpenService" แยกต่างหากเพื่อจัดการการโต้ตอบของไดอะล็อกจริง ด้วยวิธีนี้คุณสามารถปลอมบริการเปิดไฟล์ระหว่างการทดสอบ การใส่รหัสดังกล่าวในบริการแยกต่างหากอาจทำให้คุณได้รับผลข้างเคียงที่นำกลับมาใช้ใหม่ได้ดี :)
Peter Lillevold

2

ผู้นำเสนอควรดำเนินการตามคำขอให้แสดงหน้าต่าง openfiledialog ตามที่คุณแนะนำ เนื่องจากผู้นำเสนอไม่จำเป็นต้องใช้ข้อมูลจากแบบจำลองจึงควรดำเนินการตามคำขอ

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


1
ขอบคุณสำหรับการตอบกลับ นอกจากนี้คุณจะมีผู้นำเสนอคนเดียวสำหรับมุมมองหรือไม่? และผู้นำเสนอคนนั้นจะจัดการคำขอหรือหากจำเป็นต้องใช้ข้อมูลก็จะมอบหมายให้คลาสโมเดลจำนวนเท่าใดก็ได้ที่ดำเนินการตามคำร้องขอนั้น ๆ ? เป็นวิธีที่ถูกต้องหรือไม่? ขอบคุณอีกครั้ง.
Darren Young

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