MVP — คืออะไร รูปแบบ Model-View-Presenter ใน iOS และ Android

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-02-16 เวลาอ่าน: 10 นาที

MVP (Model-View-Presenter) — รูปแบบทางสถาปัตยกรรมที่ Presenter ทำหน้าที่เป็นตัวกลางระหว่าง Model และ View ผ่านอินเทอร์เฟส ViewContract ต่างจาก MVC ที่ Controller จัดการ View โดยตรงผ่าน UIKit Presenter ไม่ขึ้นอยู่กับเฟรมเวิร์ก — มันทำงานผ่านนามธรรม ทำให้สามารถทดสอบได้โดยไม่ต้องใช้ Android SDK หรือ UIKit MVP ถูกใช้อย่างแพร่หลายในการพัฒนา Android ก่อน Jetpack และยังคงมีความสำคัญสำหรับโปรเจกต์เดิม อ่านเพิ่มเติมใน บทความของ Martin Fowler

ประเด็นสำคัญ

  • MVP — สามส่วนประกอบ: Model (ข้อมูล), View (อินเทอร์เฟส), Presenter (ตรรกะและสถานะ)
  • ViewContract — อินเทอร์เฟสที่ Presenter ใช้สื่อสารกับ View ทำให้มั่นใจในความสามารถในการทดสอบ
  • Presenter — มีตรรกะทางธุรกิจทั้งหมด ไม่ขึ้นอยู่กับคลาสแพลตฟอร์ม Android/iOS
  • Passive View — View เฉื่อยที่สุดเท่าที่จะทำได้ แสดงข้อมูลตามคำสั่งของ Presenter เท่านั้น
  • MVP vs MVC — Presenter ถูกทดสอบด้วย unit test, Controller ใน MVC ขึ้นอยู่กับ UIKit/Android Framework

MVP คืออะไร: แก่นแท้ของรูปแบบ Model-View-Presenter

MVP (Model-View-Presenter) — รูปแบบทางสถาปัตยกรรมที่ Martin Fowler เสนอในช่วงต้นทศวรรษ 2000 เพื่อเป็นวิวัฒนาการของ MVC เพื่อปรับปรุงความสามารถในการทดสอบอินเทอร์เฟสผู้ใช้ Model จัดการข้อมูลและตรรกะทางธุรกิจ View รับผิดชอบการเรนเดอร์และการประมวลผลอินพุตของผู้ใช้ Presenter เป็นส่วนประกอบศูนย์กลางที่รับเหตุการณ์จาก View ดึงข้อมูลจาก Model และสร้างสถานะสำหรับการแสดงผล

ความแตกต่างหลักระหว่าง MVP และ MVC — Presenter ไม่มีการอ้างอิงโดยตรงไปยัง View แต่ Presenter โต้ตอบกับ View ผ่านอินเทอร์เฟส ViewContract View ใช้อินเทอร์เฟสนี้และส่งตัวเองไปยัง Presenter สิ่งนี้ตัดการพึ่งพา UIKit (iOS) หรือ Android Framework — Presenter สามารถทดสอบแบบแยกส่วนด้วยการจำลอง ViewContract ใน MVC ตัวควบคุม UIViewController อัปเดต UILabel โดยตรง ใน MVP Presenter เรียก view.showName(name) และ View ตัดสินใจว่าจะแสดงอย่างไร

ส่วนประกอบความรับผิดชอบความสามารถในการทดสอบ
Modelข้อมูล ตรรกะทางธุรกิจ การเรียกเครือข่ายUnit test (ไม่ขึ้นกับ UI)
Viewการเรนเดอร์ UI การส่งเหตุการณ์ไปยัง Presenterการจำลองผ่านอินเทอร์เฟส
Presenterตรรกะทางธุรกิจ การจัดการสถานะ การนำทางUnit test (ผ่านการจำลอง ViewContract)

หลักการรับผิดชอบเดียว ใน MVP ปฏิบัติตามอย่างเคร่งครัดกว่าใน MVC: View รับผิดชอบเฉพาะการเรนเดอร์ Model รับผิดชอบข้อมูล Presenter รับผิดชอบตรรกะและการประสานงาน ในโปรเจกต์จริง Presenter ครอบคลุม 40–60% ของโค้ดหน้าจอ View — 20–30% Model — 20–30% การกระจายนี้ช่วยให้ทดสอบตรรกะทางธุรกิจหลักได้โดยไม่ต้องเริ่มโปรแกรมจำลอง Android หรือ iOS

