如何使用外觀模式

我們可以新建一個檔案FlickrModel.swift,先不繼承任何類別,但是需要先載入以下的 Framework。

import Foundation
import Alamofire
import SwiftyJSON

class FlickrModel {

}

Alamofire是一個用做網路溝通協定的一個第三方 Library,而SwiftyJSON則是用來解析 JSON 格式的一個 Library。

現在我們用 FlickrModel 來管理照片數據與處理 Flickr 圖床的網絡請求,FlickrAPI有 FLickr 的 API 請求資訊,專案中的其他類別不應該也不需要知道這些邏輯。他們只需要知道 FlickrModel 這個「外觀」就可以了。

為了實現外觀模式,應該只讓 FlickrModel 持有 PhotoVO 的陣列實體資料,以及其他會需要紀錄與儲存的變數,然後 FlickrModel 暴露最少只需要給其他類別來使用的方法,這樣外部的訪問類別不需要知道內部的商業邏輯具體是怎樣的,也不用知道你是通過 Alamofire 還是 FlickrAPI 獲取到數據的。

FlickrModel 會暴露給其他類別訪問,但是其他全部的會用到的屬性則是不對外開放的。

打開 FlickrModel.swift 然後添加如下程式碼:

    private var mPhotoVOs           :[UInt:[PhotoVO]]   = [UInt:[PhotoVO]]()
    private var mCurrentPageIndex   :UInt               = 1
    private var mIsNight            :Bool               = false
    private let mPerPage            :UInt               = 99

mPhotoVOs:[UInt:[PhotoVO]]只是用來儲存這隻 App 裡面會用到的每一頁的 Flickr 圖片資料,mCurrentPageIndex:UInt則是用來紀錄 Flickr 的目前顯示與載入的資料是哪一頁,mPerPage:UInt則是表示目前一頁會有幾張照片的常數。

我們再多寫一個方法,用來儲存 PhotoVO 的字典資料。

    func addPhoto(aPageIndex:UInt,aPhotoVOs: [PhotoVO]) {
        mPhotoVOs.updateValue(aPhotoVOs, forKey: aPageIndex)
    }

當然,也要開放權限讓其他類別可以讀取,所以我們再添加兩個讓外部讀取 VO 的方法。

    func getPhotos(aPageIndex:UInt) -> [PhotoVO]{
        return mPhotoVOs[aPageIndex]!
    }

    func getCurrentPhotos() -> [PhotoVO]{
        return mPhotoVOs[mCurrentPageIndex]!
    }

我們需要在FlickrModel裡面將FlickrAPI的下載的資料轉換為我們需要的數據格式PhotoVO

    func loadFlickrData(aPageIndex:UInt){
        Alamofire.request(Method.GET, FlickrAPI.flickrAPIUrl(), parameters: FlickrAPI.flickrInterestingness(aPageIndex, aPerPage: mPerPage), encoding: ParameterEncoding.URL, headers: nil)
            .responseJSON(completionHandler: onLoadFlickrDataCompleteHandler)
    }

    private func onLoadFlickrDataCompleteHandler(aRequest:NSURLRequest?, aResponse:NSHTTPURLResponse?, aResult:Result<AnyObject>){
        var _vos:[PhotoVO] = [PhotoVO]()
        var _pageIndex:UInt = 0
        if aResult.error == nil {
            let _json:JSON = JSON(aResult.value!)
            if let _items:Array = _json["photos"]["photo"].array {
                for _item:JSON in _items {
                    let _vo:PhotoVO = PhotoVO(
                        aID  : _item["id"].stringValue,
                        aTitle  : _item["title"].stringValue,
                        aOwner  : _item["owner"].stringValue,
                        aURLQ   : _item["url_q"].stringValue,
                        aURLZ   : _item["url_z"].stringValue,
                        aWidth  : _item["width_z"].floatValue,
                        aHeight : _item["height_z"].floatValue)
                    _vos.append(_vo)
                }
                _pageIndex = _json["photos"]["page"].uIntValue
                addPhoto(_pageIndex, aPhotoVOs: _vos)
            }
            print("LoadFlickrDataComplete!count:\(_vos.count)")
        }else{
            print("loadFlickrData Error:\(aResult.error)")
        }
    }

看一下 loadFlickrData(aPageIndex:UInt) 這個方法,先透過FlickrAPI撈取到 JSON 格式的圖庫資料,然後再取出裡面的 ["photos"]["photo"]標籤裡面的陣列。

在歷遍這個圖片資訊的陣列,逐筆建立我們所會用到的PhotoVO格式,再儲存到我們做好的FlickrModel裡面。

這便是外觀模式的強大之處:如果外部類別想要做 Flickr 圖床的資料操作,它不會也不用去瞭解FlickrModel內部的實現邏輯是怎麼樣的,只需要知道FlickrModel儲存照片資料,照片資料格式為PhotoVO

可以再從FlickrModel的角色再深入去思考,對於其他的類別,也不需要知道目前有哪些照片來源,如何得到的,只要知道FlickrModel整合好圖片資訊即可。

注意:當你設計外觀的時候,請務必牢記:使用者隨時可能直接訪問你的隱藏類別。永遠不要假設使用者會遵循你當初的設計做事。

我們再打開PhotoView.swift,幫 PhotoView 多一個下載圖片的功能,而且為了日後可以重複利用,降低耦合的前提下,我們要給它的功能是,給 PhotoView 圖片網址就可以下載圖片,而不是給 PhotoVO 這個圖片描述的 Variable Object。

我們先設定一個全域變數mImageRequest,這樣圖片的下載階段我們才可以控制下載的取消與重新下載。

import Alamofire
private var mImageRequest   :Request?

然後再完成downloadImage:

    func downloadImage(aImageURL:String?,aCacheFileName:String?){
        mImageView.image = nil
        mImageRequest?.cancel()
        if aImageURL != nil {
            mLoadingView.startAnimating()
            mImageRequest = Alamofire.request(Method.GET, aImageURL!).response { (aRequest:NSURLRequest?, aResponse:NSHTTPURLResponse?, aData:NSData?, aError:ErrorType?) -> Void in
                if aError == nil {
                    if let _image:UIImage = UIImage(data: aData!){
                        self.mImageView.image = _image

                    }else{
                        print("no image")
                    }

                }else{
                    print("error:\(aError!)")
                }
            }
        }
    }

除此以外,讓PhotoView除了網址以外,也可以直接呈現 UIImage的功能,所以我們再追加一個程式。

    func setImage(aImage:UIImage){
        mImageView.image = aImage
    }

這樣,我們的PhotoView也是一個對於這支應用程式而言,一個低耦合,甚至可以直接給其他專案使用的類別,而且他可以自己處理 UIImage的呈現,也可以直接給圖片網址自行下載的功能。

FlickrModel以及PhotoView這兩個類別,就是外觀模式的應用。

完成到這一步的Demo:

Last updated