본문 바로가기

Flutter/03 Design Pattern

[Flutter] Design Pattern - MVC, MVVM

이번에는 MVC 와 MVVM 에 대해서 알아보겠습니다. 기본 개념을 아신다는 전제하에 MVC -> MVVM 으로 변경하는 장점을 중심으로 알아보겠습니다.

개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 3.0.1

 

 

화면은 아래와 같습니다. +버튼은 숫자를 증가시키고, -버튼은 숫자를 감소 시킨다.

 

 

 

패턴을 적용하지않는 일반적인 코드 - flutter 에서 신규 프로젝트 샘플과 유사한 구조,

변수/함수/View 모두 하나의 파일에서 구현하여 간단한 화면 같은 경우, 쉽게 이해할 수 있으나 화면 및 데이터가 복잡해 경우 적합하지 않다.

 

import 'package:flutter/material.dart';

class NoPatternView extends StatefulWidget {
  const NoPatternView({Key? key}) : super(key: key);

  @override
  State<NoPatternView> createState() => _NoPatternViewState();
}

class _NoPatternViewState extends State<NoPatternView> {
  int count = 0;

  void update() => setState(() {});

  void incrementCounter() {
    count++;
    update();
  }

  void decrementCounter() {
    count--;
    update();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('NO 패턴')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(count.toString(), style: const TextStyle(fontSize: 150)),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                      onPressed: incrementCounter,
                      child: const Text(
                        '+',
                        style: TextStyle(fontSize: 30),
                      )),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                      onPressed: decrementCounter,
                      child: const Text(
                        '-',
                        style: TextStyle(fontSize: 30),
                      )),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

 

 

 

MVC(Model View Controller) - View 와 Model 이 Controller 를 통해서 연결됨(Model 의 간단한 변경에도 모든 View 를 수정해야 함). 간단한 구조는 아래와 같습니다.

Model - 데이터

View -  화면

Controller - 비지니스 로직

 

 

 

예를 들어서 model.dart 에서 counter 변수명만 변경해도

  // counter 의 이름만 변경해도, View 에서는 여기서 수정을 해야함.
    Text(con.model.counter.toString(), style: const TextStyle(fontSize: 150)),

 

// model.dart
class Model {
  int _count = 0;

  // counter 의 이름을 변경하면 *****************************************
  int get counter => _count;

  int incrementCounter() => _count++;

  int decrementCounter() => _count--;
}


// mvc_controller.dart
import 'package:mvc_pattern/mvc_pattern.dart';

import '../model/model.dart';

class MVCController extends ControllerMVC {
  factory MVCController([StateMVC? state]) => _this ??= MVCController._(state);

  MVCController._(StateMVC? state)
      : model = Model(),
        super(state);

  static MVCController? _this;
  final Model model;

  void update() => setState(() {});

  void incrementCounter() {
    model.incrementCounter();
    update();
  }

  void decrementCounter() {
    model.decrementCounter();
    update();
  }
}


// mvc_view.dart
import 'package:flutter/material.dart';
import 'package:mvc_pattern/mvc_pattern.dart';

import 'mvc_controller.dart';

class MVCView extends StatefulWidget {
  const MVCView({Key? key}) : super(key: key);

  @override
  State createState() => _MVCViewState();
}

class _MVCViewState extends StateMVC<MVCView> {
  _MVCViewState() : super(MVCController()) {
    con = controller as MVCController;
  }

  late MVCController con;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('MVC 패턴')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
          // counter 의 이름을 변경하면, 여기서 수정을 해야함. ******************************
            Text(con.model.counter.toString(), style: const TextStyle(fontSize: 150)),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                      onPressed: () {
                        con.incrementCounter();
                      },
                      child: const Text('+', style: TextStyle(fontSize: 30))),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                      onPressed: () {
                        con.decrementCounter();
                      },
                      child: const Text('-', style: TextStyle(fontSize: 30))),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

 

 

 

MVVM(Model View ViewModel) - View 와 Model 간의 연결성을 제거하여 독립성을 유지할 수 있다.(Model 의 간단한 변경에 대해 ViewModel 만 수정하만 됨, View 에 대한 수정은 필요없음). 물론 Model 에서 많은 변경이 발생한다면 View 에 대한 수정이 발생할 수 있다. 간단한 구조는 아래와 같습니다.

Model - 데이터 구조

View -  화면

ViewModel - 비지니스 로직 및 화면과 모델의 데이터 연결

 

 

 

예를 들어서 model.dart 에서 counter 변수명 변경시

  // ViewModel 에서는 여기서만 수정하면 됨.
    int get count => _model.counter;

 

// model.dart 는 상기와 동일하여 생략



// mvvm_viewmodel.dart
import 'dart:async';

import '../model/model.dart';

class MvvmViewModel {
  late Model _model;
  StreamController<Model> controller = StreamController<Model>();
  Stream<Model> get mvvmStream => controller.stream;

  MvvmViewModel() {
    _model = Model();
  }

  int get count => _model.counter; // 여기만 수정 **************************************

  void update() {
    controller.sink.add(_model);
  }

  void incrementCounter() {
    _model.incrementCounter();
    update();
  }

  void decrementCounter() {
    _model.decrementCounter();
    update();
  }
}


// mvvm_view.dart
import 'package:flutter/material.dart';

import 'mvvm_viewmodel.dart';

class MVVMView extends StatelessWidget {
  MVVMView({Key? key}) : super(key: key);

  final MvvmViewModel viewModel = MvvmViewModel();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('MVVM 패턴')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            StreamBuilder(
                stream: viewModel.mvvmStream,
                builder: ((context, snapshot) {
                  return Text(viewModel.count.toString(), style: const TextStyle(fontSize: 150));
                })),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                      onPressed: () {
                        viewModel.incrementCounter();
                      },
                      child: const Text('+', style: TextStyle(fontSize: 30))),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                      onPressed: () {
                        viewModel.decrementCounter();
                      },
                      child: const Text('-', style: TextStyle(fontSize: 30))),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

 

 

아주 간단한 예시라서 크게 공감이 없을수도 있지만, View 가 많아지고, Model 이 복잡해진다면 MVVM 패턴의 장점을 알수 있다.

 

 

 

 

 

 

참고자료: 개발하는 남자

https://www.youtube.com/watch?v=UJQRiypV6UA&t=27s