1. 開篇
1. 什麼是設計模式?
在我們實際開發中通常會碰到一些特定問題,而這些問題可能會反覆出現,設計模式的出現就是針對某一特定問題,提出的某一解決方案.
因為設計模式並不提供具體的程式碼實現,所以在各種語言之間設計模式都有通用性.
例如,物件導向設計模式通過類和物件來表述其中的關係和相互作用.
設計模式可以分成三個大類:
- 結構模式(Structural design pattern): 主要關注於如何將類和物件組合成大的結構.例如,MVC,MVVM,外觀模式(Facade).
- 行為模式(Behavioral design pattern): 主要關注物件之間的通訊問題.例如,代理模式(Delegation),策略模式(Strategy),觀察者模式(Observer).
- 建立模式(Creational design pattern): 主要關注於怎樣將類的例項化抽象出來.例如,建造者模式(Builder),單例模式(Singleton), 原型模式(Prototype).
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是Dog的子類.
用實心箭頭表示屬性的指向.
Farmer有一條Dog.
我們可以用1 … * 表示一對多的關係,上圖表示Farmer有多條Dog.
這樣就可以清楚的表示,Farmer有一條SheepDog,而SheepDog繼承自Dog.
在名字前面加<< protocol>>表示協議
空心箭頭加虛線表示遵守了某一個協議.
Farmer遵守了PetOwning這個協議.
Dog實現了這個協議.
上圖就是綜合以上的一個完整的類圖.
思考下再看圖:
交通工具有兩個子類,一個是汽車,另一個是一個或多個輪子的交通工具.
教授是老師並且遵守了人這個協議.
類圖到這裡就說完啦,是不是很簡單呢!下面開始正文
2. 基本的設計模式 – MVC
1.MVC概述
作為Cocoa框架的核心的MVC,顧名思義將所有的類分為三種型別:模型(Models)、檢視(Views)、控制器(Controllers),用類圖來解釋就很形象生動.
- 模型(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中控制器對應的檢視
#####5. 總結
控制器中的view交給檢視去管理,控制器只負責處理檢視的IBAction和自己的業務邏輯.在控制器中通過模型的屬性觀察器去監聽模型的改變,從而更新檢視.
3. 基本的設計模式 – 代理
1. 代理概述
代理最通俗的形容就是:叫別人幫我們做事.
代理模式有三部分:
- 協議實現物件: 就是幫我們做事的人,由代理物件去呼叫協議方法.為了避免迴圈引用,代理屬性需要用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模式的使用,代理模式的使用.
參考: