วิธีเลือกสถาปัตยกรรม iOS ที่เหมาะสม (ตอนที่ 2)

MVC, MVP, MVVM, VIPER หรือ VIP

คุณสามารถปรึกษาส่วนหนึ่งได้ที่นี่

สถาปัตยกรรม iOS หลัก

ภาพรวมคร่าวๆ

MVC

เลเยอร์ MVC มีดังนี้:

M: ตรรกะทางธุรกิจ, เลเยอร์เครือข่ายและชั้นการเข้าถึงข้อมูล

V: UI Layer (สิ่ง UIKit, กระดานเรื่องราว, Xibs)

C: ประสานงานการไกล่เกลี่ยระหว่าง Model และ View

เพื่อให้เข้าใจ MVC เราต้องเข้าใจบริบทที่มันถูกประดิษฐ์ขึ้นมา MVC ถูกคิดค้นในวันพัฒนาเว็บเก่าโดยที่ Views ไม่มีสถานะ ในสมัยก่อนทุกครั้งที่เราต้องการการเปลี่ยนแปลงภาพในเว็บไซต์เบราว์เซอร์โหลด HTML ทั้งหมดอีกครั้ง ในเวลานั้นไม่มีแนวคิดเกี่ยวกับสถานะการดูและการเก็บรักษา

ตัวอย่างเช่นมีนักพัฒนาบางคนที่ผสมภายในไฟล์ HTML, PHP และการเข้าถึงฐานข้อมูลเดียวกัน ดังนั้นแรงจูงใจหลักของ MVC ก็คือการแยกเลเยอร์มุมมองออกจากเลเยอร์โมเดล สิ่งนี้เพิ่มความสามารถในการทดสอบของเลเยอร์โมเดล สมมุติว่าใน MVC เลเยอร์มุมมองและโมเดลไม่ควรรู้ซึ่งกันและกัน เพื่อให้สิ่งนี้เป็นไปได้เลเยอร์ตัวกลางชื่อคอนโทรลเลอร์ถูกประดิษฐ์ขึ้น นี่คือ SRP ที่นำไปใช้

ตัวอย่างของวงจร MVC:

  1. การกระทำของผู้ใช้ / เหตุการณ์ใน View Layer (เช่น: Refresh Action) ถูกเรียกใช้และการกระทำนั้นถูกสื่อสารไปยัง Controller
  2. คอนโทรลเลอร์ที่ขอข้อมูลไปยัง Model Layer
  3. สร้างแบบจำลองข้อมูลส่งคืนไปยังตัวควบคุม
  4. คอนโทรลเลอร์แจ้งว่าสำหรับการอัปเดตสถานะของเขาด้วยข้อมูลใหม่
  5. ดูอัปเดตสถานะของเขา

Apple MVC

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

ViewController มีมุมมองและเป็นเจ้าของโมเดล ปัญหาคือเราใช้ในการเขียนรหัสควบคุมเช่นเดียวกับรหัสดูใน ViewController

MVC มักจะสร้างปัญหาที่เรียกว่า Massive View Controller แต่เกิดขึ้นและกลายเป็นสิ่งที่สำคัญในแอปที่มีความซับซ้อนเพียงพอ

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

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

MVC กับ MVP

วิธีที่คุณสามารถดูแผนภาพของ MVP นั้นคล้ายกับ MVC มาก

MVC เป็นก้าวไปข้างหน้า แต่ก็ยังถูกทำเครื่องหมายโดยไม่มีหรือเงียบเกี่ยวกับบางสิ่ง

ในขณะเดียวกันเวิลด์ไวด์เว็บก็เติบโตขึ้นและสิ่งต่าง ๆ ในชุมชนของนักพัฒนาก็พัฒนาขึ้น ตัวอย่างเช่นโปรแกรมเมอร์เริ่มใช้ Ajax และโหลดเฉพาะบางส่วนของหน้าแทนที่จะเป็นหน้า HTML ทั้งหมดในครั้งเดียว

ใน MVC ฉันคิดว่าไม่มีสิ่งใดที่จะบ่งบอกว่าคอนโทรลเลอร์ไม่ควรรู้ถึงการใช้งานมุมมอง (ขาด) อย่างเฉพาะเจาะจง

HTML เป็นส่วนหนึ่งของเลเยอร์มุมมองและหลายกรณีโง่เง่ามาก ในบางกรณีจะได้รับเฉพาะกิจกรรมจากผู้ใช้และแสดงเนื้อหาภาพของ GUI

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

