🐙

フロントエンド設計思想とライブラリ/API詳細一覧

に公開

(デスクトップを除く)フロントエンドの設計思想が何十年経っても新しい概念がでてくるのはなぜなのか考察したく、初手として現状について改めて整理してみました。

以降は Gemini の出力をベースにしています

設計思想とライブラリ/API詳細一覧

設計思想 (アーキテクチャ) 主なライブラリ/フレームワーク APIの設計思想・特徴 主要なクラス・API名 特徴・主な用途
MVC Ruby on Rails CoC (Convention over Configuration) ApplicationController, ActiveRecord::Base, routes.rb, ERBテンプレート 大規模向け / フルスタック。
MVC UIKit ターゲット・アクション、デリゲートパターン UIViewController, UIView, @IBAction, UITableViewDelegate 伝統的なApple製UIフレームワーク。
MVP Jetpack以前のAndroid インターフェースによる疎結合 LoginView (Interface), LoginPresenter (Class) ※自前で定義 中〜大規模向け。テスト容易性が高い。
MVVM Jetpack Compose (with ViewModel) リアクティブストリーム、ライフサイクル対応 ViewModel, StateFlow, mutableStateOf, @Composable, collectAsState() 中〜大規模向け / Google推奨。
MVVM SwiftUI (with ObservableObject) プロパティラッパーによる宣言的データバインディング @State, @StateObject, @Published, ObservableObject, ObservedObject 中〜大規模向け / Apple推奨。
MVVM Vue.js (with Pinia) リアクティブなデータオブジェクト ref(), reactive(), computed(), watch(), defineStore() (Pinia) 大規模向け / 段階的導入。
MVVM MobX 透過的なリアクティブプログラミング (TFRP) makeObservable, observable, action, computed, autorun, observer 中〜大規模向け / リアクティブ。
Flux Redux 関数型 (純粋関数)、イミュータブル (不変性) createStore, reducer, dispatch, action, useSelector, useDispatch 中〜大規模向け / 予測可能性。
MVI Android (RxJava/Flow) すべてをストリームとして扱う .intents(), .publish(), .scan() or .reduce(), render() ※概念を実装 リアクティブ特化。
BLoC bloc / flutter_bloc イベント駆動ストリーム Bloc, Event, State, add(event), emit(state), BlocBuilder 中〜大規模向け / リアクティブ。
TCA TCA (ライブラリ名) 関数型、複合的 Reducer, State, Action, Environment, Store, WithViewStore 中〜大規模向け / 超厳格。
アトミック Atom Jotai useStateライクなミニマルAPI atom, useAtom 小〜大規模向け / パフォーマンス。
(柔軟/シンプル) React Hooks フックによる機能合成 useState, useEffect, useContext, useReducer, useCallback 小規模向け / あらゆる規模の基礎部品。
(柔軟/シンプル) Zustand フックベースのミニマルAPI、Context不要 create 小〜中規模向け / シンプル。
(柔軟/シンプル) Riverpod DIコンテナ、宣言的なProvider Provider, StateNotifierProvider, FutureProvider, ref.watch, ref.read あらゆる規模に対応 / DI機能が強力。
(柔軟/シンプル) Svelte コンパイラによる魔法、リアクティブな代入 let (リアクティブ変数), $: (リアクティブ宣言), bind:value 小〜中規模向け / コンパイラ。

主要なAPIの役割とサンプルコード

MVC (Ruby on Rails)

サーバーサイドMVCでは、状態は主にサーバー(DBやセッション)に保持されます。ここではセッションを使った例を示します。

  • 主要APIと役割
    • config/routes.rb: URLとControllerのアクションを紐付けます。
    • Controller: HTTPリクエストを受け、ロジックを実行し、Viewに渡すデータ(インスタンス変数)を用意します。sessionオブジェクトでユーザーごとの状態を保持できます。
    • View (ERB): Controllerから渡されたデータを元にHTMLを生成します。
# config/routes.rb
Rails.application.routes.draw do
  # /counter にアクセスしたらcountersコントローラーのshowアクションを呼ぶ
  get 'counter', to: 'counters#show'
  # /counter/increment にアクセスしたらincrementアクションを呼ぶ
  post 'counter/increment', to: 'counters#increment'
end

