設計模式(Swift) – 1.MVC和代理

Dariel發表於2019-02-17
設計模式(Swift) – 1.MVC和代理

1. 開篇

1. 什麼是設計模式?

在我們實際開發中通常會碰到一些特定問題,而這些問題可能會反覆出現,設計模式的出現就是針對某一特定問題,提出的某一解決方案.
因為設計模式並不提供具體的程式碼實現,所以在各種語言之間設計模式都有通用性.
例如,物件導向設計模式通過類和物件來表述其中的關係和相互作用.

設計模式可以分成三個大類:

  1. 結構模式(Structural design pattern): 主要關注於如何將類和物件組合成大的結構.例如,MVC,MVVM,外觀模式(Facade).
  2. 行為模式(Behavioral design pattern): 主要關注物件之間的通訊問題.例如,代理模式(Delegation),策略模式(Strategy),觀察者模式(Observer).
  3. 建立模式(Creational design pattern): 主要關注於怎樣將類的例項化抽象出來.例如,建造者模式(Builder),單例模式(Singleton), 原型模式(Prototype).

2. 設計模式使用的優缺點

優點:
  1. 設計模式可以用特定的方式去表述問題的解決方案,減少了開發者因為不同語言所產生的溝通成本.
  2. 合理的使用設計模式有利於提高程式碼的可維護性.
  3. 設計模式的通用性原則,可以使我們快速的應用到別的語言.
缺點:
  1. 設計模式是用來解決特定場景下的問題,過度使用會使程式碼的可維護性變得很差.
  2. 雖然設計模式有著通用性,但並不是所有的設計模式都是這樣,也需要針對特定的語言去選擇合理的設計模式.

3. 這個系列會涉及到的設計模式

  • 基本的設計模式
    MVC、代理模式(Delegation Pattern)、策略模式(Strategy Pattern)、單例模式(Singleton Pattern)、備忘錄模式(Memento Pattern)、觀察者模式(Observer Pattern)、建造者模式(Builder Pattern)
  • 不常用的設計模式
    MVVM、工廠模式(Factory Pattern)、介面卡模式(Adapter Pattern)、迭代器模式(Iterator Pattern)、原型模式(Prototype Pattern)、狀態模式(State Pattern)、多播代理模式(Multicast Delegate Pattern)、外觀模式(Facade Pattern)
  • 高階一點的設計模式
    享元模式(Flyweight Pattern)、中介者模式(Mediator Pattern)、組合模式(Composite Pattern)、命令模式(Command Pattern)、職責鏈模式(Chain of Responsibility)、裝飾者模式(Decorator Pattern)

具體的細節請看後面分解.

4. 表述設計模式的工具 – 類圖

大家都知道統一建模語言UML,它主要用來建模,為軟體開發提供視覺化的支援.當然了我們不需要關心這麼多.我們只需要使用UML的類圖來描述設計模式.

1.類圖的描述
類

上圖表示建立了一個Dog物件.

繼承

空心箭頭表示繼承,我們一般使用Is a來表述繼承

SheepDog is a Dog

上圖表示SheepDog是Dog的子類.

屬性

用實心箭頭表示屬性的指向.

設計模式(Swift) – 1.MVC和代理

Farmer有一條Dog.

設計模式(Swift) – 1.MVC和代理

我們可以用1 … * 表示一對多的關係,上圖表示Farmer有多條Dog.

設計模式(Swift) – 1.MVC和代理

這樣就可以清楚的表示,Farmer有一條SheepDog,而SheepDog繼承自Dog.

設計模式(Swift) – 1.MVC和代理

在名字前面加<< protocol>>表示協議

設計模式(Swift) – 1.MVC和代理

空心箭頭加虛線表示遵守了某一個協議.

設計模式(Swift) – 1.MVC和代理

Farmer遵守了PetOwning這個協議.

設計模式(Swift) – 1.MVC和代理

Dog實現了這個協議.

設計模式(Swift) – 1.MVC和代理

上圖就是綜合以上的一個完整的類圖.
思考下再看圖:
交通工具有兩個子類,一個是汽車,另一個是一個或多個輪子的交通工具.

設計模式(Swift) – 1.MVC和代理

教授是老師並且遵守了人這個協議.

設計模式(Swift) – 1.MVC和代理

類圖到這裡就說完啦,是不是很簡單呢!下面開始正文

2. 基本的設計模式 – MVC

1.MVC概述

作為Cocoa框架的核心的MVC,顧名思義將所有的類分為三種型別:模型(Models)、檢視(Views)、控制器(Controllers),用類圖來解釋就很形象生動.

MVC模式
  • 模型(Models): 負責儲存資料,通常是結構體或者類.
  • 檢視(Views): 負責展示螢幕上的元素和空間,通常是UIView的子類.
  • 控制器(Controllers): 模型和檢視之間的協調者,通常是UIViewController的子類.
    三者之間的關係:
    控制器負責管理模型和檢視,一個控制器可以對應有多個模型和檢視.控制器強引用著模型和檢視,模型和控制器的互動通過屬性觀察器,檢視跟控制器的互動通過IBActions.
    因為控制器通常負責特定的業務邏輯,所以MVC的重用性並不是那麼好.

