MVP Application

1. Giới thiệu

MVC

Phần lớn các ứng dụng iOS theo định hướng của Apple đều được xây dựng theo mô hình MVC. Dưới đây là mong muốn của Apple về mô hình này:

MVC

Trong đó Controller đóng vai trò kết nối giữa View và Model do đó View và Model không biết lẫn nhau. Controller là thành phần khó sử dụng lại nhất và nó là nơi chúng ta lưu các code liên quan tới business. Mô hình này có nhược điểm là Controller đôi lúc trở nên quá lớn khi chứa toàn bộ code logic, đồng thời nó kiêm luôn vai trò quản lý vòng đời của View khiến View và Controller khó có thể tách rời. Dưới đây là thực tế của mô hình MVC của Apple:

MVC

Đặc điểm:

  • Phân phối: View và Model được tách rời nhưng View và Controller bị liên kết chặt với nhau.
  • Khả năng test: bạn chỉ có thể test được Model.
  • Tính dễ dùng: số lượng code ít nhất trong số các mô hình, ngoài ra do tất cả mọi người đều biết về mô hình này nên nó dễ dàng bảo trì ngay cả với những lập trình viên ít kinh nghiệm.

Ngoài MVC chúng ta còn có các mô hình sau:

  • MVP
  • MVVM
  • VIPER

Mỗi mô hình đều có những ưu và nhược điểm riêng, trong khuôn khổ bài viết này tôi sẽ trình bày về mô hình MVP.

MVP

MVP

Trong mô hình MVP UIViewController đóng vai trò là View chứ không phải là Presenter. Do đó tăng khả năng test, đổi lại thì việc phát triển ứng dụng sẽ mất thời gian hơn do chúng ta phải tạo các kết nối data và event thủ công.

Đặc điểm của mô hình MVP:

  • Phân phối: phần lớn các trách nhiệm được chia đều giữa Presenter và Model, View có rất ít vai trò, chủ yếu để hiển thị dữ liệu.
  • Khả năng test: chúng ta có thể test phần lớn các code logic do ít phụ thuộc vào View.
  • Tính dễ dùng: số lượng code gấp 2 so với mô hình MVC truyền thống, tuy nhiên về mặt ý tưởng thì mô hình MVP rất rõ ràng.

Để hiểu rõ hơn về mô hình này, chúng ta sẽ làm một ứng dụng cho phép hiển thị danh sách các sản phẩm.

2. Demo

Tạo mới dự án

Chúng ta sẽ tạo một project Single View Application mới với các thông số như sau:

New xcode project

Storyboard

Để đơn giản chúng ta chỉ có một màn hình hiển thị danh sách sản phẩm, sử dụng UITableViewController

Storyboard

Model

Model của chúng ta là sản phẩm:

struct Product {
    var name: String
    var price: Double
}

View

Phần View có nhiệm vụ thiết lập việc hiển thị danh sách các sản phẩm:

protocol ProductListView: class {
    func setProductList(products: [Product])
}

Presenter

Phần Presenter có nhiệm vụ liên kết giữa View và Model:

protocol ProductListViewPresenter: class {
    func showProductList()
}
class ProductListPresenter: ProductListViewPresenter {
    unowned let view: ProductListView
    let products: [Product]

    required init(view: ProductListView, products: [Product]) {
        self.view = view
        self.products = products
    }

    func showProductList() {
        self.view.setProductList(self.products)
    }
}

Controller

Controller chỉ đóng vai trò là View, liên kết với Presenter để hiển thị dữ liệu:

class ProductListViewController: UITableViewController, ProductListView {

    var presenter: ProductListViewPresenter!
    var productListDataSource: ProductListDataSource!

    override func viewDidLoad() {
        super.viewDidLoad()

        presenter.showProductList()
    }

    func setProductList(products: [Product]) {
        productListDataSource = ProductListDataSource()
        productListDataSource.products = products
        tableView.dataSource = productListDataSource
        tableView.reloadData()
    }
}

Chúng ta sử dụng data source để đưa dữ liệu vào data table:

class ProductListDataSource: NSObject, UITableViewDataSource {
    var products: [Product]!

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return products.count
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("ProductCell", forIndexPath: indexPath)
        let product = products[indexPath.row]
        cell.textLabel?.text = product.name
        cell.detailTextLabel?.text = String(product.price)
        return cell
    }
}

Như vậy là việc triển khai mô hình MVP đã xong, việc cuối cùng là kết nối các thành phần với nhau, chúng ta sẽ thực hiện ở AppDelegate:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.

    let nc = self.window?.rootViewController as! UINavigationController
    let productListViewController = nc.topViewController as! ProductListViewController

    let products = [
        Product(name: "Keyboard", price: 6),
        Product(name: "Mouse", price: 5)
    ]

    let productListPresenter = ProductListPresenter(view: productListViewController, products: products)
    productListViewController.presenter = productListPresenter
    return true
}

Trong khuôn khổ bài viết này, tôi sử dụng mock data để demo, thực tế các bạn có thể dùng cơ sở dữ liệu như Core Data.

Chạy thử chương trình:

Demo

3. Kết luận

Như vậy là chúng ta đã hoàn thành việc tìm hiểu mô hình MVP, các bạn có thể hoàn thiện chương trình bằng cách thêm các tính năng thêm, sửa, xóa sản phẩm.

Với các bài toán nhỏ, yêu cầu thời gian triển khai nhanh thì mô hình MVC là phù hợp, tuy nhiên với bài toán lớn và yêu cầu có unit test thì các mô hình khác như MVP, VIPER sẽ hợp lý hơn. Hi vọng bài viết này sẽ giúp các bạn có thêm một hướng đi mới cho việc triển khai ứng dụng của mình.

Tham khảo

Source code