# app/controllers/counters_controller.rb
class CountersController < ApplicationController
  def show
    # セッションから値を取得、なければ0
    @count = session[:count] ||= 0
  end

  def increment
    # セッションの値をインクリメント
    session[:count] = (session[:count] || 0) + 1
    # counterの表示ページにリダイレクト
    redirect_to counter_path
  end
end

# app/views/counters/show.html.erb
<h1>Counter</h1>
<p>Count: <%= @count %></p>
<%# ボタンを押すと/counter/incrementにPOSTリクエストが送られる %>
<%= button_to "Increment", counter_increment_path, method: :post %>

MVC (UIKit)

UIKitでは、ViewControllerがControllerとViewの両方の役割を色濃く担います。

  • 主要APIと役割
    • UIViewController: 画面とUIのライフサイクルを管理します。
    • @IBOutlet: Storyboard上のUI要素(UILabelなど)をコード内のプロパティに接続します。
    • @IBAction: Storyboard上のUIイベント(ボタンタップなど)をコード内のメソッドに接続します。
// CounterViewController.swift
import UIKit

class CounterViewController: UIViewController {
    // Storyboard上のUILabelと接続
    @IBOutlet weak var countLabel: UILabel!

    // Modelとしての状態
    private var count = 0 {
        didSet {
            // 状態が変更されたらViewを更新
            updateLabel()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        updateLabel()
    }

    // Storyboard上のUIButtonと接続
    @IBAction func incrementButtonTapped(_ sender: UIButton) {
        // Controllerがユーザー入力を受けてModelを更新
        count += 1
    }

    private func updateLabel() {
        countLabel?.text = "Count: \(count)"
    }
}

MVP (Jetpack以前のAndroid)

Presenterがロジックを持ち、Viewはインターフェースを通じて操作される「受け身」の存在になります。

  • 主要APIと役割
    • Contract Interface: ViewとPresenterが互いにどのようなメソッドを持つべきかを定義する契約書。
    • Presenter: ビジネスロジックを持ち、Viewインターフェースを介してUI更新を指示します。
    • Activity/Fragment: Contract.Viewを実装し、UIイベントをPresenterに伝達します。
// CounterContract.java (契約書)
public interface CounterContract {
    interface View {
        void showCount(int count);
    }
    interface Presenter {
        void increment();
        void attachView(View view);
        void detachView();
    }
}

// CounterPresenter.java (ロジック担当)
public class CounterPresenter implements CounterContract.Presenter {
    private CounterContract.View view;
    private int count = 0;

    @Override
    public void increment() {
        count++;
        if (view != null) {
            view.showCount(count);
        }
    }
    // ... attachView, detachViewの実装 ...
}

// CounterActivity.java (UI担当)
public class CounterActivity extends AppCompatActivity implements CounterContract.View {
    private TextView countTextView;
    private CounterContract.Presenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        presenter = new CounterPresenter();
        presenter.attachView(this);

        countTextView = findViewById(R.id.countTextView);
        Button incrementButton = findViewById(R.id.incrementButton);
        incrementButton.setOnClickListener(v -> presenter.increment());
    }

    @Override
    public void showCount(int count) {
        countTextView.setText("Count: " + count);
    }
    // ... onDestroyでpresenter.detachView() ...
}

MVVM (Jetpack Compose with ViewModel)

  • 主要APIと役割
    • ViewModel: UIの状態とビジネスロジックを保持する、ライフサイクルを意識したクラス。
    • StateFlow: 現在の状態を保持し、更新をストリームとして公開するためのホットなFlow。
    • @Composable: UIを構築するための宣言的な関数。
    • collectAsState(): StateFlowをComposableが監視できるStateオブジェクトに変換する。
// CounterViewModel.kt
class CounterViewModel : ViewModel() {
    // UIに公開する読み取り専用の状態
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> = _count.asStateFlow()

    // 状態を更新するロジック
    fun increment() {
        _count.value++
    }
}

// CounterScreen.kt
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
    // ViewModelのStateFlowを購読
    val count by viewModel.count.collectAsState()

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Count: $count")
        Button(onClick = { viewModel.increment() }) {
            Text("Increment")
        }
    }
}