MVP ใน Android: Presenter, ViewContract และ Activity

MVP ใน Android ใช้ Activity หรือ Fragment เป็น View ซึ่งใช้ ViewContract — อินเทอร์เฟสที่มีวิธีการแสดงข้อมูล Presenter ถูกสร้างใน Activity แนบ View เข้ากับตัวเองและจัดการการโหลดข้อมูล เมื่อหน้าจอหมุน Activity จะถูกสร้างใหม่ — Presenter สามารถรักษาไว้ผ่าน retain fragment หรือพื้นที่จัดเก็บภายนอก ซึ่งแก้ปัญหาการสูญเสียสถานะที่เป็นลักษณะเฉพาะของ MVC บริสุทธิ์

kotlin
// ViewContract — อินเทอร์เฟสสำหรับเชื่อมต่อ Presenter กับ View
interface UserView {
    fun showLoading()
    fun hideLoading()
    fun showUser(user: User)
    fun showError(message: String)
}

// Presenter — ชั้นตรรกะที่ทดสอบได้
class UserPresenter(
    private val repository: UserRepository
) {
    private var view: UserView? = null

    fun attachView(view: UserView) {
        this.view = view
    }

    fun detachView() {
        view = null
    }

    fun loadUser(userId: Int) {
        view?.showLoading()
        repository.getUser(userId) { result ->
            view?.hideLoading()
            result.onSuccess { user ->
                view?.showUser(user)
            }.onFailure { e ->
                view?.showError(e.message ?: "Unknown error")
            }
        }
    }
}

// View (Activity) ใช้อินเทอร์เฟส
class UserActivity : AppCompatActivity(), UserView {
    private val presenter = UserPresenter(UserRepository())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        presenter.attachView(this)
        presenter.loadUser(42)
    }

    override fun onDestroy() {
        presenter.detachView()
        super.onDestroy()
    }

    override fun showUser(user: User) { /* อัปเดต UI */ }
    override fun showLoading() { /* แสดง ProgressBar */ }
    override fun hideLoading() { /* ซ่อน ProgressBar */ }
    override fun showError(message: String) { /* แสดง Snackbar */ }
}

การจัดการวงจรชีวิต — ปัญหาสำคัญของ MVP บน Android Activity ถูกทำลายเมื่อหมุนหน้าจอ และ presenter.attachView() ถูกเรียกอีกครั้งใน onCreate() หากการโหลดข้อมูลเป็นแบบอะซิงโครนัส (RxJava, coroutines) เมื่อเสร็จสิ้น View อาจถูกแยกออกแล้ว วิธีแก้ปัญหา — ยกเลิกการสมัครสมาชิกใน detachView() หรือใช้ Loader จาก Support Library (สำหรับโปรเจกต์ที่ไม่มี Jetpack) ที่ IT Sectr เราใช้組合 MVP + RxJava เป็นเวลาหลายปีในโปรเจกต์เชิงพาณิชย์ — รูปแบบมีความเสถียรแต่ต้องมีวินัยในการจัดการการสมัครสมาชิก

Retain fragment — กลไกการรักษา Presenter เมื่อหมุนหน้าจอ Fragment ที่ไม่มี UI (setRetainInstance(true)) มีอายุยืนกว่า Activity และเก็บการอ้างอิงไปยัง Presenter เมื่อ Activity ถูกสร้างใหม่ Fragment จะส่ง Presenter เดียวกันไปยัง Activity ใหม่ Retain fragment ถูกเลิกใช้งานตั้งแต่ AndroidX แต่เวอร์ชันก่อน Jetpack (Fragment.setRetainInstance) ยังคงทำงานในโปรเจกต์เดิม ในการพัฒนาสมัยใหม่ Google แนะนำ ViewModel แทน retain fragment

MVP ใน iOS: Presenter และ View Protocol

MVP ใน iOS สร้างขึ้นผ่านโปรโตคอล View UIViewController ใช้โปรโตคอล Presenter ไม่นำเข้า UIKit และสามารถทดสอบได้อย่างแท้จริง ต่างจาก Apple MVC ที่ UIViewController มีตรรกะและการเชื่อมต่อ IBOutlet โดยตรง Presenter จัดการสถานะและสั่ง View ผ่านเมธอดของโปรโตคอล View ไม่ตัดสินใจ — มันดำเนินการคำสั่งของ Presenter: showUser, showLoading, navigateToProfile

swift
import Foundation

