2
1

SwiftでFactory Methodパターンを使ってみる

Last updated at Posted at 2024-07-16

Factory Methodについて

インスタンスの生成方法をスーパークラス(Swiftではプロトコルの場合も)で定め、具体的な処理をサブクラスで行うパターンをFactory Methodパターンと呼ぶ。
オブジェクトの生成と具体的な処理を分離することで、大幅に可読性が上がり、テストの柔軟性も向上する。

本記事では、上記のメリットを、Swiftのコードを用いて体感してみる。
Clean ArchitectureにFactory Methodパターンを適用してみる。今回はDomain層からPresentation層にデータを流す部分に焦点を当てる。

Domain層

Entity

Message.swift
struct Message: Identifiable {
    var id: String {
        description
    }
    var description: String
    var email: String
}

上記のようなMessageを取得し、画面に表示するコードを書いてみる。

UseCase

MessageUse.swift
protocol MessageUseCase {
    var messages: [Message] {get set}
}
MessageUseCaseImpl.swift
class MessageUseCaseImpl: MessageUseCase  {
    var messages: [Message]

    init() {
        self.messages = [
            .init(
                description: "hoge",
                email: "hoge@gmail.com"
            ),
            .init(
                description: "fuga",
                email: "fuga@gmail.com"
            )
        ]
    }
}

本来は、Data層からMessageを取得するので、UseCaseで状態管理などの実装が必要であるが、今回は諸々省略。

Presentation層

Presenter

MessagePresenter.swift
class MessagePresenter: ObservableObject {
    @Published var messages: [Message]

    init(messages: [Message] = []) {
        self.messages = messages
    }
}
MessagePresenterImpl.swift
class MessagePresenterImpl: MessagePresenter {
    let useCase: MessageUseCase

    init(useCase: MessageUseCase) {
        self.useCase = useCase
        super.init(messages: useCase.messages)
    }
}

View

MessageView.swift
import SwiftUI

struct MessageView: View {
    @StateObject var presenter: MessagePresenter

    var body: some View {
        List(presenter.messages) { message in
            Text(message.description)
        }
    }
}

Preview

private extension MessagePresenter {
    static var mock: MessagePresenter {
        .init(messages: [
            .init(description: "Factory", email: "Factory"),
            .init(description: "Factory", email: "Factory")
        ])
    }
}

#Preview("Presenter") {
    MessageView(presenter: MessagePresenter.mock)
}

この実装でも、一見正しくモックは差し込めているが、もしメッセージについての画面以外も多数実装する必要がある、あるいは可能性がある状況であったり、複雑なロジックで生成された動的なPresenterのモックがいくつか必要な場合、より良い実装ができる。

Factory

PresenterFactory.swift
protocol PresenterFactory {
    func getMessagePresenter() -> MessagePresenter
    // 以下にMessage以外のPresenterの生成も書ける
}
PresenterFactoryImpl.swift
class PresenterFactoryImpl: PresenterFactory {
    func getMessagePresenter() -> MessagePresenter {
        MessagePresenterImpl(useCase: MessageUseCaseImpl())
    }
    // 以下にMessage以外のPresenterの生成も書ける
}

このように、MessagePresenterのオブジェクトを返すメソッドを定義する。こうすることでPresenterの生成ロジックを他のViewでも共有でき、コードの可読性が向上する。
また、様々なUseCaseをPresenterに注入できるため、テストの柔軟性も向上する。

MessageView.swift
struct MessageFactoryPatternView: View {
    @StateObject var presenter: MessagePresenter

    // viewFactoryを受け取るイニシャライザを追加
    init(viewFactory: MessagePresenterFactory) {
        _presenter = StateObject(wrappedValue: viewFactory.getMessagePresenter())
    }

    var body: some View {
        List(presenter.messages) { message in
            Text(message.description)
        }
    }
}

// Preview用のPresenterFactoryに準拠したFactory
private final class MessageViewFactory: PresenterFactory {
    func getMessagePresenter() -> MessagePresenter {
        MessagePresenter(messages: [
            .init(description: "Factory", email: "Factory"),
            .init(description: "Factory", email: "Factory")
        ])
    }
}

#Preview("Factory") {
    MessageFactoryPatternView(viewFactory: MessageViewFactory())
}

そして、Viewを上記のように書き換える。これで、Message以外のViewが存在する場合、オブジェクトの生成方法をすべてのPresenterで統一することができるようになった。

まとめ

Factory Methodパターンはこのようなケース以外でも、様々な場面で応用できる。そしてオブジェクトの生成を分離するだけで、格段にスケーラビリティが向上することが分かった。大規模なアプリケーション開発では、このような設計も選択肢に入れると良いと思う。

参考

2
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
2
1