MVVM และรูปแบบการบริการ


14

ฉันกำลังสร้างแอปพลิเคชัน WPF โดยใช้รูปแบบ MVVM ในตอนนี้ viewmodels ของฉันเรียกเลเยอร์บริการเพื่อเรียกคืนโมเดล (ความเกี่ยวข้องกับ viewmodel) และแปลงเป็น viewmodels ฉันใช้ตัวสร้างการฉีดเพื่อส่งผ่านบริการที่จำเป็นสำหรับโมเดลวิวเวอร์

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

ฉันกำลังคิดถึงวิธีแก้ปัญหาบางอย่าง:

  1. การสร้างบริการแบบซิงเกิล (IServices) ที่มีบริการทั้งหมดที่มีอยู่เป็นส่วนต่อประสาน ตัวอย่าง: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve () ด้วยวิธีนี้ฉันไม่มีคอนสตรัคเตอร์ขนาดใหญ่ที่มีพารามิเตอร์บริการจำนวนมากในตัวพวกเขา

  2. การสร้างส่วนหน้าสำหรับบริการที่ใช้โดย viewModel และส่งผ่านออบเจ็กต์นี้ใน ctor ของ viewmodel ของฉัน แต่แล้วฉันจะต้องสร้างซุ้มสำหรับแต่ละมุมมองเชิงซ้อนของฉันและมันอาจจะค่อนข้างมาก ...

คุณคิดว่าเป็นวิธีที่ "ถูกต้อง" ในการใช้งานสถาปัตยกรรมประเภทนี้?


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

@AmyBlankenship: ดูรุ่นไม่ควรจะต้อง (หรือแม้กระทั่งจำเป็นต้องสามารถที่จะ) สร้างตัวเอง แต่หลีกเลี่ยงไม่ได้ในบางครั้งจะเป็นผู้รับผิดชอบสำหรับการสร้างแบบจำลองมุมมองอื่นคอนเทนเนอร์ IoC พร้อมการสนับสนุนจากโรงงานอัตโนมัติเป็นความช่วยเหลืออย่างมากที่นี่
Aaronaught

"บางครั้งจะ" และ "ควร" เป็นสัตว์สองชนิดที่แตกต่างกัน;)
Amy Blankenship

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

แน่นอนว่าต้องมีความสามารถในการขอให้มีการสร้างมุมมองไว้ที่ใดที่หนึ่ง แต่เพื่อให้ตัวเอง? ไม่ได้อยู่ในโลกของฉัน :) แต่อีกครั้งในโลกที่ฉันอาศัยอยู่เราเรียก VM ว่า "รูปแบบการนำเสนอ"
Amy Blankenship

คำตอบ:


22

ในความเป็นจริงการแก้ปัญหาทั้งสองนี้ไม่ดี

การสร้างบริการแบบซิงเกิล (IServices) ที่มีบริการทั้งหมดที่มีอยู่เป็นส่วนต่อประสาน ตัวอย่าง: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve () ด้วยวิธีนี้ฉันไม่มีคอนสตรัคเตอร์ขนาดใหญ่ที่มีพารามิเตอร์บริการจำนวนมากในตัวพวกเขา

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

การสร้างส่วนหน้าสำหรับบริการที่ใช้โดย viewModel และส่งผ่านออบเจ็กต์นี้ใน ctor ของ viewmodel ของฉัน แต่แล้วฉันจะต้องสร้างซุ้มสำหรับแต่ละมุมมองเชิงซ้อนของฉันและมันอาจจะค่อนข้างมาก ...

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

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

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

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

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

หากคุณทำสิ่งนี้ถูกต้องควรชัดเจนจากการดูที่รหัสเพราะคุณมีรูปแบบมุมมองสั้นกระชับรวบรัดและทดสอบได้


ใช่นั่นคือสิ่งที่ฉันจะทำ! ขอบคุณมากครับ
alfa-alfa

ฉันคิดว่าเขาลองแล้ว แต่ก็ไม่ประสบความสำเร็จ @ alfa-alfa
Euphoric

@Eurhoric: คุณ "ไม่ประสบความสำเร็จ" ในเรื่องนี้ได้อย่างไร อย่างที่โยดาบอกว่า: ไม่ต้องทำหรือไม่ต้องลอง
Aaronaught

@Aaraught ตัวอย่างเช่นเขาต้องการข้อมูลทั้งหมดในรูปแบบมุมมองเดียว บางทีเขามีตารางและคอลัมน์ที่แตกต่างกันมาจากบริการที่แตกต่างกัน คุณไม่สามารถทำอย่างนั้นกับองค์ประกอบ
ร่าเริง

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

1

ฉันสามารถเขียนหนังสือเกี่ยวกับเรื่องนี้ ... อันที่จริงฉันคือ;)

ก่อนอื่นไม่มีวิธีที่ "ถูกต้อง" ในระดับสากลในการทำสิ่งต่าง ๆ คุณต้องคำนึงถึงปัจจัยอื่น ๆ ด้วย

เป็นไปได้ว่าบริการของคุณละเอียดเกินไป การห่อ Services ด้วย Facades ที่ให้อินเทอร์เฟซที่ Viewmodel ที่เฉพาะเจาะจงหรือแม้แต่คลัสเตอร์ของ ViewModels ที่เกี่ยวข้องก็อาจใช้เป็นทางออกที่ดีกว่า

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

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


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

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

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

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

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

0

ทำไมไม่รวมกันทั้งสองอย่าง

สร้างส่วนหน้าและวางบริการทั้งหมดที่คุณใช้ดูรุ่น จากนั้นคุณสามารถมีซุ้มเดียวสำหรับทุกมุมมองของคุณรุ่นโดยไม่มีคำ S ที่ไม่ดี

หรือคุณสามารถใช้การฉีดคุณสมบัติแทนการฉีดคอนสตรัคเตอร์ แต่คุณต้องแน่ใจว่าสิ่งเหล่านั้นได้รับการฉีดอย่างถูกต้อง


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