// View Protocol — นามธรรมสำหรับ Presenter
protocol UserViewProtocol: AnyObject {
    func showLoading()
    func hideLoading()
    func display(user: User)
    func displayError(message: String)
}

// Presenter — ตรรกะบริสุทธิ์ ไม่มี UIKit
final class UserPresenter {
    private weak var view: UserViewProtocol?
    private let service: UserServiceProtocol

    init(service: UserServiceProtocol) {
        self.service = service
    }

    func attach(view: UserViewProtocol) {
        self.view = view
    }

    func detach() {
        view = nil
    }

    func loadUser(id: Int) {
        view?.showLoading()
        service.fetchUser(id: id) { [weak self] result in
            guard let self else { return }
            self.view?.hideLoading()
            switch result {
            case .success(let user):
                self.view?.display(user: user)
            case .failure(let error):
                self.view?.displayError(message: error.localizedDescription)
            }
        }
    }
}

// View (UIViewController) ใช้โปรโตคอล
final class UserViewController: UIViewController, UserViewProtocol {
    private let presenter = UserPresenter(service: UserService())

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter.attach(view: self)
        presenter.loadUser(id: 42)
    }

    func display(user: User) {
        nameLabel.text = user.name
    }
    // ... เมธอดที่เหลือของโปรโตคอล
}

Weak reference ไปยัง View — เงื่อนไขบังคับใน iOS MVP UIViewController สามารถถูกทำลาย (pop จาก navigation stack) และ closure ใน Presenter จะสร้าง retain cycle การอ้างอิงแบบอ่อน (weak var) รับประกันว่า View จะถูกปล่อยเมื่อออกจากหน้าจอ โดยไม่คำนึงถึงการดำเนินการแบบอะซิงโครนัสใน Presenter ใน Android ปัญหาที่คล้ายกันแก้ไขผ่าน detachView() — การเรียกใน onDestroy() จะล้างการอ้างอิงไปยัง View

Passive View vs Supervising Controller — สองรูปแบบของ MVP โดย Martin Fowler Passive View: View ไม่มีตรรกะ Presenter จัดการสถานะอย่างสมบูรณ์ Supervising Controller: View เองทำการผูกข้อมูลอย่างง่าย (เช่น ผ่าน data binding) Presenter แทรกแซงเฉพาะในสถานการณ์ที่ซับซ้อน ในการพัฒนาโมบาย Passive View ถูกใช้บ่อยกว่า — ให้ความสามารถในการทดสอบสูงสุดและการคาดเดาสถานะหน้าจอได้

ความแตกต่างระหว่าง MVP และ MVC และข้อดีของการทดสอบ

ความแตกต่างหลัก ระหว่าง MVP และ MVC คือวิธีการสื่อสารกับ View ใน MVC Controller มีการอ้างอิงโดยตรงไปยัง View (UIViewController.IBOutlets, Activity.findViewById) ใน MVP Presenter โต้ตอบกับ View ผ่านอินเทอร์เฟส ViewContract ความแตกต่างนี้เปลี่ยนแปลงความสามารถในการทดสอบอย่างสิ้นเชิง: วัตถุจำลองที่ใช้ ViewContract ช่วยให้ทดสอบตรรกะของ Presenter โดยไม่ต้องเริ่มแอป โปรแกรมจำลอง หรือเฟรมเวิร์ก UI

เกณฑ์MVCMVP
การเชื่อมต่อกับ Viewโดยตรง (Controller → View)ผ่านอินเทอร์เฟส (Presenter → ViewContract)
การทดสอบตรรกะต้องใช้ UIKit/Android FrameworkUnit test โดยไม่พึ่งพาแพลตฟอร์ม
วงจรชีวิตController อยู่กับหน้าจอPresenter อยู่ได้นานกว่า (retain)
ความซับซ้อนน้อยที่สุด+1 อินเทอร์เฟสต่อหน้าจอ
Massive Controllerปัญหาทั่วไปตรรกะใน Presenter, View บาง

ตัวอย่าง unit test ของ Presenter ใน Kotlin: สร้าง UserView จำลอง ส่งไปยัง Presenter เรียก loadUser ตรวจสอบว่า showUser ถูกเรียกด้วยข้อมูลที่ถูกต้อง การทดสอบดำเนินการในหน่วยมิลลิวินาที ไม่ต้องใช้โปรแกรมจำลอง บน iOS คล้ายกัน — OCMock หรือ stub โปรโตคอลตรวจสอบการเรียกเมธอด UserViewProtocol ในโปรเจกต์ของ IT Sectr ที่ใช้ MVP ความครอบคลุมของ unit test ตรรกะทางธุรกิจสูงถึง 85–90% ซึ่งสูงกว่าโปรเจกต์ MVC ที่คล้ายกัน 2–3 เท่า

