LoginSignup
1
1

More than 3 years have passed since last update.

SwiftでMiddlewareパターン実装してみた

Last updated at Posted at 2019-07-03

普段iOSを開発しているのですが、最近Web入門していてMiddlewareが何してるか分からなかったので、パターン理解のためにも手を動かしてみました。Swiftで再実装してます。

実装してみた記事なので、主目的としては Swiftで実装したらこうなったよ って言うのを「へぇ〜」って眺めてもらう事になります。コメントあればバシバシ欲しいです!

以下の記事を参考にしました。
https://qiita.com/suin/items/5c53f72e9c7e2c05b18f

@suin さんの記事なのですが、UMLや設計思想も書いてあって非常に参考になったのでオススメします。

入力の加工

middleware.swift
import UIKit

struct Input {
    var selfIntroduction: String
}

struct Output {
    var text: String

    func printText() {
        print(text)
    }

}

protocol Handler {
    func handle(input: Input) -> Output
}

protocol Middleware {
    func process(input: Input, next: Handler) -> Output
}

final class PipelineBuilder {

    private var middlewares: [Middleware] = []

    func use(middleware: Middleware) -> PipelineBuilder {
        middlewares.insert(middleware, at: 0)
        return self
    }

    func build(handler: Handler) -> Handler {
        var mutateHandler = handler
        middlewares.forEach { middleware in
            mutateHandler = MiddlewareHandler(middleware: middleware, handler: mutateHandler)
        }
        return mutateHandler
    }


}

struct MiddlewareHandler: Handler {

    private var middleware:Middleware
    private var handler: Handler

    init(middleware: Middleware, handler: Handler) {
        self.middleware = middleware
        self.handler = handler
    }

    func handle(input: Input) -> Output {
        return self.middleware.process(input: input, next: handler)
    }


}

struct OuterMiddleware: Middleware {

    func process(input: Input, next: Handler) -> Output {
        var mutateInput = input
        mutateInput.selfIntroduction.append(contentsOf: "I'm 24 years old.")
        return next.handle(input: mutateInput)
    }

}

struct InnerMiddleware: Middleware {

    func process(input: Input, next: Handler) -> Output {
        var mutateInput = input
        mutateInput.selfIntroduction.append(contentsOf: "My hobby is playing a guiter.")
        return next.handle(input: mutateInput)
    }

}


struct ConcreteHander: Handler {

    func handle(input: Input) -> Output {
        return Output(text: input.selfIntroduction)
    }

}

let pipeline = PipelineBuilder()
    .use(middleware: OuterMiddleware())
    .use(middleware: InnerMiddleware())
    .build(handler: ConcreteHander())

let output = pipeline.handle(input: Input(selfIntroduction: "My name is John."))
output.printText() // My name is John.I'm 24 years old.My hobby is playing a guiter.

外側のミドルウェアから順番に実行されていき、内側のミドルウェアまでたどり着くにつれて入力のテキストに文字が足されていき、ハンドラでOutput確定します。出力printText()を実行すると、Middlewareで付け足した文字も含めたテキストが外部出力されます。

PipelineBuilder が、func use(middleware: Middleware) -> PipelineBuilder で自身を返すのでメソッドチェーンの実行が可能で、 func build(handler: Handler) -> Handler でHanlderしか返さないのでチェーンを終了できるのが良いですね。

insert(newItem, at: 0) みたいな先頭挿入でなく、items.append(newItem)build の時にmiddlewares配列をリバースして使う実装でも実現可能かな?と思いました。

出力の加工

上記のコードは入力を加工する分かりやすいサンプルとなりましたが、この設計は

  1. 次のMiddlewareへの入力を加工できる
  2. Middlewareでの出力を加工できる

という特性を持っており、この性質のおかげでRequestの後、Reponseの前に処理を挟む事を可能としてくれるのが、Middlewareパターンの良い所かなと思います。なのでちょっとコードを修正します。

以下のようにmiddlewareでの出力を取り出して加工を加える事で、内側から外側へ向かって加工を行うことも出来ました。

ModifyOutputMiddleware.swift
struct OuterMiddleware: Middleware {

    func process(input: Input, next: Handler) -> Output {
        var mutateInput = input
        mutateInput.selfIntroduction.append(contentsOf: "I'm 24 years old.")
        var mutateOutput = next.handle(input: mutateInput)
        mutateOutput.text.append(contentsOf: "Thank you!")
        return mutateOutput
    }

}

struct InnerMiddleware: Middleware {

    func process(input: Input, next: Handler) -> Output {
        var mutateInput = input
        mutateInput.selfIntroduction.append(contentsOf: "My hobby is playing a guiter.")
        var mutateOutput = next.handle(input: mutateInput)
        mutateOutput.text.append(contentsOf: "Goodbye!")
        return mutateOutput
    }

}

let pipeline = PipelineBuilder()
    .use(middleware: OuterMiddleware())
    .use(middleware: InnerMiddleware())
    .build(handler: ConcreteHander())

let output = pipeline.handle(input: Input(selfIntroduction: "My name is John."))
output.printText() // My name is John.I'm 24 years old.My hobby is playing a guiter.Goodbye!Thank you!

この入力時の外側→内側順での加工、出力時の内側→外側順での加工が行えるのがMiddlewareパターンの特筆すべきところかと思いました。

感想

デコレーターパターンやCoRなどをベースにしてるっぽい?

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1