ตรรกะการนำเสนอเป็นตรรกะที่ควบคุมวิธีการแสดง UI และองค์ประกอบ UI โต้ตอบกันได้อย่างไร ตัวอย่างคือตรรกะควบคุมเมื่อตัวบ่งชี้การโหลดควรเริ่มแสดง / เคลื่อนไหวและเมื่อใดควรหยุดแสดง / เคลื่อนไหว

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

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

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

ลองพิจารณาตัวอย่างต่อไปนี้: เรามีผู้ใช้ที่มีชื่อและนามสกุล ในมุมมองเราต้องแสดงชื่อผู้ใช้เป็น“ นามสกุลชื่อจริง” (เช่น“ Flores, Tiago”)

หากบทบาทของ Model คือการให้ข้อมูล "ดิบ" หมายความว่ารหัสในมุมมองจะเป็น:

ให้ firstName = userModel.getFirstName ()
ให้ lastName = userModel.getLastName ()
nameLabel.text = lastName +“,“ + firstName

ดังนั้นนี่จึงเป็นความรับผิดชอบของ View ในการจัดการตรรกะ UI แต่สิ่งนี้ทำให้ตรรกะ UI เป็นไปไม่ได้ที่จะทดสอบหน่วย

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

ให้ชื่อ = userModel.getDisplayName ()
nameLabel.text = name

MVP มีความชัดเจนเกี่ยวกับเรื่องนั้นและตรรกะของงานนำเสนอยังคงอยู่ใน Presenter Layer สิ่งนี้จะเพิ่มความสามารถในการทดสอบของเลเยอร์ผู้นำเสนอ ตอนนี้ Model และ Presenter Layer สามารถทดสอบได้ง่าย

โดยปกติในการใช้งาน MVP มุมมองจะถูกซ่อนอยู่ด้านหลังอินเตอร์เฟส / โปรโตคอลและไม่ควรมีการอ้างอิงถึง UIKit ในผู้นำเสนอ

สิ่งที่ควรทราบก็คือการพึ่งพาสกรรมกริยา

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

เลเยอร์ที่แตกต่างกันก็เปลี่ยนไปด้วยเหตุผลที่ต่างกันและในอัตราที่ต่างกัน ดังนั้นเมื่อคุณเปลี่ยนเลเยอร์คุณไม่ต้องการให้เกิดเอฟเฟ็กต์ / ปัญหารองในเลเยอร์อื่น

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

ดังนั้นสัญญา (โปรโตคอล) จึงสร้างการแยกระหว่างชั้น

MVP กับ MVVM

แผนภาพ MVVM

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

ใน MVP เราทำการเชื่อมโยงด้วยตนเองระหว่างผู้นำเสนอและมุมมองโดยใช้อินเทอร์เฟซ / โปรโตคอล
ใน MVVM เราทำการผูกข้อมูลอัตโนมัติโดยใช้ RxSwift, KVO หรือใช้กลไกที่มีชื่อสามัญและการปิด

ใน MVVM เราไม่จำเป็นต้องมีสัญญา (เช่น: java interface / iOS protocol) ระหว่าง ViewModel และ View เพราะเรามักจะสื่อสารผ่าน Observer Design Pattern

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

นึกถึงตรรกะการนำเสนอของตัวบ่งชี้การโหลด ใน MVP ผู้นำเสนอจะทำ ViewProtocol.showLoadingIndicator ใน MVVM อาจมีคุณสมบัติ isLoading ใน ViewModel เลเยอร์มุมมองผ่านการเชื่อมโยงข้อมูลอัตโนมัติตรวจพบเมื่อคุณสมบัตินี้มีการเปลี่ยนแปลงและรีเฟรชตัวเอง MVP มีความจำเป็นมากกว่า MVVM เพราะผู้นำเสนอให้คำสั่ง

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

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

PS: ฉันทำการอัปเดตบทความที่ทำให้มันโตขึ้นมากดังนั้นจึงจำเป็นต้องแบ่งออกเป็นสามส่วน คุณสามารถอ่านส่วนที่สามได้ที่นี่

ส่วนที่สองจบลงที่นี่ ข้อเสนอแนะทั้งหมดยินดีต้อนรับ ส่วนที่สามจะพูดคุยเกี่ยวกับ VIPER, VIP, การเขียนโปรแกรมเชิงโต้ตอบ, การแลกเปลี่ยน, ข้อ จำกัด และความรู้สึกตามบริบท

ขอบคุณสำหรับการอ่าน! หากคุณชอบบทความนี้โปรดปรบมือ
เพื่อให้คนอื่น ๆ สามารถอ่านได้ :)