Skip to main content

Giới thiệu về iOS design pattern (P1)


Nội dung

  • Giới thiệu vể iOS Design pattern
  • MVC - The King of design patterns
  • The Single Pattern
  • The Facade design pattern
  • The Decorator Design Pattern

Giới thiệu

Với người mới bắt đầu lập trình thì khi mới bắt đầu họ thường thắc mắc: làm thế nào để tổ chức một chương trình với cấu trúc tốt, dễ dàng thay đổi và mở rộng. Với mỗi ngôn ngữ đều có các chuẩn chung trong quá trình thiết kế phần mềm chúng ta gọi đó là Design patterns. Thật vậy Design pattern giúp chúng ta giải quyết rất nhiều vấn đề khi phát triển phần mềm như: viết code dễ để đọc hiểu, tái sử dụng hoặc có thể thay đổi các thành phần trong code mà không có quá phức tạp.
Trong bài biết này chúng ta sẽ cùng tìm hiểu về các design patterns phổ biến trong iOS như:
  • Khởi tạo: Singleton.
  • Cấu trúc: MVC, Decorator, Adapter, Facade.
  • Hành vi: Observer, Adapter, Memento
Nào chúng ta cùng bắt đầu!

MVC - The King of design patterns

Model - View - Controller (MVC) là một trong những design pattern thường hay sử dụng và nhiều người biết đến nhất. Nó phân loại đối tượng theo những quy tắc chung và khuyến khích chia code theo chức năng của từng thành phần.
Trong đó:
Model: Là dối tượng có vai trò định nghĩa, tổ chức trữ và thao tác trực tiếp với dữ liệu.
View: Là đối tượng chịu trách nhiệm về hiển thị giao diện hay nói cách khác đó là tầng mà người dùng quan sát và tương tác trực tiếp.
Controller: Là bộ điều kiển trung gian có vai trò quản lý, điều phối tất cả công việc. Nó truy cập dữ liệu từ Model và quyết định hiển thị nó trên View. Khi có một sự kiện được truyền xuống từ View, Controller quyết định cách thức xử lý: có thể là tương tác với dữ liệu, thay đổi giao diện trên View...
Một cách cài đặt tốt nhất theo nguyên tắc design pattern này đó là mỗi một object sẽ rơi vào một trong những thành phần này.
Dưới đây là hình ảnh minh họa về sự giao tiếp giữa các thành phần trong mô hình MVC:
mvc0.png
Tất cả các phần được tách biệt và có thể dùng lại. Ví dụ khi View không dựa trên một Model cụ thể, nó hoàn toàn có thể tái sử dụng với một Model khác để hiển thị dữ liệu khác.
Làm thế nào để sử dụng MVC pattern?
Đầu tiên bạn cần đảm bảo chắc ràng mỗi class trong project của bạn là một trong các thành phần của MVC.
Tiếp theo bạn cần tạo 3 nhóm để quản lý code tương ứng với mỗi thành phần của MVC. Tất nhiên bạn có thể có các nhóm khác nhưng phần chính của ứng dụng sẽ được chứa trong 3 nhóm này. Dưới đây là hình ảnh về cấu trúc dự án theo mô hình MVC:
swiftDesignPattern2-259x320.png

The Single Pattern

Ý tưởng của Singleton design pattern là tạo ra một lần và sử dụng mãi mãi. Nó đảm bảo rằng chỉ có duy nhất một thể hiện (instance) của 1 lớp củ thể và mọi đối tượng trong dự án đều có thể truy cập vào thể hiện này.
Làm thế nào để sử dụng Single pattern?
singleton.png
Theo như trên chúng ta có class tên là Logger với một thuộc tính và hai phương thức là sharedInstance và init.
Đầu tiên khi thực hiện gọi SharedInstance, nếu thuộc tính instance chưa được cài đặt, tiến hành cài đặt instance mới của class đó và trả về.
Tiếp theo bạn gọi SharedInstance, instance ngay lập tức được trả về mà không cần bất kì cài đặt nào. Lôgic này đảm bảo việc chỉ có duy nhất một insance tồn tại tại một thời điểm.
Dưới đây là đoạn code ví dụ về việc tạo một singleton instance
 //1
