# 如何使用外觀模式

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

```swift
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` 然後添加如下程式碼：

```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 的字典資料。

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

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

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

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

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

```swift
    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`，這樣圖片的下載階段我們才可以控制下載的取消與重新下載。

```swift
import Alamofire
```

```swift
private var mImageRequest   :Request?
```

然後再完成`downloadImage:`。

```swift
    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`的功能，所以我們再追加一個程式。

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

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

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

完成到這一步的Demo：

* 查看原始碼
* [下載ZIP](https://github.com/wildenchen/swift-design-patterns/tree/8774a70f22cd1d2123f7e6c90567c20f7cb7c47b/workspaces/FlickrPhotos-3-Facade.zip)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wilden-chen.gitbook.io/swift-design-patterns/facade/use-facade.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
