แนวคิดการใช้งาน Model-View-Presenter


34

ฉันพยายามเข้าใจวิธีนำ decoupling ที่ดีระหว่าง UI และโมเดลมาใช้ แต่ฉันมีปัญหาในการหาตำแหน่งที่จะแบ่งเส้นตรง

ฉันได้ดู Model-View-Presenter แล้ว แต่ฉันไม่แน่ใจว่าจะนำไปใช้อย่างไร ตัวอย่างเช่นมุมมองของฉันมีหลายกล่องโต้ตอบ ..

  • ควรมีคลาส View พร้อมอินสแตนซ์ของแต่ละกล่องโต้ตอบหรือไม่? จากนั้นในกรณีนั้นกล่องโต้ตอบควรโต้ตอบกับผู้นำเสนออย่างไร กล่าวคือ ถ้าไดอะล็อกแต่ละรายการต้องการขอข้อมูลจาก Model ผ่าน Presenter กล่องโต้ตอบนั้นควรได้รับการอ้างอิงไปยัง Presenter อย่างไร ผ่านการอ้างอิงไปยังมุมมองที่ให้ไว้ในระหว่างการก่อสร้าง?
  • ฉันคิดว่าบางทีมุมมองที่ควรจะเป็นระดับคงที่? จากนั้นกล่องโต้ตอบ GetView และรับผู้นำเสนอจากที่นั่น ...
  • ฉันกำลังคิดเกี่ยวกับการตั้งค่าผู้นำเสนอด้วยความเป็นเจ้าของมุมมองและรูปแบบ (ตรงข้ามกับมุมมองที่มีผู้นำเสนอและผู้นำเสนอมีรูปแบบ) และผู้นำเสนอลงทะเบียนการเรียกกลับสำหรับกิจกรรมในมุมมอง แต่นั่นทำให้ดูเหมือนมาก คู่เพิ่มเติม (หรือภาษาขึ้นอยู่กับอย่างน้อย)