class var sharedInstance: LibraryAPI {
  //2
  struct Singleton {
    //3
    static let instance = LibraryAPI()
  }
  //4
  return Singleton.instance
}

The Facade design pattern

facade.jpg
Facade design pattern cung cấp một interface cho một hệ thống con phức tạp. Người dùng chỉ cần quan tâm đến interface và tương tác với nó chứ không cần quan tâm đến chi tiết việc sử lý yêu cầu của mình.
facade2-480x241.png
Nhìn vào hình minh họa trên chúng ta có thể thấy là tầng interface(người dùng tương tác với API) không hề thay đổi khi người dùng họ chọn lấy dữ liệu từ remoteServer hay từ database. Nó chỉ khác nhau về cách implementation chức năng tương ứng của API mà thôi: API thay vì trỏ tới RemoteServer, nó sẽ trỏ sang database...
Làm thế nào để sử dụng Facade Pattern?
design-patterns-facade-uml-480x71.png
Chúng ta có PersistencyManager làm nhiệm vụ lưu dữ liệu ở local và HTTPClient để xử lý dữ liệu. LibaryAPI đóng vai trò là mặt ngoài và tương tác với Other code(các class khác). Các class ngoài sẽ chỉ biết tới LibraryAPI và các service mà nó expose. Việc implementation tương ứng sẽ do LibraryAPI quyết định và tương tác với PersistencyManager và HTTPClient.

The Decorator Design Pattern

Ý tưởng của Decorator design pattern là tự động thêm các hành vi và tính năng của object mà không cần thay đổi code của nó. Nó là một sự lựa chọn thay thế cho Subclassing(Trong Subclassing bạn cần thay đổi hành vi của class bằng việc wrap nó với một object khác).
Trong swift, có 2 cách implementation nổi tiếng cho pattern này là ExtensionsDelegation.

Extensions

Extensions cho phép thêm các function mới vào các class, structure, enumeration mà không cần phải subclass. Nghĩa là bạn có thể thêm các method của bạn vào Cocoa classes như là UIView hoặc UIImage... Đặc biệt là phương thức mới sẽ được thêm vào ngay tại thời điểm complie và có thể thực hiện giống như các methods thông thường của class bởi vì extension không tạo instance của class nó mở rộng.
Làm thế nào để sử dụng Extensions?
Tưởng tượng bạn có một Album object bạn muốn hiển thị nó trong một table view.
swiftDesignPattern3-480x262.png
Album là một Model object do đó nó không quan tâm việc làm thế nào bạn hiển thị dữ liệu. Bạn cần mở rộng code để thêm function vào Album class nhưng không thay đổi trực tiếp vào class.
Bạn sẽ tạo extension tên là AlbumExtensions để mở rộng Album class. Nó sẽ định nghĩa phương thước mới trả về kiểu dữ liệu structure để dễ dàng sử dụng ở UITableView. Dữ liệu sẽ giống như hình minh họa dưới đây:
delegate2-480x67.png
Dưới đây là nội dung của file AlbumExtensions.swift
extension Album {
  func ae_tableRepresentation() -> (titles:[String], values:[String]) {
    return (["Artist", "Album", "Genre", "Year"], [artist, title, genre, year])
  }
}

Delegation

