Edited at

[Swift] プロトコル指向って何? サンプルを見ながら勉強してみる

More than 3 years have passed since last update.


プロトコル指向プログラミングとは?

・プロトコルファーストということ?

・何を実装しなければならないかが明確になるからよい?? (共通化??)

・値型なので、別のオブジェクトから変更されないからセーフティ?? 

よく分からないので、プロトコル指向プログラミングが紹介されている

サンプルコードを見ながら写経してみます。


サンプル

今回は、下記の例を利用してご説明します。

ログイン画面にて、メールアドレスとパスワードを入力し、ログインボタンを押下する。

該当フォームが空の場合は、該当フォームをブルブル震わす。

スクリーンショット 2016-08-15 11.41.55.png

ダメなパターンも含め、思いつく実装案を色々挙げてみました。


(A案)

ブルブル震わすロジックを直接ViewControlerに組み込む

おそらく一番バッドパターン。

新人プログラマがやるパターン。「仮実装なので」といいながら、そのまま最後まで残るパターン。

一事が万事!(★最近良く聞く言葉です。)


ViewController.swift

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var mailAddressTextField: FormTextField!
@IBOutlet weak var passWordTextField: FormTextField!

override func viewDidLoad() {
super.viewDidLoad()
}

@IBAction func didTapShake(sender: UIButton) {

if let mailAddress = mailAddressTextField.text where mailAddress.isEmpty {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(mailAddressTextField.center.x - 4.0, mailAddressTextField.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(mailAddressTextField.center.x + 4.0, mailAddressTextField.center.y))
mailAddressTextField.layer.addAnimation(animation, forKey: "position")
}

if let passWord = passWordTextField.text where passWord.isEmpty {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(passWordTextField.center.x - 4.0, passWordTextField.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(passWordTextField.center.x + 4.0, passWordTextField.center.y))
passWordTextField.layer.addAnimation(animation, forKey: "position")
}
}
}



(B案)

ブルブル震わすロジックを直接ViewControlerに組み込むのは変わらないが、

メールアドレスとパスワードが共通処理なので、一応メソッド分割はする。

A案より一歩進んだ段階。


ViewController.swift

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var mailAddressTextField: FormTextField!
@IBOutlet weak var passWordTextField: FormTextField!

override func viewDidLoad() {
super.viewDidLoad()
}

@IBAction func didTapShake(sender: UIButton) {

if let mailAddress = mailAddressTextField.text where mailAddress.isEmpty {
shake(mailAddressTextField)
}

if let passWord = passWordTextField.text where passWord.isEmpty {
shake(passWordTextField)
}
}

private func shake(view: UIView) {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(view.center.x - 4.0, view.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(view.center.x + 4.0, view.center.y))
view.layer.addAnimation(animation, forKey: "position")
}
}



(C案)

サブクラスを作り、ブルブル震わすメソッドを定義する。

B案より一歩進み、ViewControllerからViewへ切り出そうという意識がある段階。


FormTextField.swift

class FormTextField: UITextField {

func shake() {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
layer.addAnimation(animation, forKey: "position")
}
}


ViewController.swift

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var mailAddressTextField: FormTextField!
@IBOutlet weak var passWordTextField: FormTextField!

override func viewDidLoad() {
super.viewDidLoad()
}

@IBAction func didTapShake(sender: UIButton) {

if let mailAddress = mailAddressTextField.text where mailAddress.isEmpty {
mailAddressTextField.shake()
}

if let passWord = passWordTextField.text where passWord.isEmpty {
passWordTextField.shake()
}
}
}



(D案)

UIViewのExtensionを作り、ブルブル震わすメソッドを定義する。

Obj-Cでカテゴリ、SwiftでExtensionを覚えたので、使ってみた段階。

個人的は、このパターン乱発してました。(汗)


UIView+Shake.swift

import UIKit

extension UIView {

func shake() {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
layer.addAnimation(animation, forKey: "position")
}
}



ViewController.swift

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var mailAddressTextField: FormTextField!
@IBOutlet weak var passWordTextField: FormTextField!

override func viewDidLoad() {
super.viewDidLoad()
}

@IBAction func didTapShake(sender: UIButton) {

if let mailAddress = mailAddressTextField.text where mailAddress.isEmpty {
mailAddressTextField.shake()
}

if let passWord = passWordTextField.text where passWord.isEmpty {
passWordTextField.shake()
}
}
}



(E案)

プロトコル指向を採用して、ブルブル震わすメソッドを定義する。

制約付きの拡張なので、UIView以外では使えないから安心。


Shakeable.swift

import UIKit

protocol Shakeable {}

extension Shakeable where Self: UIView {

func shake() {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
layer.addAnimation(animation, forKey: "position")
}
}



FormTextField.swift

import UIKit

class FormTextField: UITextField, Shakeable {
}



ViewController.swift

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var mailAddressTextField: FormTextField!
@IBOutlet weak var passWordTextField: FormTextField!

override func viewDidLoad() {
super.viewDidLoad()
}

@IBAction func didTapShake(sender: UIButton) {

if let mailAddress = mailAddressTextField.text where mailAddress.isEmpty {
mailAddressTextField.shake()
}

if let passWord = passWordTextField.text where passWord.isEmpty {
passWordTextField.shake()
}
}
}



まとめ

ボトムアップの観点で考えると、

クラス間で共通なプロパティや処理は、まずプロトコルを採用することを考えようということなのでしょうか。

トップダウンの観点で考えると、

ユースケースをプロトコルとして採用することを考えようということなのでしょうか。

有志の方、教えて下さい。

現時点では、御作法として、

プロトコルファーストで設計及び実装していきたいと思います。

・参考サイト

下記のサイトを参考にさせて頂きました。

大変勉強になりました。

https://realm.io/news/appbuilders-natasha-muraschev-practical-protocol-oriented-programming/

https://www.infoq.com/jp/news/2015/06/protocol-oriented-swift