普段iOSを開発しているのですが、最近Web入門していてMiddlewareが何してるか分からなかったので、パターン理解のためにも手を動かしてみました。Swiftで再実装してます。
実装してみた記事なので、主目的としては Swiftで実装したらこうなったよ って言うのを「へぇ〜」って眺めてもらう事になります。コメントあればバシバシ欲しいです!
以下の記事を参考にしました。
https://qiita.com/suin/items/5c53f72e9c7e2c05b18f
@suin さんの記事なのですが、UMLや設計思想も書いてあって非常に参考になったのでオススメします。
入力の加工
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
配列をリバースして使う実装でも実現可能かな?と思いました。
出力の加工
上記のコードは入力を加工する分かりやすいサンプルとなりましたが、この設計は
- 次のMiddlewareへの入力を加工できる
- Middlewareでの出力を加工できる
という特性を持っており、この性質のおかげでRequestの後、Reponseの前に処理を挟む事を可能としてくれるのが、Middlewareパターンの良い所かなと思います。なのでちょっとコードを修正します。
以下のようにmiddlewareでの出力を取り出して加工を加える事で、内側から外側へ向かって加工を行うことも出来ました。
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などをベースにしてるっぽい?