Swift tips: 屬性觀察器

   // 屬性觀察器
   var s = "whatever" { // s必須是變數
        willSet { // s變數被設定新值之前呼叫
            print("willSet: ", newValue) // 新的值
        }
        didSet { // 接收新值之後被呼叫
            print("didSet: ", oldValue) // 舊的值
        }
    }
    s = "hello"
    // willSet:  hello
    // didSet:  whatever
    
    // 注意點: willSet和didSet兩種情況下不會呼叫: 初始化的時候,在didSet中改變變數的值的時候

複製程式碼

2.MVC的使用

1.模型中的程式碼,包括四個屬性:
public struct Address {
    public var street: String
    public var city: String
    public var state: String
    public var zipCode: String
}
複製程式碼
2.檢視中的程式碼:
public final class AddressView: UIView {
    
    @IBOutlet weak var streetTextField: UITextField!
    @IBOutlet weak var cityTextField: UITextField!
    @IBOutlet weak var stateTextField: UITextField!
    @IBOutlet weak var zipCodeTextField: UITextField

    @IBOutlet weak var addressLabel: UILabel!
}
複製程式碼

注意:用AddressView替代了控制器中的view,好處是控制器只負責處理業務邏輯.

3.控制器中的程式碼:
class AddressViewController: UIViewController {
    
    // MARK: - Properties
    public var address: Address? { 
        didSet { // 屬性觀察器
            updateViewFromAddress()
        }
    }
    
    public var addressView: AddressView! { // 關聯view
        guard isViewLoaded else {
            return nil
        }
        return view as! AddressView
    }

    // MARK: - View Lifecycle
    public override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    private func updateViewFromAddress() {
        
        if let address = address {  // 可選型別解包
            addressView.addressLabel.text = "street: "+address.street+"
city: "+address.city+"
state: "+address.state+"
zipCode: "+address.zipCode
        }else {
            addressView.addressLabel.text = "地址為空"
        }
    }
    
    // MARK: - Actions
    @IBAction public func updateAddressFromView(_ sender: AnyObject) {
        
        guard let street = addressView.streetTextField.text, street.count > 0,
            let city = addressView.cityTextField.text, city.count > 0,
            let state = addressView.stateTextField.text, state.count > 0,
            let zipCode = addressView.zipCodeTextField.text, zipCode.count > 0
            else {
                return
        }
        address = Address(street: street, city: city,
                          state: state, zipCode: zipCode)
    }
    
    @IBAction func clearAddressFromView(_ sender: Any) {
        
        address = nil
    }
}
複製程式碼

#####4. StroyBoard中控制器對應的檢視

設計模式(Swift) – 1.MVC和代理

#####5. 總結
控制器中的view交給檢視去管理,控制器只負責處理檢視的IBAction和自己的業務邏輯.在控制器中通過模型的屬性觀察器去監聽模型的改變,從而更新檢視.

3. 基本的設計模式 – 代理

1. 代理概述

代理最通俗的形容就是:叫別人幫我們做事.

設計模式(Swift) – 1.MVC和代理

代理模式有三部分:

  • 協議實現物件: 就是幫我們做事的人,由代理物件去呼叫協議方法.為了避免迴圈引用,代理屬性需要用weak去修飾.
  • 代理協議: 定義了一些需要實現的方法,這些方法可以是可選的.
  • 協議遵守物件: 也就是我們,由協議遵守物件去實現協議方法.
    代理和MVC一樣被大量應用於Cocoa框架,最常見的就是tableView的代理和資料來源方法了.

2. 代理的使用

1.協議實現方:
class MenuViewController: UIViewController {
    
   weak var delegate: MenuViewControllerDelegate?
    
    @IBOutlet weak var tableView: UITableView! {
        didSet {
            tableView.dataSource = self
            tableView.delegate = self
        }
    }
    
    let items = ["ITEMS 1", "ITEMS 2", "ITEMS 3", "ITEMS 4", "ITEMS 5", "ITEMS 6", "ITEMS 7", "ITEMS 8"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

extension MenuViewController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row]
        return cell
    }
}

extension MenuViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        delegate?.menuViewController(self, didSelectItemAtIndex: indexPath.row, chooseItem: items[indexPath.row])
        
        // 可選鏈式呼叫,當有一個方法的返回值為nil,會導致整個鏈式呼叫失敗
        delegate?.didSelectItemAtIndex?(indexPath.row)
        
        self.navigationController?.popViewController(animated: true)
    }
}
複製程式碼
2.協議:
@objc protocol MenuViewControllerDelegate: class { // 限制協議實現的型別
    
    // 必須實現的方法
    func menuViewController(_ menuViewController: MenuViewController, didSelectItemAtIndex index: Int, chooseItem item: String)
    
    @objc optional func didSelectItemAtIndex(_ index: Int) // 可選方法
}
複製程式碼
3.協議遵守方:
class ViewController: UIViewController {

    @IBOutlet weak var chooseLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let viewController = segue.destination as? MenuViewController else { return }
        
        viewController.delegate = self;
    }
}

extension ViewController: MenuViewControllerDelegate {
    func menuViewController(_ menuViewController: MenuViewController, didSelectItemAtIndex index: Int, chooseItem item: String) {
        
        chooseLabel.text = item
    }

    func didSelectItemAtIndex(_ index: Int) {   
        print(index)
    }   
}
複製程式碼

4.總結

本篇主要講了三個內容,如何用類圖直觀的表達設計模式,MVC模式的使用,代理模式的使用.

示例程式碼

參考:

The Swift Programming Language (Swift 4.1)

Objective-C程式設計之道

Design Patterns by Tutorials

相關文章