ฉันพยายามที่จะ:

  1. ทำให้เป็น decoupled มากที่สุด
  2. ทำให้เป็นไปได้ที่จะจับคู่ Presenter / Model กับมุมมองของภาษาอื่น ๆ (ฉันไม่ได้ทำเรื่องระหว่างภาษา แต่ฉันรู้ว่ามันเป็นไปได้โดยเฉพาะยิ่งvoid(void)ฉันสามารถติดแอพ C # อย่างน้อยด้วย ห้องสมุด C ++ ...
  3. รักษารหัสให้สะอาดและเรียบง่าย

ดังนั้น .. ข้อเสนอแนะใด ๆ ว่าควรจัดการกับการโต้ตอบอย่างไร?


คุณดูที่บทความนี้หรือไม่: en.wikipedia.org/wiki/Model-view-presenter
Bernard

1
ฉันมี .. ฉันพบว่าเป็นระดับที่รวดเร็วและสูงฉันกำลังมองหาที่จะเข้าใจวิธีการจัดการหลายกล่องโต้ตอบในโครงการขนาดใหญ่ด้วยการมีเพศสัมพันธ์น้อยที่สุดเท่าที่เป็นไปได้ ..
trycatch

คำตอบ:


37

ยินดีต้อนรับสู่ความลาดชันลื่น คุณมาถึงจุดนี้แล้วด้วยความตระหนักว่าการโต้ตอบระหว่างกันของมุมมองโมเดลมีความผันแปรไม่สิ้นสุด MVC, MVP (Taligent, Dolphin, Passive View), MVVM เป็นเพียงชื่อไม่กี่

รูปแบบผู้นำเสนอมุมมองแบบเหมือนกับรูปแบบสถาปัตยกรรมส่วนใหญ่เปิดให้มีความหลากหลายและการทดลองมากมาย สิ่งหนึ่งที่การเปลี่ยนแปลงทั้งหมดมีเหมือนกันคือบทบาทของผู้นำเสนอในฐานะ "คนกลาง" ระหว่างมุมมองและโมเดล สองสิ่งที่พบได้บ่อยที่สุดคือมุมมองแบบพาสซีฟและตัวควบคุม / ผู้นำเสนอ - [ Fowler ] มุมมองแบบพาสซีฟถือว่า UI เป็นอินเทอร์เฟซแบบตื้นมากระหว่างผู้ใช้และผู้นำเสนอ มันมีน้อยมากถ้ามีเหตุผลใด ๆ มอบหมายความรับผิดชอบให้กับผู้นำเสนอ ผู้นำเสนอ / ผู้ควบคุมพยายามใช้ประโยชน์จากการเชื่อมโยงข้อมูลที่สร้างไว้ในเฟรมเวิร์ก UI จำนวนมาก UI จัดการการซิงโครไนซ์ข้อมูล แต่ขั้นตอนผู้นำเสนอ / ตัวควบคุมสำหรับตรรกะที่ซับซ้อนมากขึ้น ในทั้งสองกรณีโมเดลดูและผู้นำเสนอในรูปแบบสาม

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

สำหรับวิธีการนำเสนอมุมมองและได้รับการอ้างอิงไปยังแต่ละอื่น ๆ นี้บางครั้งเรียกว่าการเดินสายไฟ คุณมีสามทางเลือก:

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

MyForm.SomeEvent(Sender)
{
  Presenter.DoSomething(Sender.Data);
}

เนื่องจากผู้นำเสนอไม่มีการอ้างอิงไปยังมุมมองมุมมองจึงต้องส่งข้อมูลเป็นอาร์กิวเมนต์ ผู้นำเสนอสามารถสื่อสารกลับไปที่มุมมองโดยใช้ฟังก์ชั่น events / callback ซึ่งมุมมองต้องฟัง

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

Presenter.SomeEvent(Sender)
{
  DomainObject.DoSomething(View.SomeProperty);
  View.SomeOtherProperty = DomainObject.SomeData;
}

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

View.SomeEvent(Sender)
{
  Presenter.DoSomething();
}

Presenter.DoSomething()
{
  View.SomeProperty = DomainObject.Calc(View.SomeProperty);
}

มีปัญหาอื่น ๆ ที่ต้องพิจารณาด้วยรูปแบบ MVP ลำดับการสร้างอายุการใช้งานของวัตถุที่การเดินสายเกิดขึ้นการสื่อสารระหว่าง MVP triads แต่คำตอบนี้เติบโตนานพอแล้ว


1
สิ่งนี้มีประโยชน์อย่างแน่นอน การสื่อสารระหว่าง triads และอายุการใช้งานคือที่ฉันกำลังมีปัญหาในขณะนี้ที่ฉันเข้าใจบางอย่างนี้
trycatch

8

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

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

นี่คือตัวอย่าง ก่อนอื่นเรามีคลาสมุมมองด้วยวิธีง่าย ๆ ในการแสดงข้อความถึงผู้ใช้:

interface IView
{
  public void InformUser(string message);
}

ตอนนี้ที่นี่คือผู้นำเสนอ โปรดทราบว่าผู้นำเสนอใช้เวลาใน IView เป็นคอนสตรัคเตอร์

class Presenter
{
  private IView _view;
  public Presenter(IView view)
  {
    _view = view;
  }
}

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

class View : IView
{
  private Presenter _presenter;

  public View()
  {
    _presenter = new Presenter(this);
  }

  public void InformUser(string message)
  {
    MessageBox.Show(message);
  }
}

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

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

class Presenter
{
  public void DoSomething()
  {
    _view.InformUser("Starting model processing...");
  }
}

นี่คือที่ที่คุณได้รับ decoupling ของคุณ ผู้นำเสนอมีการอ้างอิงถึงการนำไปใช้งานของ IView เท่านั้นและไม่สนใจว่าจะนำไปใช้อย่างไร

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


4

ฉันคิดว่ามันสำคัญที่ต้องจำไว้ว่า Controller / Presenter เป็นที่ที่การกระทำนั้นเกิดขึ้นจริง การเชื่อมต่อในคอนโทรลเลอร์เป็นสิ่งที่หลีกเลี่ยงไม่ได้เนื่องจากมีความจำเป็น

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

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

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

Caveat:ฉันมาจาก Mac ทั้งหมดวัตถุประสงค์ -C พื้นหลังโกโก้ซึ่งผลักดันให้คุณเข้าสู่กระบวนทัศน์ MVC ไม่ว่าคุณจะต้องการหรือไม่ก็ตาม


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

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

2
ฉันเห็นด้วยอย่างยิ่งกับ P / C ที่จะต้องรับผิดชอบต่อการดู แต่ฉันคิดว่าส่วนที่ควรจะทำให้ MVP มีประสิทธิภาพคือความสามารถในการดึงไลบรารี่ UI ทั้งหมดออกและเชื่อมต่อใหม่กับการนวด (dllimporting และ whatnot) สามารถเรียกใช้อีกอันหนึ่งได้ สิ่งนี้จะไม่ยากขึ้นหรือไม่หากผู้ควบคุม / ผู้นำเสนอเข้าถึงกล่องโต้ตอบโดยตรง แน่นอนฉันไม่ได้พยายามที่จะโต้แย้งเพียงเข้าใจ :)
trycatch