เมื่อใดที่ MVP เหมาะกว่า MVC — ในโปรเจกต์ที่มีข้อกำหนดด้านความเสถียรสูง: แอปธนาคาร ระบบการแพทย์ เครื่องชำระเงิน ในสาขาเหล่านี้ต้นทุนของข้อผิดพลาดสูง และ unit test มีความสำคัญ ในโปรเจกต์หลัง MVP (เมื่อผลิตภัณฑ์อยู่ในตลาดแล้วแต่โค้ดเบสเป็นของเดิม) MVP ช่วยให้สามารถแยกตรรกะจาก Massive View Controller ออกเป็นชั้นที่ทดสอบได้อย่างค่อยเป็นค่อยไปโดยไม่ต้องเขียนสถาปัตยกรรมใหม่ทั้งหมด

ข้อจำกัดของ MVP และการเปลี่ยนไปใช้ MVVM

ข้อเสียหลักของ MVP — การเพิ่มขึ้นของจำนวนอินเทอร์เฟสและการจัดการการสมัครสมาชิกด้วยตนเอง แต่ละหน้าจอต้องการอย่างน้อยหนึ่ง ViewContract + Presenter สำหรับ 50 หน้าจอ — 50 อินเทอร์เฟสและ 50 คลาส Presenter ใน MVVM ViewModel แทนที่ Presenter และใช้กลไกแบบรีแอกทีฟ (LiveData, StateFlow, ObservableObject) ซึ่งขจัดความจำเป็นในการ attach/detach ด้วยตนเองและอินเทอร์เฟส ViewContract

RxJava และ MVP — การรวมกันที่ได้รับความนิยมใน Android 2015–2019 Presenter สมัครสมาชิก Observable จาก Repository แสดงผลผ่าน ViewContract ปัญหา: disposable ต้องถูกยกเลิกอย่างชัดเจนใน detachView() มิฉะนั้นการรั่วไหลของการสมัครสมาชิกจะทำให้เกิดการขัดข้องเมื่ออัปเดต View ที่ถูกแยก ไลบรารี RxLifecycle และ AutoDispose ทำให้การยกเลิกการสมัครเป็นอัตโนมัติบางส่วนแต่เพิ่มการพึ่งพา ที่ IT Sectr เราเปลี่ยนจาก MVP+RxJava เป็น MVVM+Flow ในปี 2020 — โค้ดสั้นลง 25–30% เนื่องจากการกำจัด ViewContract

การย้ายจาก MVP ไปยัง MVVM — กระบวนการแบบเป็นขั้นตอน 1) แทนที่ ViewContract ด้วย LiveData/StateFlow ใน Presenter 2) ลบเมธอด attach/detach — การสมัครสมาชิกผ่าน observe() 3) เปลี่ยนชื่อ Presenter เป็น ViewModel 4) รวม DI (Hilt/Koin) สำหรับ ViewModelFactory การย้ายหนึ่งหน้าจอใช้เวลา 2–4 ชั่วโมง ทั้งโค้ดเบส — 2–4 สัปดาห์สำหรับโปรเจกต์ที่มี 50–100 หน้าจอ หลังการย้าย อินเทอร์เฟส ViewContract ถูกลบ โค้ดลดลง การทดสอบยังคงอยู่

MVP ในการพัฒนาสมัยใหม่ — รูปแบบยังมีชีวิตอยู่แต่ยอมให้ MVVM และ MVI Google แนะนำอย่างเป็นทางการให้ใช้ MVVM กับ Jetpack สำหรับโปรเจกต์ใหม่ Apple — MVVM กับ SwiftUI อย่างไรก็ตาม ความรู้เกี่ยวกับ MVP จำเป็นสำหรับการทำงานกับโค้ดเดิม: แอป Android หลายร้อยรายการบน Google Play ยังคงทำงานบน MVP รวมถึงแอปของธนาคารขนาดใหญ่ ผู้ค้าปลีก และบริษัทขนส่ง การเข้าใจ MVP เป็นพื้นฐานสำหรับการเรียนรู้ MVI และ Clean Architecture เนื่องจาก Presenter เป็นบรรพบุรุษโดยตรงของ Use Case ในศัพท์ของ Robert Martin

คำถามที่พบบ่อย

MVP แตกต่างจาก MVC อย่างไร?

