如何使用適配器模式

到目前我們完成了可以呈現照片的 PhotoView,還希望能夠有一個資訊導覽列,用作顯示目前的圖片訊息,所以我們再新增一個繼承UIView的類別,取名為 InfoBarView,並完成下面程式碼。

import UIKit

class InfoBarView: UIView {
    private let mInfoLabel:UILabel = UILabel()
    var infoLabel:UILabel{
        return mInfoLabel
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.whiteGoldColor()
        mInfoLabel.textColor = UIColor.goldColor()
        mInfoLabel.font = UIFont.systemFontOfSize(20.0)
        mInfoLabel.text = "Photo Information"
        mInfoLabel.textAlignment = NSTextAlignment.Left
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    override func drawRect(rect: CGRect) {
        mInfoLabel.frame = CGRect(x: 20, y: 0, width: rect.width - 40, height: rect.height)
        self.addSubview(mInfoLabel)
    }

    func setText(aText:String){
        mInfoLabel.text = aText
    }

    func setHighlight(aIsHighlight:Bool) {
        if aIsHighlight {
            mInfoLabel.textColor = UIColor.whiteGoldColor()
            self.backgroundColor = UIColor.goldColor()
        } else {
            mInfoLabel.textColor = UIColor.goldColor()
            self.backgroundColor = UIColor.whiteGoldColor()

        }
    }
}

我們建立了一個繼承自 UIView 但是又是用做文字功能的一個自訂 View,其實在最開始我們自訂 PhotoView 時,應該就有類似的感覺:

  • PhotoView 有著和 UIImageView 類似的功能,但是 PhotView 不是繼承 UIImageView 做出來的 View,而是繼承 UIView 來複合 UIImageView。

    • PhotoView 可以從外部類別實體去賦予 PhotoView 的 UIImage 做圖片的顯示。

    • UIImageView 可以從外部類別實體去賦予 PhotoView 的 UIImage 做圖片的顯示。

  • InfoBarView 有著和 UILabel 類似的功能,,但是 InfoBarView 不是繼承 UILabel 做出來的 View,而是繼承 UIView 來複合 UILabel。

    • InfoBarView 可以從外部類別實體去賦予 InfoBarView 的 String 做字串的顯示。

    • UILabel 可以從外部類別實體去賦予 UILabel 的 String 做字串的顯示。

若無法理解上述內容的,可以回顧 Swift 殿堂之路繼承與複合的篇章,這也是適配器模式的主要概念。

適配器模式的概念,在於把兩個以上不同類別中相同的功能整理出來,甚至可以把這些沒有彼此直接繼承的不同類別當成同一個「類別」來使用。

有沒有發現,高光的功能,不管是資訊列InfoBarView以及PhotoView都有這功能,儘管他們有不同的內容,但是卻有同樣的方法名稱,而且使用的時機與情景也幾乎一樣。

在我們實際導入 Adapter 模式之前,我們先嘗試用目前的程式碼做一次操作。我們在剛剛viewDidLoad再添加 InforBarView 的視覺物件,並修改之前範例建立的 UIButton 的 Selector。

先宣告我們要測試的InfoBarView以及一個高亮開關mIsHighLight

    private let mInfoView:InfoBarView = InfoBarView()
    private var mIsHighLight:Bool = false
    private func createDemoLayout(){
        mFlickrModel.loadFlickrData(1)

        for _index:UInt in 0..<10 {
            let _photoView  :PhotoView = PhotoView()
            let _boardWidth :CGFloat = 5
            let _dWidth     :CGFloat = (self.view.bounds.width - _boardWidth * 5)  / 4
            let _dHeight    :CGFloat = (self.view.bounds.height - _boardWidth * 6) / 5
            let _dX         :CGFloat = _boardWidth + CGFloat(_index % 4) * (_dWidth + _boardWidth)
            let _dY         :CGFloat = 50 + CGFloat(_index / 4) * (_dHeight + _boardWidth)
            _photoView.frame = CGRectMake( _dX, _dY , _dWidth, _dHeight)

            self.view.addSubview(_photoView)
            mPhotoViews.append(_photoView)
        }

        let _button:UIButton = UIButton()
        _button.frame = CGRectMake(10, self.view.bounds.height - 30, self.view.bounds.width - 20, 25)
        _button.backgroundColor = UIColor.redColor()
        self.view.addSubview(_button)

        //_button.addTarget(self, action: Selector("onTestLoadImageHandler"), forControlEvents: UIControlEvents.TouchUpInside)
        _button.addTarget(self, action: Selector("onTestHightLightHandler"), forControlEvents: UIControlEvents.TouchUpInside)

        mInfoView.frame = CGRectMake(0, self.view.bounds.height - 80, self.view.bounds.width, 20)
        self.view.addSubview(mInfoView)
    }

    func onTestHighLightHandler(){
        mIsHighLight = !mIsHighLight
        mInfoView.setHighlight(mIsHighLight)
        for _photoView:PhotoView in mPhotoViews {
            _photoView.setHighlight(mIsHighLight)
        }
    }

其實,是因為在編寫InfoBarView的時候,我們故意把設定底圖狀態方法的名稱取的跟PhotoView的設定底圖狀態方法一樣,所以在外面的類別使用這些方法的時候,才會有統一性與一致性。

再導入 Adapter 模式,我們需要再開啟一個新的檔案,在Views這資料夾裡再添加一個檔案HighLightable.swift

import Foundation

protocol HighLightable{
    func setHighlight(aIsHighlight:Bool)
}

只要有需要這功能的,無論是視覺物件或是資料,都可以實做這個協定。

我們再把我們的PhotoView以及InfoBarView都遵從這協定。

當然,因為方法名稱我們之前都取好了,所以雖然實踐這個協定,但是程式碼內容幾乎都不用改動。

class PhotoView: UIView,HighLightable {
    // 中間略
}
class InfoBarView: UIView,HighLightable {
    // 中間略
}

我們來看看MainViewController.swift裡面,我們在做這樣的修改:

    private var mHighLightViews:[HighLightable] = [HighLightable]()

先把協定的HighLightable當成類別宣告一個陣列。 然後在self.view.addSubview的下面都分別把有實踐HighLightable協定的物件都加進去,有PhotoViewInfoBarView

    private func createDemoLayout(){
        mFlickrModel.loadFlickrData(1)

        for _index:UInt in 0..<10 {
            let _photoView  :PhotoView = PhotoView()
            let _boardWidth :CGFloat = 5
            let _dWidth     :CGFloat = (self.view.bounds.width - _boardWidth * 5)  / 4
            let _dHeight    :CGFloat = (self.view.bounds.height - _boardWidth * 6) / 5
            let _dX         :CGFloat = _boardWidth + CGFloat(_index % 4) * (_dWidth + _boardWidth)
            let _dY         :CGFloat = 50 + CGFloat(_index / 4) * (_dHeight + _boardWidth)
            _photoView.frame = CGRectMake( _dX, _dY , _dWidth, _dHeight)

            self.view.addSubview(_photoView)
            mHighLightViews.append(_photoView)
        }

        let _button:UIButton = UIButton()
        _button.frame = CGRectMake(10, self.view.bounds.height - 30, self.view.bounds.width - 20, 25)
        _button.backgroundColor = UIColor.redColor()
        self.view.addSubview(_button)

        //_button.addTarget(self, action: Selector("onTestLoadImageHandler"), forControlEvents: UIControlEvents.TouchUpInside)
        _button.addTarget(self, action: Selector("onTestHighLightHandler"), forControlEvents: UIControlEvents.TouchUpInside)

        let _infoView:InfoBarView = InfoBarView()
        _infoView.frame = CGRectMake(0, self.view.bounds.height - 80, self.view.bounds.width, 20)
        self.view.addSubview(_infoView)
        mHighLightViews.append(_infoView)
    }

我們再把按鈕事件給成如下:

    func onTestHighLightHandler(){
        mIsHighLight = !mIsHighLight
        //mInfoView.setHighlight(mIsHighLight)
        for _view:HighLightable in mHighLightViews {
            _view.setHighlight(mIsHighLight)
        }
    }

是的,本來PhotoViewInfoBarView是不同的類別,彼此沒有上下關聯的繼承關係,所以是不可能互相做轉型的,而因為統一了介面,實踐了同樣的協定,所以可以把這些實體都當成HighLightable的物件來做同樣的操作,而各自都可以實踐他們自己的功能。

這就是 Adapter 適配器模式。

完成到這一步的Demo:

Last updated