Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Boundaries In Practice // 実践的 “Boundaries”

Boundaries In Practice // 実践的 “Boundaries”

Talk given 3 Mar 2016 at try! Swift Tokyo

// Boundaries In Practice
One of the talks that I’ve enjoyed watching is Boundaries by Gary Bernhardt (https://www.destroyallsoftware.com/talks/boundaries). If you’ve seen a talk about functional programming in Swift, you’ve probably heard it being referenced. When I first watched the talk a few years ago, I understood the theory but wasn’t sure where exactly to apply the concepts. As I’ve been writing more and more Swift, I started to see that the concept of “Functional Core, Imperative Shell” applies not just to functional programming, but also to good engineering in general. In this talk, we’ll go over a couple of specific examples how these “boundaries” can help us write better, safer, and more future-proof Swift code. You don’t need to watch the Boundaries talk to understand this talk, but if you have time, definitely watch it because it’s a great talk.

// 実践的 “Boundaries”
Gary BernhardtさんのBoundariesの講演 (https://www.destroyallsoftware.com/talks/boundaries) 、ご存知ですか。ファンクショナルプログラミングの講演で良く参照される講演です。私は数年前に初めてこの講演を観た時、「Functional Core, Imperative Shell」の理論は理解できたのですが、実際どの様にこのコンセプトを実用化できるのか良く分かりませんでした。しかし、毎日少しずつSwiftを書いているうちに、このコンセプトはファンクショナルプログラミングだけではなく、良いエンジニアリングに一般的に適用できるのが明らかになってきました。今回はどの様に「Boundaries」を利用して、もっと良い、もっと安全なそして将来性のあるSwiftを書くことが出来るのかを幾つかの事例を紹介しながらお話しさせて頂きます。「Boundaries」をご覧になった事がなくても私の話をご理解頂けると思いますが、もしお時間があれば是非一度ご覧下さい。素晴らしい話です。

Avatar for Ayaka Nonaka

Ayaka Nonaka

March 03, 2016
Tweet

More Decks by Ayaka Nonaka

Other Decks in Programming

Transcript

  1. “ALL YOU REALLY KNOW MAYBE IS THAT YOUR TASTE IS

    IMPROVING A LOT FASTER THAN YOUR ABILITY.” ANDY MATUSCHAK, ADVANCED IOS APPLICATION ARCHITECTURE AND PATTERNS (WWDC 2014)
  2. struct Story { let ID: String let title: String let

    message: String let sender: User let recipient: User let date: NSDate // ... }
  3. class StoryDetailViewController: UIViewController { private let titleView: StoryTitleView private let

    senderView: AvatarView private let recipientView: AvatarView private let dateLabel: DateLabel init(story: Story) { titleView = StoryTitleView(story: story) senderView = AvatarView(user: story.sender) recipientView = AvatarView(user: story.recipient) dateLabel = DateLabel(date: story.date) } // ... }
  4. class StoryDetailViewController: UIViewController { private let titleView: StoryTitleView private let

    senderView: AvatarView private let recipientView: AvatarView private let dateLabel: DateLabel init(story: Story) { /* Same as before લͱಉ͡ */ } init(storyID: String) { // Hmmmmmmm.ɹ͜ΕԿͩΖ͏ʁ } }
  5. class StoryDetailViewController: UIViewController { let storyID: String private var titleView:

    StoryTitleView? private var senderView: AvatarView? private var recipientView: AvatarView? private var dateLabel: DateLabel? init(story: Story) { /* Same as before લͱಉ͡ */ } init(storyID: String) { self.storyID = storyID titleView = nil senderView = nil recipientView = nil dataLabel = nil } // Load everything from API in viewDidLoad? // viewDidLoadͰAPI͔Βϩʔυʁ!ɹ }
  6. class StoryContainerViewController: UIViewController { let storyID: String init(storyID: String) {

    self.storyID = storyID } override func viewDidLoad() { client.showStory(ID: storyID) { result in switch result { case .Success(let story): let viewController = StoryDetailViewController(story: story) self.addChildViewController(viewController) self.view.addSubview(viewController.view) viewController.view.frame = view.bounds viewController.didMoveToParentViewController(self) case .Error(let error): // Show errorɹΤϥʔදࣔ } } } }
  7. protocol RemoteContentProviding { typealias Content func fetchContent(completion: Result<Content, Error> ->

    Void) func viewControllerForContent(content: Result<Content, Error>) -> UIViewController }
  8. class RemoteContentContainerViewController<T: RemoteContentProviding>: UIViewController { let provider: T init(provider: T)

    { self.remoteContentProvider = remoteContentProvider super.init(nibName: nil, bundle: nil) } override func viewDidLoad() { super.viewDidLoad() provider.fetchContent { content in let viewController = self.provider.viewControllerForContent(content) self.addChildViewController(viewController) self.view.addSubview(viewController.view) viewController.view.frame = view.bounds viewController.didMoveToParentViewController(self) } } }
  9. struct StoryProvider: RemoteContentProviding { let ID: String func fetchContent(completion: Result<Story,

    Error> -> Void) { client.showStory(ID: ID, completion: completion) } func viewControllerForContent(content: Result<Story, Error>) -> UIViewController { switch content { case .Success(let story): return StoryDetailViewController(story: story) case .Error(_): return ErrorViewController(title: "Could not find story.") } } }
  10. class StoryDetailViewController: UIViewController { let storyID: String private var titleView:

    StoryTitleView? private var senderView: AvatarView? private var recipientView: AvatarView? private var dateLabel: DateLabel? init(story: Story) { /* Same as before */ } init(storyID: String) { self.storyID = storyID titleView = nil senderView = nil recipientView = nil dataLabel = nil } // Load everything from API in viewDidLoad? // viewDidLoadͰAPI͔Βϩʔυʁ! }
  11. class StoryDetailViewController: UIViewController { private let titleView: StoryTitleView private let

    senderView: AvatarView private let recipientView: AvatarView private let dateLabel: DateLabel init(story: Story) { titleView = StoryTitleView(story: story) senderView = AvatarView(user: story.sender) recipientView = AvatarView(user: story.recipient) dateLabel = DateLabel(date: story.date) } // ... }
  12. @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? private

    lazy var applicationCoordinator: ApplicationCoordinator = { return ApplicationCoordinator(window: self.window!) }() func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { window = UIWindow(frame: UIScreen.mainScreen().bounds) applicationCoordinator.start() return true } }
  13. class ApplicationCoordinator: Coordinator { let window: UIWindow let rootViewController =

    UITabBarController() let wordsNavigationController = UINavigationController() let phrasesNavigationController = UINavigationController() let wordsCoordinator: WordsCoordinator let phrasesCoordinator: PhrasesCoordinator init(window: UIWindow) { self.window = window let viewControllers = [wordsNavigationController, phrasesNavigationController] self.rootViewController.setViewControllers(viewControllers, animated: false) self.wordsCoordinator = WordsCoordinator(presenter: wordsNavigationController) self.phrasesCoordinator = PhrasesCoordinator(presenter: phrasesNavigationController) } func start() { window.rootViewController = rootViewController wordsCoordinator.start() phrasesCoordinator.start() window.makeKeyAndVisible() } }
  14. class WordsCoordinator: Coordinator { let presenter: UINavigationController private let listViewController:

    ListViewController<Word> private let dataSource: WordsDataSource init(presenter: UINavigationController) { self.presenter = presenter self.dataSource = WordsDataSource() self.listViewController = ListViewController<Word>() self.listViewController.title = "Words" self.listViewController.items = dataSource.words self.listViewController.configureCell = { cell, item in cell.item = item } self.listViewController.didSelectItem = { item in presenter.pushViewController(WordViewController(word: item), animated: true) } } func start() { presenter.pushViewController(listViewController, animated: false) } }
  15. class PhrasesCoordinator: Coordinator { let presenter: UINavigationController private let listViewController:

    ListViewController<Phrase> private let dataSource: PhrasesDataSource init(presenter: UINavigationController) { self.presenter = presenter self.dataSource = PhrasesDataSource() self.listViewController = ListViewController<Phrase>() self.listViewController.title = "Phrases" self.listViewController.items = dataSource.phrases self.listViewController.configureCell = { cell, item in cell.item = item } self.listViewController.didSelectItem = { item in presenter.pushViewController(PhraseViewController(phrase: item), animated: true) } } func start() { presenter.pushViewController(listViewController, animated: false) } }