ใน MVP Presenter โต้ตอบกับ View ผ่านอินเทอร์เฟส ViewContract ไม่ใช่โดยตรง ใน MVC Controller มีการอ้างอิงโดยตรงไปยัง View ผ่าน IBOutlet/findViewById MVP ช่วยให้ทดสอบ Presenter ด้วย unit test โดยไม่ต้องใช้ iOS Simulator หรือ Android Emulator เนื่องจาก Presenter ไม่ขึ้นกับ UIKit หรือ Android Framework MVC ต้องการให้เริ่มแอปเพื่อทดสอบคอนโทรลเลอร์

เมื่อใดควรใช้ MVP แทน MVVM?

MVP เหมาะสมในโปรเจกต์เดิมที่สร้างบนรูปแบบนี้แล้ว และในแอปที่ไม่รองรับกลไกแบบรีแอกทีฟ (LiveData, StateFlow, Combine) สำหรับโปรเจกต์ใหม่ Google แนะนำ MVVM กับ Jetpack (Android) และ Apple แนะนำ MVVM กับ SwiftUI (iOS) MVP ยังคงเป็นตัวเลือกที่ดีที่สุดสำหรับโปรเจกต์บน UIKit บริสุทธิ์ที่ไม่มี Combine เมื่อต้องการ unit test ของตรรกะทางธุรกิจ

จะแก้ปัญหาการสูญเสีย Presenter เมื่อหมุนหน้าจอได้อย่างไร?

บน Android — ใช้ retain fragment (setRetainInstance(true)) หรือ ViewModel จาก Jetpack retain fragment เก็บ Presenter เมื่อหมุนและส่งไปยัง Activity ใหม่ ViewModel ของ Google เป็นทางเลือกสมัยใหม่ที่รักษาสถานะอัตโนมัติเมื่อหมุนโดยไม่ต้องใช้ retain fragment บน iOS — Presenter ถูกสร้างใหม่ทุก viewDidLoad แต่ถูกแคชในบริการประสานงานแยกต่างหาก

ต้องใช้กี่คลาสสำหรับหนึ่งหน้าจอใน MVP?

อย่างน้อย 4: อินเทอร์เฟส ViewContract, การใช้งาน ViewContract (Activity/Fragment), Presenter, Model (Repository) หากใช้ Dagger/Hilt จะเพิ่มโมดูล DI สำหรับ 50 หน้าจอ — มากกว่า 200 คลาส MVVM ลดลง 1 ไฟล์ต่อหน้าจอ (ไม่ต้องใช้ ViewContract) MVI เพิ่มคลาส State และ Intent จำนวนคลาสเป็นข้อโต้แย้งหลักต่อ MVP ในโปรเจกต์ขนาดใหญ่

ความแตกต่างระหว่าง Passive View และ Supervising Controller ใน MVP คืออะไร?

Passive View — View ไม่มีตรรกะ Presenter จัดการสถานะและข้อมูลอย่างสมบูรณ์ Supervising Controller — View เองทำการผูกข้อมูลอย่างง่าย (data binding) Presenter แทรกแซงเฉพาะในสถานการณ์ที่ซับซ้อน ในการพัฒนาโมบาย Passive View มีอำนาจเหนือกว่า — ให้ความสามารถในการทดสอบและการคาดเดาสูงสุด Supervising Controller ใช้ในเฟรมเวิร์กเว็บ (ASP.NET Web Forms, GWT)

สรุป

  • MVP (Model-View-Presenter) — วิวัฒนาการของ MVC ที่มีชั้น Presenter ที่ทดสอบได้ผ่านอินเทอร์เฟส ViewContract
  • ViewContract — อินเทอร์เฟสที่แยก View ออกจาก Presenter ทำให้สามารถทดสอบแบบจำลองได้
  • Passive View — รูปแบบ MVP ที่มีอิทธิพลในการพัฒนาโมบายที่มี View แบบเฉื่อย
  • Presenter — มีตรรกะทางธุรกิจ ไม่ขึ้นกับ UIKit หรือ Android Framework
  • MVP vs MVC — MVP แก้ปัญหาการทดสอบ แต่เพิ่ม 1 อินเทอร์เฟสต่อหน้าจอ
  • Android retain fragment — การรักษา Presenter เมื่อหมุนหน้าจอก่อน Jetpack ViewModel
  • การย้ายไปยัง MVVM — การแทนที่ ViewContract ด้วย LiveData/StateFlow ลดโค้ดลง 25–30%

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม