Swift20/90Days - 類型設計

Swift90Days - 類型的設計

介紹

今天讀的這篇博文,做者是一名 Haskell 程序員,從 Swift 枚舉類能夠經過 rawValue 來初始化這個特性談起,提出了一些本身的觀點,摘錄一部分以下:html

I always thought of the rawValue mechanism as an escape hatch to drop back down to the world of Objective-C, rather than something you should use in Swift if you can help it. I don't want to say that using rawValue is wrong in any way, but rather that it doesn't fit with my intuition. Years of Haskell programming have warped my brain in interesting ways. One important lesson I've learned is that designing the right types for your problem is a great way to help the compiler debug your program.git

感興趣的同窗建議直接閱讀原文:The Design of Types。因爲是一邊看着老羅的直播一邊寫的,因此寫的可能有點凌亂哈哈哈哈。程序員

輸入校驗

輸入校驗你們應該都不陌生,爲了防止 SQL 注入之類的問題,一般會對輸入的內容進行校驗。簡單來講咱們能夠定義一個函數進行校驗:github

func sanitize(userInput : String) -> String

每次須要校驗輸入的時候用這個函數過濾一下就行。數據庫

但問題也隨之而來:當出現一個字符串的時候,須要咱們手動判斷是否須要進行校驗,一不當心可能會校驗屢次 (好比獲取的時候校驗了,寫入數據庫又校驗了,這有點糟糕),也有可能就沒校驗 (好比獲取的時候覺得寫入的時候會校驗,寫入數據庫的時候覺得已經校驗過了,這更糟糕)。swift

爲了不這種繁瑣的問題,咱們可讓編譯器去作這個工做:api

typealias UserInput = String

struct SanitizedString {
    let value : String

    init(input : UserInput)
    {
        value = sanitize(input)
    }
}

能夠看到咱們定義了兩個新的類型 SanitizedStringUserInputless

  • UserInput 就是 String 類型,用來標記用戶的輸入。
  • SanitizedString 是結構體,經過一個 value 存儲校驗後的字符串。

是的,這樣就不用親自監控輸入的校驗了,使用起來看看。好比咱們經過 sanitize 把空格去掉而後返回:函數

import Foundation

func sanitize(userInput : String) -> String {
    return userInput.stringByReplacingOccurrencesOfString(" ", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
}

typealias UserInput = String

struct SanitizedString {
    let value : String

    init(input : UserInput)
    {
        value = sanitize(input)
    }
}

var a: SanitizedString = SanitizedString(input: "  Hello WHY  ")
println(a.value)    // HelloWHY

用戶註冊

咱們能夠用個元組來封裝用戶信息,好比存儲了郵件地址和臉書帳號:post

typealias UserInfo = (EmailAddress?, FacebookAccount?)

可是很不幸,這樣定義的類型會出現這種無效值:

let bogus : UserInfo = (nil, nil)

文中提出了這樣一個觀點:應該讓非法狀態不可表述。換句話說,若是咱們想避免這種既沒有郵箱地址也沒有臉書帳號的狀況出現,應該讓這種狀況沒法表現出來。

好比咱們能夠用這樣一個枚舉類型來表示用戶的狀態:

enum UserInfo {
  case Email (EmailAddress)
  case Facebook (FacebookAccount)
  case EmailAndFacebook (EmailAddress, FacebookAccount)
}

用戶的信息分爲三種狀況:

  • 郵件地址
  • 臉書帳戶
  • 郵件地址和臉書帳戶

看起來增長了不少額外的工做量,卻也收穫了不少。咱們能夠把一部分業務邏輯分離出來。

好比移除臉書帳號,若是用元組的方式進行刪除是這樣的:

func removeFacebookInfo(userInfo : (EmailAddress?, FacebookAccount?)) -> (EmailAddress?, FacebookAccount?) 
{
  return (userInfo.0, nil)
}

用枚舉類型來解決則能夠返回可選類型表示移除失敗的狀況:

func removeFacebookInfo(userInfo : UserInfo) -> UserInfo? 
{
  switch userInfo 
  {
    case let .EmailAndFacebook(email,_):
        return UserInfo.Email(email)
    default:
        return nil
  }
}

其餘

對於類型的設計,做者分享一段不錯的代碼 routes.swift

import Foundation

 enum Github {
    case Zen
    case UserProfile(String)
 }

 protocol Path {
    var path : String { get }
 }

 extension Github : Path {
    var path: String {
        switch self {
        case .Zen: return "/zen"
        case .UserProfile(let name): return "/users/\(name)"
        }
    }
 }

 let sample = Github.UserProfile("ashfurrow")

 println(sample.path) // Prints "/users/ashfurrow"

 // So far, so good

 protocol Moya : Path {
    var baseURL: NSURL { get }
    var sampleData: String { get } // Probably JSON would be better than AnyObject
 }

 extension Github : Moya {
    var baseURL: NSURL { return NSURL(string: "https://api.github.com")! }
    var sampleData: String {
        switch self {
        case .Zen: return "Half measures are as bad as nothing at all."
        case .UserProfile(let name): return "{login: \"\(name)\", id: 100}"
        }
    }
 }

 func url(route: Moya) -> NSURL {
    return route.baseURL.URLByAppendingPathComponent(route.path)
 }

 println(url(sample)) // prints https://api.github.com/users/ashfurrow

相關文章在這裏:Type-safe URL routes in Swift。就不展開討論了。

總結

做者對於如何合理有效的利用 Swift 的特性進行了思考和討論,值得一看。

感受寫總結很費時間,由於本身寫的沒有原文好,又沒時間展開討論只能匆匆記錄下來。下次嘗試寫成英文的鍛鍊一下英語能力吧。。。


References

相關文章
相關標籤/搜索