MVVM (SwiftUI with ObservableObject)**

  • 主要APIと役割
    • ObservableObject: Viewが変更を監視できるオブジェクトにするためのプロトコル。
    • @Published: プロパティが変更されたときに、自動的にViewに変更を通知するプロパティラッパー。
    • @StateObject: ViewがObservableObjectのインスタンスを生成・所有するためのプロパティラッパー。
// CounterViewModel.swift
import SwiftUI

// ViewModelはObservableObjectに準拠
class CounterViewModel: ObservableObject {
    // @Publishedを付けると、変更が自動でViewに通知される
    @Published var count = 0

    func increment() {
        count += 1
    }
}

// CounterView.swift
struct CounterView: View {
    // ViewがViewModelを所有・監視
    @StateObject private var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            Text("Count: \(viewModel.count)")
            Button("Increment") {
                viewModel.increment()
            }
        }
    }
}

Flux (Redux with React Hooks)

  • 主要APIと役割
    • reducer: (state, action)を受け取り、新しいstateを返す純粋関数。状態変更ロジックの本体。
    • dispatch: action(状態変更の意図を表すオブジェクト)をreducerに送るための関数。
    • useSelector: Storeから状態の一部をコンポーネントで購読するためのフック。
    • useDispatch: dispatch関数を取得するためのフック。
// store.js
import { createStore } from 'redux';

// Reducer: 状態の更新方法を定義
function counterReducer(state = { count: 0 }, action) {
    switch (action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 };
        default:
            return state;
    }
}
export const store = createStore(counterReducer);

// Counter.jsx
import { useSelector, useDispatch } from 'react-redux';

function Counter() {
    // Storeから状態を取得
    const count = useSelector(state => state.count);
    // Actionを送るためのdispatch関数を取得
    const dispatch = useDispatch();

    return (
        <div>
            <p>Count: {count}</p>
            {/* Actionをdispatchして状態の更新を依頼 */}
            <button onClick={() => dispatch({ type: 'INCREMENT' })}>
                Increment
            </button>
        </div>
    );
}

アトミック (Jotai)

  • 主要APIと役割
    • atom: 状態の最小単位(原子)を定義する。
    • useAtom: useStateとほぼ同じAPIで、グローバルなatomを読み書きするためのフック。
// atoms.js
import { atom } from 'jotai';

// グローバルにアクセス可能な状態の原子を定義
export const countAtom = atom(0);

// Counter.jsx
import { useAtom } from 'jotai';
import { countAtom } from './atoms';

function Counter() {
    // useStateのようにatomを扱う
    const [count, setCount] = useAtom(countAtom);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(c => c + 1)}>
                Increment
            </button>
        </div>
    );
}

柔軟/シンプル (Zustand)

  • 主要APIと役割
    • create: 状態とそれを更新するアクションを持つストアを作成する関数。戻り値はカスタムフック。
// useCounterStore.js
import { create } from 'zustand';

// 状態とアクションをまとめてストアを作成
export const useCounterStore = create((set) => ({
    count: 0,
    increment: () => set(state => ({ count: state.count + 1 })),
}));

// Counter.jsx
import { useCounterStore } from './useCounterStore';

function Counter() {
    // 作成したフックから状態とアクションを取得
    const count = useCounterStore(state => state.count);
    const increment = useCounterStore(state => state.increment);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={increment}>Increment</button>
        </div>
    );
}

柔軟/シンプル (Riverpod)

  • 主要APIと役割
    • StateNotifier: イミュータブルな状態を管理するためのクラス。ビジネスロジックをここに記述する。
    • StateNotifierProvider: StateNotifierをDIコンテナに登録し、UIから利用可能にするためのProvider。
    • ref.watch: Providerを購読し、状態の変更に応じてUIを再ビルドさせる。
    • ref.read().notifier: StateNotifierのインスタンスを取得し、メソッドを呼び出す。
// counter_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 状態とロジックを持つクラス
class Counter extends StateNotifier<int> {
  Counter() : super(0); // 初期状態
  void increment() => state++;
}

// Providerをグローバルに定義
final counterProvider = StateNotifierProvider<Counter, int>((ref) {
            ElevatedButton(
  return Counter();
});

// counter_view.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// ConsumerWidgetを継承
class CounterView extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Providerを購読して状態を取得
    final int count = ref.watch(counterProvider);

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Count: $count'),
              // notifier経由でメソッドを呼び出す
              onPressed: () => ref.read(counterProvider.notifier).increment(),
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

MVVM (MobX with React)

  • 主要APIと役割
    • makeObservable: クラスのプロパティを監視可能にします。
    • observable: 監視対象の状態プロパティ。
    • action: 状態を変更するメソッド。
    • observer: Reactコンポーネントを監視可能にし、関連するobservableの変更時に自動で再レンダリングさせる高階コンポーネント。
// CounterStore.js
import { makeObservable, observable, action } from 'mobx';

class CounterStore {
    count = 0;

    constructor() {
        makeObservable(this, {
            count: observable,
            increment: action,
        });
    }

    increment() {
        this.count++;
    }
}
export const counterStore = new CounterStore();


// Counter.jsx
import { observer } from 'mobx-react-lite';
import { counterStore } from './CounterStore';

// observerでコンポーネントをラップする
const Counter = observer(() => {
    return (
        <div>
            <p>Count: {counterStore.count}</p>
            <button onClick={() => counterStore.increment()}>
                Increment
            </button>
        </div>
    );
});
export default Counter;

BLoC (Flutter)

  • 主要APIと役割
    • Event: ユーザー操作などを表現するクラス。Blocへの入力。
    • State: UIの状態を表現するクラス。Blocからの出力。
    • Bloc: Eventを受け取り、ロジックを実行してStateをemit(出力)します。
    • BlocBuilder: BlocのStateの変更に応じてUIの一部を再構築します。
// counter_event.dart
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}

// counter_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_event.dart';

class CounterBloc extends Bloc<CounterEvent, int> { // <Event, State>
  CounterBloc() : super(0) { // 初期状態
    on<IncrementEvent>((event, emit) {
      emit(state + 1); // 新しいStateを出力
    });
  }
}

// counter_view.dart
// ...
// BlocProviderでCounterBlocをDIコンテナに登録しておく
// ...
BlocBuilder<CounterBloc, int>(
  builder: (context, count) { // countは現在のState
    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(
          onPressed: () {
            // BlocにEventを送る
            context.read<CounterBloc>().add(IncrementEvent());
          },
          child: Text('Increment'),
        ),
      ],
    );
  },
)

TCA (The Composable Architecture)

  • 主要APIと役割
    • Reducer: State, Action, Environmentを扱う中心的なロジック。
    • State: アプリケーションの状態を保持するstruct。
    • Action: ユーザー操作や副作用の結果を表すenum。
    • Store: Reducerをラップし、UIと接続するランタイム。
    • WithViewStore: SwiftUIのViewでStoreを安全に扱うためのラッパー。
// CounterFeature.swift
import ComposableArchitecture

struct CounterState: Equatable {
    var count = 0
}

enum CounterAction: Equatable {
    case incrementButtonTapped
    case decrementButtonTapped
}

let counterReducer = Reducer<CounterState, CounterAction, Void> { state, action, _ in
    switch action {
    case .incrementButtonTapped:
        state.count += 1
        return .none
    case .decrementButtonTapped:
        state.count -= 1
        return .none
    }
}

// CounterView.swift
import SwiftUI
import ComposableArchitecture

struct CounterView: View {
    let store: Store<CounterState, CounterAction>

    var body: some View {
        WithViewStore(self.store) { viewStore in
            VStack {
                Text("Count: \(viewStore.count)")
                HStack {
                    Button("−") { viewStore.send(.decrementButtonTapped) }
                    Button("+") { viewStore.send(.incrementButtonTapped) }
                }
            }
        }
    }
}

柔軟/シンプル (React Hooks)

ここではuseReducerを使った、より管理しやすいローカル状態の例を示します。

  • 主要APIと役割
    • useReducer: useStateの代替。コンポーネント内でローカルなReduxのように状態管理ができます。状態更新ロジックが複雑な場合に適しています。
// Counter.jsx
import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        default:
            throw new Error();
    }
}

function Counter() {
    const [state, dispatch] = useReducer(reducer, initialState);

    return (
        <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
            <button onClick={() => dispatch({ type: 'increment' })}>+</button>
        </div>
    );
}

柔軟/シンプル (Svelte)

Svelteは、フレームワークがコンパイラとして働き、ボイラープレートを極限まで削減します。

  • 主要APIと役割
    • <script>ブロック: コンポーネントのロジックを記述します。
    • let: 変数を宣言するだけで、その変数はリアクティブな状態になります。
    • on:click: イベントハンドラを宣言的に記述します。
<script>
    // これだけでリアクティブな状態になる
    let count = 0;

    function increment() {
        count += 1;
    }
</script>

<main>
    <h1>Counter</h1>
    <p>Count: {count}</p>

    <button on:click={increment}>
        Increment
    </button>
</main>

(特別にピックアップ)RIBs

RIBsは特に大規模なアプリケーションにおいて、チーム間の連携やコードの責務分離といった課題を解決するために設計されており、他のアーキテクチャとは一線を画す思想を持っています。


RIBs (Router, Interactor, Builder)

1. 概要と核心的な思想

RIBsは、Uberが自社の巨大なiOSアプリ(一つのアプリを数百人のエンジニアが開発)における課題を解決するために生み出した、クロスプラットフォーム対応のアーキテクチャです。その名前は、中核をなす3つの要素Router, Interactor, Builderの頭文字から取られています。

  • ビジネスロジック中心: アプリケーションの最も重要な部分であるビジネスロジックをInteractorに集約します。InteractorはUI(View)から完全に独立しているため、テストが非常に容易になります。
  • ツリー構造とスコープ: アプリケーション全体を、親子関係を持つRIBの木(ツリー)として構築します。各RIBは独立した機能単位(スコープ)となり、状態や依存関係をカプセル化します。これにより、機能ごとの独立性が高まり、チーム間の衝突を防ぎます。
  • 強力な依存性注入 (DI): BuilderComponentという仕組みを使って、各コンポーネントが必要とする依存関係を明示的に注入します。これにより、各部品の再利用性とテスト容易性が向上します。
  • 責務の超・明確な分離:
    • Router: 画面遷移とRIBの親子関係の管理にのみ責任を持つ。
    • Interactor: ビジネスロジックと状態管理にのみ責任を持つ。
    • Builder: RIBの各部品を組み立て、依存性を注入することにのみ責任を持つ。

2. 主要な構成要素と役割

RIBsの一つのユニット(RIBletとも呼ばれる)は、主に以下の要素で構成されます。

要素 役割
Interactor 「頭脳」。ビジネスロジックと状態管理を担当。ViewやRouterからの入力を受け、ロジックを実行し、結果をRouter(子RIBの制御)やPresenter/View(UI更新)に伝える。
Router 「ナビゲーター」。自身のInteractorとViewを接続し、子RIBのAttach/Detach(画面遷移に相当)を管理する。画面遷移のロジックがここに集約される。
Builder 「工場」。RIBの全コンポーネント(Interactor, Router, ViewController, Componentなど)をインスタンス化し、それぞれの依存関係を解決して注入(DI)する。
ViewController (View) 「見た目」。UIのレイアウトと表示のみに責任を持つ。ユーザーからの入力を受け付け、それをInteractorに(通常はPresenter経由で)通知する。
Component 「部品供給源」。BuilderがDIを行うための依存関係(親RIBから引き継ぐものなど)を供給する。
Presenter (任意) 「通訳」。InteractorとViewControllerの間に置かれるアダプター。Interactorから受け取ったビジネスデータを、ViewControllerが表示できる形式(ViewModelなど)に変換する。

3. サンプルコード(カウンターアプリ)

RIBsはその構造上、多くのファイルとお決まりのコード(ボイラープレート)を必要とします。以下に、カウンター機能を実装するCounterRIBの構成要素を示します。

① CounterBuilder.swift

CounterRIBを組み立てる工場。依存関係を解決し、各部品をインスタンス化して接続します。

  • 主要APIと役割
    • Component: 親から引き継ぐ依存関係と、このRIB内で生成する依存関係を定義します。
    • build(withListener:): Builderのメインメソッド。RIBを構築してRouterを返します。

import RIBs

// ... (プロトコルの定義) ...

final class CounterComponent: Component<EmptyComponent> {
    // このComponentがViewControllerを生成する責務を持つ
    var viewController: CounterViewController {
        return CounterViewController()
    }
}

// MARK: - Builder

final class CounterBuilder: Builder<EmptyComponent>, CounterBuildable {
    override init(dependency: EmptyComponent) {
        super.init(dependency: dependency)
    }

    func build(withListener listener: CounterListener) -> CounterRouting {
        let component = CounterComponent(dependency: dependency)
        let viewController = component.viewController
        let interactor = CounterInteractor(presenter: viewController)
        interactor.listener = listener

        return CounterRouter(interactor: interactor, viewController: viewController)
    }
}
② CounterInteractor.swift

ビジネスロジックと状態管理を担当する「頭脳」。

  • 主要APIと役割
    • Interactor: RIBsのライフサイクル(didBecomeActiveなど)を持ちます
    • presenter: Viewを操作するためのインターフェース
    • listener: 親RIBと通信するためのインターフェース

import RIBs
import RxSwift // (状態管理によく使われる)

// ... (プロトコルの定義) ...

final class CounterInteractor: PresenterableInteractor<CounterPresentable>, CounterInteractable, CounterListener {
    weak var router: CounterRouting?
    weak var listener: CounterListener?

    private var count = 0

    override init(presenter: CounterPresentable) {
        super.init(presenter: presenter)
        presenter.listener = self
    }
    
    // Viewからのイベントを処理するメソッド
    func increment() {
        count += 1
        // Viewに状態の更新を指示
        presenter.updateCount(to: count)
    }
}
③ CounterViewController.swift

UIの表示とユーザー入力の受付のみを担当する「見た目」。

  • 主要APIと役割
    • listener: Interactorと通信するためのインターフェース。
    • updateCount(to:): Interactorから指示を受けてUIを更新するメソッド。

import RIBs
import UIKit

// ... (プロトコルの定義) ...

final class CounterViewController: UIViewController, CounterPresentable, CounterViewControllable {
    weak var listener: CounterPresentableListener?

    private let countLabel = UILabel()
    private let incrementButton = UIButton()

    // ... (viewDidLoadでUIをセットアップ) ...
    
    @objc private func didTapIncrementButton() {
        // ユーザー操作をInteractorに通知
        listener?.increment()
    }

    // Interactorからの指示でUIを更新
    func updateCount(to count: Int) {
        countLabel.text = "Count: \(count)"
    }
}
④ CounterRouter.swift

画面遷移を管理する「ナビゲーター」。この例では子RIBがないため、シンプルな実装になります。


import RIBs

// ... (プロトコルの定義) ...

final class CounterRouter: ViewableRouter<CounterInteractable, CounterViewControllable>, CounterRouting {
    override init(interactor: CounterInteractable, viewController: CounterViewControllable) {
        super.init(interactor: interactor, viewController: viewController)
        interactor.router = self
    }
}

4. 特徴と他のアーキテクチャとの比較

  • 長所:
    • 超スケーラブル: 機能ごとに完全に独立したRIBを開発できるため、大規模・多人数開発に非常に強い。
    • 高いテスト容易性: ビジネスロジック(Interactor)がUIに全く依存しないため、単体テストが書きやすい。
    • 明確な責務分離: 各コンポーネントの役割が厳格に決まっているため、コードの可読性とメンテナンス性が高い。
  • 短所:
    • 学習コストが非常に高い: 登場人物が多く、DIや依存関係のツリーなど、理解すべき概念が多い。
    • ボイラープレートが多い: 一つの機能を実装するために、多数のプロトコルやクラスファイルを作成する必要がある。
    • 小規模アプリには過剰設計: 明らかにオーバースペックであり、開発速度を著しく低下させる可能性がある。
  • MVVM/TCAとの違い:
    • vs. MVVM: RIBsはMVVMのViewModelが持つ責務を、Interactor(ビジネスロジック)とPresenter(表示ロジック)に分割し、さらにRouter(画面遷移)とBuilder(DI)をフレームワークとして強制する点で、より責務分離が厳格です。
    • vs. TCA: TCAが関数型プログラミングの思想(状態、アクション、リデューサー)に基づいているのに対し、RIBsはクラスとプロトコルを多用するオブジェクト指向の思想に基づいています。TCAが状態中心であるのに対し、RIBsは責務中心のアーキテクチャと言えます。

Discussion