Delegation là cơ chế trong đó một đối tượng hoạt động thay mặt cho hoặc tương tác với một đối tượng khác. Có thể nói là nó đơn giản chỉ là subclass một object và override lại phương thức cần thiết nhưng chú ý rằng với subclass thì bạn chỉ có thể subclass từ một class duy nhất. Do đó nếu bạn muốn một object là delegate của hai hay nhiều object khác, bạn sẽ không thể làm được nếu dùng subclassing.
Làm thế nào để sử dụng Delegation?
Dưới đây là hình ảnh minh họa quá trình tạo UItableView
delegate-480x252.png
Dưới đây là đoạn code sử dụng delegate của UITableView
extension ViewController: UITableViewDataSource {
  func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if let albumData = currentAlbumData {
      return albumData.titles.count
    } else {
      return 0
    }
  }

  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
    if let albumData = currentAlbumData {
      cell.textLabel!.text = albumData.titles[indexPath.row]
            cell.detailTextLabel!.text = albumData.values[indexPath.row]
    }
    return cell
  }
}
extension ViewController: UITableViewDelegate {
}
Ở đây chỉ ra rằng ViewController sẽ phù hợp với giao thức UITableViewDataSource và UITableViewDelegate. UITableView đảm bảo rằng các phương thức bắt buộc được cài đặt bởi delegate của nó.
Trong đó:
tableView(_:numberOfRowsInSection:) trả về số row được hiển thị trong table view
tableView(_:cellForRowAtIndexPath:) khởi tạo và trả về đối tượng cell tương ứng với row của table view

Tổng kết

Thông qua bài viết này, chúng ta hiểu thêm về các design pattern thường sử dụng trong iOS. Trong khi hầu hết các developer đồng ý là design pattern rất quan trọng nhưng dường như lại không chú ý nhiều về design pattern khi viết code.
Design pattern giúp cuộc sống của developer dễ dàng hơn và code của bạn trở nên tốt hơn!

Comments

Popular posts from this blog

Swift Tool Belt, Part 1: Adding a Border, Corner Radius, and Shadow to a UIView with Interface Builder

During my iOS work, I’ve assembled a set of code that I bring with me on every iOS project. I’m not talking about large frameworks or CocoaPods here. These are smaller Swift extensions or control overrides that are applicable to many projects. I think of them as my tool belt. In this post, I’ll show you an extension that will add a border, a corner radius, and a shadow to any UIView, UIButton, or UILabel and allow you to preview what it will look like in Interface Builder. Back in 2014, I wrote a blog post on Expanding User-Defined Runtime Attributes in Xcode where I added a border, corner radius, and shadow to a UIView using Interface Builder’s user-defined runtime attributes. This solution had no type checking—you had to type the property you wanted to modify by hand and often had to look up what it was called. You also had to run your project in order to see the effect of the runtime attribute. Starting with Xcode 6 , there is a new mech...

Alamofire vs URLSession

Alamofire vs URLSession: a comparison for networking in Swift Alamofire and URLSession both help you to make network requests in Swift. The URLSession API is part of the foundation framework, whereas Alamofire needs to be added as an external dependency. Many  developers  doubt  whether it’s needed to include an extra dependency on something basic like networking in Swift. In the end, it’s perfectly doable to implement a networking layer with the great URLSession API’s which are available nowadays. This blog post is here to compare both frameworks and to find out when to add Alamofire as an external dependency. Build better iOS apps faster Looking for a great mobile CI/CD solution that has tons of iOS-specific tools, smooth code signing, and even real device testing? Learn more about Bitrise’s iOS specific solutions! This shows the real power of Alamofire as the framework makes a lot of things easier. What is Alamofire? Where URLSession...

Frame vs Bounds in iOS

This article is a repost of an answer I wrote on Stack Overflow . Short description frame = a view’s location and size using the parent view’s coordinate system ( important for placing the view in the parent) bounds = a view’s location and size using its own coordinate system (important for placing the view’s content or subviews within itself) Details To help me remember frame , I think of a picture frame on a wall . The picture frame is like the border of a view. I can hang the picture anywhere I want on the wall. In the same way, I can put a view anywhere I want inside a parent view (also called a superview). The parent view is like the wall. The origin of the coordinate system in iOS is the top left. We can put our view at the origin of the superview by setting the view frame’s x-y coordinates to (0, 0), which is like hanging our picture in the very top left corner of the wall. To move it right, increase x, to move it down increase y. To help me remember bound...