ฉันคิดว่าพลังที่แท้จริงนั้นมาจากสองทิศทาง: สิ่งแรกคือสิ่งที่ View and the Model ไม่มีส่วนเกี่ยวข้องอื่น ๆ และสิ่งที่สองที่การพัฒนาส่วนใหญ่ทำงานเป็นเครื่องมือของแอปนั้นถูกบรรจุไว้อย่างเรียบร้อย หน่วยควบคุม แต่ความรับผิดชอบบางอย่างก็เกิดขึ้น อย่างน้อยที่สุดการสลับอินเทอร์เฟซส่วนใหญ่จะกระทำในคอนโทรลเลอร์และการเชื่อมโยงใด ๆ จากมุมมองจะน้อยที่สุด ดังที่คนอื่น ๆ ได้กล่าวเอาไว้ว่าตรรกะและการอนุญาตให้มีเลือดออก MVC ไม่ใช่กระสุนวิเศษ
Philip Regan

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

2

โดยทั่วไปคุณต้องการให้แบบจำลองของคุณครอบคลุมการโต้ตอบทั้งหมดกับแบบจำลองนั้น ตัวอย่างเช่นการกระทำ CRUD ของคุณ (สร้างอ่านอัปเดตลบ) เป็นส่วนหนึ่งของโมเดลทั้งหมด เช่นเดียวกันสำหรับการคำนวณพิเศษ มีสองเหตุผลที่ดีสำหรับสิ่งนี้:

  • มันง่ายกว่าที่จะทำการทดสอบรหัสนี้โดยอัตโนมัติ
  • มันเก็บทุกสิ่งที่สำคัญไว้ในที่เดียว

ในคอนโทรลเลอร์ของคุณ (แอป MVC) สิ่งที่คุณกำลังทำคือรวบรวมโมเดลที่คุณต้องใช้ในมุมมองของคุณและเรียกใช้ฟังก์ชันที่เหมาะสมในโมเดล การเปลี่ยนแปลงสถานะของโมเดลเกิดขึ้นในเลเยอร์นี้

มุมมองของคุณเพียงแสดงโมเดลที่คุณเตรียมไว้ โดยพื้นฐานแล้วมุมมองจะอ่านเฉพาะรุ่นและปรับเอาท์พุทให้เหมาะสม

การแม็พหลักการทั่วไปกับคลาสจริง

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

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