MVP (Model-View-Presenter) — Presenter-in ViewContract interfeysi vasitəsilə Model və View arasında vasitəçi rolunu oynadığı memarlıq nümunəsi. MVC-dən fərqli olaraq, burada Controller birbaşa UIKit vasitəsilə View-i idarə edir, Presenter isə freymvorkdan asılı deyil — o, abstraksiya vasitəsilə işləyir, bu da onu Android SDK və ya UIKit olmadan test edilə bilən edir. MVP Android inkişafında Jetpack meydana gəlməzdən əvvəl geniş istifadə olunurdu və legacy layihələr üçün aktual olaraq qalır. Daha ətraflı — Martin Faulerin məqaləsində.
Əsas məqamlar
MVP (Model-View-Presenter) — 2000-ci illərin əvvəlində Martin Fauler tərəfindən istifadəçi interfeysinin test edilə bilənliyini yaxşılaşdırmaq üçün MVC-nin təkamülü olaraq təklif edilmiş memarlıq nümunəsi. Model məlumatları və biznes məntiqini idarə edir, View göstərmə və istifadəçi daxiletməsini emal etmək üçün cavabdehdir, Presenter — View-dən hadisələri alan, Model-dən məlumatları çıxaran və göstərmək üçün vəziyyəti formalaşdıran mərkəzi komponentdir.
MVP-nin MVC-dən əsas fərqi — Presenter-in View-ə birbaşa istinadı yoxdur. Bunun əvəzinə Presenter ViewContract interfeysi vasitəsilə View ilə qarşılıqlı əlaqə qurur. View bu interfeysi tətbiq edir və özünü Presenter-ə ötürür. Bu, UIKit (iOS) və ya Android Framework-dən asılılığı qırır — Presenter ViewContract-in mock tətbiqi ilə təcrid olunmuş şəkildə test edilir. MVC-də controller UIViewController birbaşa UILabel-i yeniləyir, MVP-də Presenter view.showName(name) metodunu çağırır, View isə necə göstərəcəyinə qərar verir.
| Komponent | Məsuliyyət | Test edilə bilənlik |
|---|---|---|
| Model | Məlumatlar, biznes məntiqi, şəbəkə çağırışları | Unit-testlər (UI-dən asılı deyil) |
| View | UI-nin göstərilməsi, hadisələrin Presenter-ə ötürülməsi | Interfeys vasitəsilə mock tətbiqi |
| Presenter | Biznes məntiqi, vəziyyətin idarə edilməsi, naviqasiya | Unit-testlər (ViewContract mock vasitəsilə) |
MVP-də tək məsuliyyət prinsipi MVC-dən daha ciddi şəkildə qorunur: View yalnız renderinqə, Model — məlumatlara, Presenter — məntiq və koordinasiyaya cavabdehdir. Real layihələrdə Presenter ekran kodunun 40–60%-ni, View — 20–30%-ni, Model — 20–30%-ni təşkil edir. Belə bölgü Android emulyatoru və ya iOS simulyatoru işə salmadan əsas biznes məntiqini test etməyə imkan verir.
MVP Android-də Activity və ya Fragment-i ViewContract-i tətbiq edən View kimi istifadə edir — məlumatları göstərmək üçün metodları olan interfeys. Presenter Activity-də yaradılır, View-i özünə bağlayır və yükləməni idarə edir. Ekran döndərildikdə Activity yenidən yaradılır — Presenter retain-fragment və ya xarici yaddaş vasitəsilə qoruna bilər, bu da təmiz MVC üçün xarakterik olan vəziyyət itkisi problemini həll edir.
// ViewContract — Presenterin View ilə əlaqəsi üçün interfeys
interface UserView {
fun showLoading()
fun hideLoading()
fun showUser(user: User)
fun showError(message: String)
}
// Presenter — test edilə bilən məntiq qatı
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 ?: "Naməlum xəta")
}
}
}
}
// View (Activity) interfeysi tətbiq edir
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-ni yenilə */ }
override fun showLoading() { /* ProgressBar göstər */ }
override fun hideLoading() { /* ProgressBar gizlət */ }
override fun showError(message: String) { /* Snackbar göstər */ }
}
Həyat dövrünün idarə edilməsi — Android-də MVP-nin əsas problemi. Activity ekran döndərildikdə məhv edilir və presenter.attachView() onCreate()-də yenidən çağırılır. Məlumat yükləməsi asinxron olarsa (RxJava, korutinlər), tamamlanma anında View ayrılmış ola bilər. Həll yolu — detachView()-də abunəliklərin ləğvi və ya Support Library-dən Loader istifadəsi (Jetpack olmayan layihələr üçün). IT Sectr-də illərlə kommersiya layihələrində MVP + RxJava birləşməsini tətbiq etdik — nümunə sabitdir, lakin abunəliklərin idarə edilməsində intizam tələb edir.
Retain-fragmentlər — ekran döndərildikdə Presenter-in qorunması mexanizmi. UI olmayan fragment (setRetainInstance(true)) Activity-dən daha uzun yaşayır və Presenter-ə istinad saxlayır. Activity yenidən yaradıldıqda fragment eyni Presenter-i yeni Activity-ə ötürür. Retain-fragmentlər AndroidX ilə deprecated edilib, lakin onların pre-Jetpack analoqu (Fragment.setRetainInstance) hələ də legacy layihələrdə işləyir. Müasir inkişafda Google retain-fragmentlər əvəzinə ViewModel-i tövsiyə edir.
MVP iOS-da View protokolu vasitəsilə qurulur. UIViewController protokolu tətbiq edir, Presenter UIKit-i idxal etmir və təmiz şəkildə test edilir. Apple MVC-dən fərqli olaraq, burada UIViewController özü məntiq və birbaşa IBOutlet əlaqələrini ehtiva edir, Presenter vəziyyəti idarə edir və View-ə protokol metodları vasitəsilə əmr verir. View qərarlar qəbul etmir — Presenter-in əmrlərini yerinə yetirir: showUser, showLoading, navigateToProfile.
import Foundation
// View Protocol — Presenter üçün abstraksiya
protocol UserViewProtocol: AnyObject {
func showLoading()
func hideLoading()
func display(user: User)
func displayError(message: String)
}
// Presenter — təmiz məntiq, UIKit olmadan
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) protokolu tətbiq edir
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
}
// ... protokolun qalan metodları
}
Weak reference View-ə — iOS MVP-də məcburi şərtdir. UIViewController məhv edilə bilər (naviqasiya stack-dən pop), onun Presenter-dəki closure-u retain cycle yaradacaq. Zəif istinad (weak var) View-in ekrandan çıxdıqda, Presenter-də asinxron əməliyyatların olmasından asılı olmayaraq, sərbəst buraxılmasını təmin edir. Android-də oxşar problem detachView() vasitəsilə həll edilir — onDestroy()-də çağırış View-ə istinadı sıfırlayır.
Passive View vs Supervising Controller — Martin Faulerdən MVP-nin iki variantı. Passive View: View məntiq ehtiva etmir, Presenter tamamilə vəziyyəti idarə edir. Supervising Controller: View özü sadə məlumat bağlamasını edir (məsələn, data binding vasitəsilə), Presenter yalnız mürəkkəb ssenarilərə müdaxilə edir. Mobil inkişafda Passive View daha tez-tez tətbiq olunur — maksimum test edilə bilənlik və ekran vəziyyətinin proqnozlaşdırıla bilənliyini verir.
MVP və MVC arasında əsas fərq — View ilə əlaqə üsulu. MVC-də Controller View-ə birbaşa istinada malikdir (UIViewController.IBOutlets, Activity.findViewById). MVP-də Presenter View ilə ViewContract interfeysi vasitəsilə qarşılıqlı əlaqə qurur. Bu fərq test edilə bilənliyi köklü şəkildə dəyişir: ViewContract-i tətbiq edən Mock obyekti Presenter-in məntiqini tətbiq, emulyator və UI-freymvork işə salmadan yoxlamağa imkan verir.
| Meyar | MVC | MVP |
|---|---|---|
| View ilə əlaqə | Birbaşa (Controller → View) | Interfeys vasitəsilə (Presenter → ViewContract) |
| Məntiqin test edilməsi | UIKit/Android Framework tələb edir | Platforma asılılıqları olmadan unit-testlər |
| Həyat dövrü | Controller ekranla yaşayır | Presenter daha uzun yaşaya bilər (retain) |
| Mürəkkəblik | Minimal | +1 interfeys ekran başına |
| Massive Controller | Tipik problem | Məntiq Presenter-də, View incə |
Presenter-in unit-test nümunəsi Kotlin-də: mock UserView yaradılır, Presenter-ə ötürülür, loadUser çağırılır, showUser-un düzgün məlumatlarla çağırıldığı yoxlanılır. Test millisaniyələr ərzində yerinə yetirilir, emulyator tələb etmir. iOS-da analoji olaraq — OCMock və ya protokol-stab UserViewProtocol metod çağırışlarını yoxlayır. IT Sectr-in MVP ilə layihələrində biznes məntiqinin unit-testlərlə əhatəsi 85–90%-ə çatırdı ki, bu da oxşar MVC layihələrindən 2–3 dəfə yüksəkdir.
MVP nə vaxt MVC-dən üstündür — sabitliyə ciddi tələbləri olan layihələrdə: bank tətbiqləri, tibbi sistemlər, ödəniş terminalları. Bu sahələrdə səhvin qiyməti yüksəkdir və unit-testlər kritik əhəmiyyət daşıyır. Post-MVP layihələrində (məhsul artıq bazarda olduqda, lakin kod bazası legacy olduqda) MVP Massive View Controller-dən məntiqi test edilə bilən təbəqəyə tədricən çıxarmağa imkan verir, memarlığı tamamilə yenidən yazmadan.
MVP-nin əsas çatışmazlıqları — interfeyslərin sayının artması və abunəliklərin əl ilə idarə edilməsi. Hər ekran ən azı bir ViewContract + Presenter tələb edir, 50 ekran üçün — 50 interfeys və 50 Presenter sinfi. MVVM-də ViewModel Presenter-i əvəz edir və reaktiv mexanizmlərdən (LiveData, StateFlow, ObservableObject) istifadə edir, bu da əl ilə attach/detach və ViewContract interfeyslərinə ehtiyacı aradan qaldırır.
RxJava və MVP — Android 2015–2019-da məşhur birləşmə. Presenter Repository-dən Observable-a abunə olur, nəticəni ViewContract vasitəsilə göstərir. Problem: disposable detachView()-də açıq şəkildə ləğv edilməlidir, əks halda abunəlik sızması ayrılmış View-i yeniləyərkən crash-ə səbəb olacaq. RxLifecycle və AutoDispose kitabxanaları qismən avtomatlaşdırdı, lakin asılılıq əlavə etdi. IT Sectr-də 2020-ci ildə MVP+RxJava-dan MVVM+Flow-a keçdik — kod ViewContract-in aradan qalxması hesabına 25–30% qısaldı.
MVP-dən MVVM-ə miqrasiya — mərhələli proses. 1) Presenter-də ViewContract-i LiveData/StateFlow ilə əvəz etmək. 2) attach/detach metodlarını silmək — abunəlik observe() vasitəsilə gedir. 3) Presenter-i ViewModel adlandırmaq. 4) ViewModelFactory üçün DI (Hilt/Koin) inteqrasiyası. Bir ekranın miqrasiyası 2–4 saat, bütün kod bazasının miqrasiyası — 50–100 ekranlı layihə üçün 2–4 həftə çəkir. Miqrasiyadan sonra ViewContract interfeysləri silinir, kod qısalır, testlər qalır.
Müasir inkişafda MVP — nümunə yaşayır, lakin MVVM və MVI-dan geri qalır. Google rəsmi olaraq yeni layihələr üçün MVVM-i Jetpack ilə tövsiyə edir. Apple — MVVM-i SwiftUI ilə. Bununla belə, MVP-ni bilmək legacy kodla işləmək üçün məcburidir: Google Play-də yüzlərlə Android tətbiqi, o cümlədən böyük bankların, pərakəndə satış şəbəkələrinin və nəqliyyat şirkətlərinin tətbiqləri hələ də MVP ilə işləyir. MVP-ni anlamaq MVI və Clean Architecture-ı mənimsəmək üçün əsasdır, çünki Presenter Robert Martin terminologiyasında Use Case-in birbaşa sələfidir.
Tez-tez verilən suallar
MVP-də Presenter View ilə ViewContract interfeysi vasitəsilə qarşılıqlı əlaqə qurur, birbaşa deyil. MVC-də Controller-in View-ə birbaşa istinadı var (IBOutlet/findViewById). MVP Presenter-i unit-testlərlə iOS Simulator və ya Android Emulator olmadan test etməyə imkan verir, çünki Presenter UIKit və ya Android Framework-dən asılı deyil. MVC kontroleri test etmək üçün tətbiqin işə salınmasını tələb edir.
MVP artıq bu nümunə üzərində qurulmuş legacy layihələrdə və reaktiv mexanizmləri (LiveData, StateFlow, Combine) dəstəkləməyən tətbiqlərdə əsaslandırılmışdır. Yeni layihələr üçün Google MVVM-i Jetpack ilə (Android), Apple isə MVVM-i SwiftUI ilə (iOS) tövsiyə edir. MVP biznes məntiqinin unit-test edilməsi tələbi ilə Combine olmadan təmiz UIKit-də layihələr üçün ən yaxşı seçim olaraq qalır.
Android-də — retain-fragment (setRetainInstance(true)) və ya Jetpack-dən ViewModel istifadə edin. Retain-fragment Presenter-i döndərmə zamanı saxlayır və yeni Activity-ə ötürür. Google-un ViewModel-i — retain-fragmentlər olmadan döndərmə zamanı vəziyyəti avtomatik qoruyan müasir alternativdir. iOS-da — Presenter hər viewDidLoad-da yenidən yaradılır, lakin ayrıca koordinator xidmətində keşlənir.
Minimum 4: ViewContract interfeysi, ViewContract tətbiqi (Activity/Fragment), Presenter, Model (Repository). Dagger/Hilt istifadə olunarsa, DI modulu əlavə edilir. 50 ekran üçün bu 200+ sinifdir. MVVM sayı ekran başına 1 fayl azaldır (ViewContract lazım deyil), MVI State və Intent siniflərini əlavə edir. Siniflərin sayı — böyük layihələrdə MVP-ə qarşı əsas arqumentdir.
Passive View — View məntiq ehtiva etmir, Presenter tamamilə vəziyyəti və məlumatları idarə edir. Supervising Controller — View özü sadə bağlama aparır (data binding), Presenter mürəkkəb ssenarilərə müdaxilə edir. Mobil inkişafda Passive View üstünlük təşkil edir — maksimum test edilə bilənlik və proqnozlaşdırıla bilənlik verir. Supervising Controller veb-freymvorklarda (ASP.NET Web Forms, GWT) tətbiq olunur.
Yekun
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun