소프트웨어 디자인 패턴 - 6. Flux 패턴

2025. 1. 20. 12:43소프트웨어/디자인 패턴

Flux 패턴이란?

Flux 패턴은 단방향 데이터 흐름을 기반으로 애플리케이션 상태를 관리하는 소프트웨어 디자인 아키텍처입니다. React와 함께 사용되는 Redux의 기반이 되는 패턴으로, 상태 관리와 데이터 흐름을 단순하고 예측 가능하게 만듭니다. Flux는 복잡한 애플리케이션에서도 데이터 흐름을 명확히 설계할 수 있도록 돕습니다.


구성 요소

1. Store

  • 애플리케이션 상태와 비즈니스 로직을 관리합니다.
  • 상태를 읽고 변경하는 유일한 소스로 작동하며, View에 상태를 제공합니다.

2. Action

  • 사용자 또는 시스템 이벤트를 나타냅니다.
  • 상태 변경 요청을 Store로 전달하기 위한 명령 역할을 합니다.

3. Dispatcher

  • Action을 Store로 전달하는 역할을 합니다.
  • 중앙에서 모든 Action을 관리하며, Store가 적절히 상태를 업데이트하도록 합니다.

4. View

  • 현재 상태를 렌더링하고 사용자와 상호작용을 처리합니다.
  • 사용자 입력을 Action으로 변환하여 Dispatcher에 전달합니다.

Flux의 특징

  1. 단방향 데이터 흐름:
    • Action -> Dispatcher -> Store -> View 순서로 데이터가 흐릅니다.
    • 데이터 흐름이 단순하고 예측 가능합니다.
  2. 중앙 집중식 상태 관리:
    • Store를 통해 모든 상태를 관리하여 데이터 흐름을 단순화하고 안정성을 제공합니다.
  3. 구성 요소 간의 명확한 역할 분리:
    • 각각의 구성 요소가 독립적으로 동작하며, 각자의 책임이 명확합니다.

장점

  1. 예측 가능한 상태 관리:
    • 단방향 데이터 흐름으로 상태 변경이 명확하고 디버깅이 쉽습니다.
  2. 유지보수성과 확장성:
    • 데이터 흐름과 상태 관리가 체계적으로 구성되어 변경이 다른 부분에 영향을 덜 미칩니다.
  3. 테스트 용이성:
    • Action, Store, View 등을 독립적으로 테스트할 수 있습니다.

단점

  1. 초기 학습 곡선:
    • Flux와 단방향 데이터 흐름의 개념을 이해하는 데 시간이 걸릴 수 있습니다.
  2. 구현 복잡성 증가:
    • 간단한 애플리케이션에서는 Flux 구조가 불필요하게 복잡해질 수 있습니다.

적용 예시

1. Todo 리스트 애플리케이션

  • Store: Todo 항목의 목록을 저장.
  • Action: Todo 항목 추가, 삭제, 완료 상태 변경.
  • Dispatcher: Action을 Store로 전달.
  • View: Todo 항목을 화면에 표시하고 사용자 입력을 처리.

2. 쇼핑 카트 애플리케이션

  • Store: 장바구니 항목과 총합 정보를 관리.
  • Action: 상품 추가, 삭제, 수량 변경.
  • Dispatcher: Action을 Store로 전달하여 상태 업데이트.
  • View: 장바구니 항목과 총합을 화면에 표시하고 사용자 입력을 처리.

3. 채팅 애플리케이션

  • Store: 채팅 메시지와 사용자 정보를 저장.
  • Action: 메시지 전송, 읽음 상태 변경.
  • Dispatcher: Action을 통해 메시지를 Store로 전달.
  • View: 채팅 메시지를 렌더링하고 사용자 입력을 처리.

C++ 예제 코드: 간단한 Todo 리스트 애플리케이션

#include <iostream>
#include <vector>
#include <string>
#include <functional>
#include <algorithm>
#include <limits>
using namespace std;

// Action: 사용자 또는 시스템 이벤트 정의
struct Action {
    string type;
    string payload;
};

// Store: 애플리케이션 상태와 비즈니스 로직 관리
class Store {
private:
    vector<string> todos;
    function<void()> onChange;

public:
    void dispatch(const Action& action) {
        if (action.type == "ADD_TODO") {
            todos.push_back(action.payload);
        } else if (action.type == "REMOVE_TODO") {
            auto it = find(todos.begin(), todos.end(), action.payload);
            if (it != todos.end()) {
                todos.erase(it);
            } else {
                cout << "Todo not found!" << endl;
            }
        }
        if (onChange) onChange();
    }

    vector<string> getTodos() const {
        return todos;
    }

    void subscribe(function<void()> callback) {
        onChange = callback;
    }
};

// Dispatcher: Action을 Store로 전달
class Dispatcher {
private:
    Store& store;

public:
    Dispatcher(Store& store) : store(store) {}

    void dispatch(const Action& action) {
        store.dispatch(action);
    }
};

// View: 사용자 인터페이스
class View {
private:
    Store& store;
    Dispatcher& dispatcher;

    void clearScreen() {
#ifdef _WIN32
        system("cls");
#else
        system("clear");
#endif
    }

public:
    View(Store& store, Dispatcher& dispatcher) : store(store), dispatcher(dispatcher) {
        store.subscribe([this]() { render(); });
    }

    void render() {
        clearScreen();
        cout << "Todo List:" << endl;
        auto todos = store.getTodos();
        for (size_t i = 0; i < todos.size(); ++i) {
            cout << i + 1 << ". " << todos[i] << endl;
        }
        cout << endl;
    }

    void showMenu() {
        while (true) {
            cout << "1. Add Todo" << endl;
            cout << "2. Remove Todo" << endl;
            cout << "3. Exit" << endl;
            cout << "Choose an option: ";

            int choice;
            if (!(cin >> choice)) {
                cin.clear();
                cin.ignore(numeric_limits<streamsize>::max(), '\n');
                cout << "Invalid input. Please enter a number." << endl;
                continue;
            }
            cin.ignore(numeric_limits<streamsize>::max(), '\n');

            if (choice == 1) {
                cout << "Enter a new todo: ";
                string todo;
                getline(cin, todo);
                dispatcher.dispatch({"ADD_TODO", todo});
            } else if (choice == 2) {
                cout << "Enter the todo to remove: ";
                string todo;
                getline(cin, todo);
                dispatcher.dispatch({"REMOVE_TODO", todo});
            } else if (choice == 3) {
                break;
            } else {
                cout << "Invalid option. Try again." << endl;
            }
        }
    }
};

// Main: 애플리케이션 실행
int main() {
    Store store;
    Dispatcher dispatcher(store);
    View view(store, dispatcher);

    view.showMenu();

    return 0;
}

코드 설명

Store

  • 애플리케이션 상태(todos)를 관리하며, Action을 처리하여 상태를 업데이트.
  • 상태 변경 시 등록된 콜백을 실행하여 View를 갱신.

Dispatcher

  • Action을 Store로 전달하여 중앙 집중식 상태 관리를 지원.

View

  • 상태를 렌더링하고 사용자 입력을 처리하여 Action을 생성하고 Dispatcher에 전달.

실행 결과 예시

Todo List:

1. Add Todo
2. Remove Todo
3. Exit
Choose an option: 1
Enter a new todo: Buy groceries

Todo List:
1. Buy groceries

1. Add Todo
2. Remove Todo
3. Exit
Choose an option: 2
Enter the todo to remove: Buy groceries

Todo List:

1. Add Todo
2. Remove Todo
3. Exit
Choose an option: 3

Flux 패턴 요약

  1. Store: 상태와 비즈니스 로직을 관리.
  2. Action: 상태 변경 요청을 전달하는 이벤트.
  3. Dispatcher: Action을 Store로 전달하여 중앙에서 처리.
  4. View: 상태를 렌더링하고 사용자 입력을 처리.

Flux 패턴은 단방향 데이터 흐름과 중앙 집중식 상태 관리로 복잡한 애플리케이션에서도 예측 가능한 상태 관리를 제공합니다.