Factory 패턴

팩토리 메서드 패턴(Factory method pattern)은 객체지향 디자인 패턴이다. Factory method는 부모(상위) 클래스에 알려지지 않은 구체 클래스를 생성하는 패턴이며. 자식(하위) 클래스가 어떤 객체를 생성할지를 결정하도록 하는 패턴이기도 하다.

장점

싱글톤 패턴 : static을 사용한 효과로 객체를 생성하지 않고도 패턴을 사용가능.

예제

피자 가게에서 피자를 주문하고 그 가격을 출력해주는 예제

객체 생성 로직을 어디서 하는가 ?

Factory 패턴을 사용하지 않은 예제

void main(){
	var userSelectedPizza = PizzaType.HamMushroom;
	Pizza pizza;
	switch (userSelectedPizza){
		case PizzaType.HamMushroom;
			pizza = HamAndMushroomPizza();
			break;
		case PizzaType.Deluxe;
			pizza = DeluxePizza();
			break;
		case PizzaType.Seafood;
			pizza = SeafoodPizza();
			break;
	}
	print(pizza.getPrice());
}
// 피자 종류
enum PizzaType { HamMushroom, Deluxe, Seafood }

abstract class Pizza{
	double getPrice();
}

class HamAndMushroomPizza implements Pizza{
	double price = 10.5;
	@override
	double getPrice(){
		return price;
	}
}

class DeluxePizza implements Pizza{
	double price = 5.5;
	@override
	double getPrice(){
		return price;
	}
}

class SeafoodPizza implements Pizza{
	double price = 7.8;
	@override
	double getPrice(){
		return price;
	}
}

Factory 패턴을 사용한 예제

void main(){
	var userSelectedPizza = PizzaType.HamMushroom;
	print(Pizza.pizzaFactory(userSelectedPizza).getPrice());
}
// 피자 종류
enum PizzaType { HamMushroom, Deluxe, Seafood }

abstract class Pizza{
	double getPrice();
	// 팩토리 패턴
	static pizzaFactory(PizzaType type){
		switch (userSelectedPizza){
		case PizzaType.HamMushroom;
			return HamAndMushroomPizza();
			break;
		case PizzaType.Deluxe;
			return DeluxePizza();
			break;
		case PizzaType.Seafood;
			return SeafoodPizza();
			break;
		}
	}
}

class HamAndMushroomPizza implements Pizza{
	double price = 10.5;
	@override
	double getPrice(){
		return price;
	}
}

class DeluxePizza implements Pizza{
	double price = 5.5;
	@override
	double getPrice(){
		return price;
	}
}

class SeafoodPizza implements Pizza{
	double price = 7.8;
	@override
	double getPrice(){
		return price;
	}
}

로직 변경

DeluxePizza 객체 생성할때 orderNumber가 필요하다고 수정

class DeluxePizza implements Pizza{
	double price = 5.5;
	String orderNumber;
	DeluxePizza(this.orderNumber);
	@override
	double getPrice(){
		return price;
	}
}

Factory 패턴을 사용하지 않은 예제

void main(){
	var userSelectedPizza = PizzaType.HamMushroom;
	Pizza pizza;
	var orderNumber = '1234';
	switch (userSelectedPizza){
		case PizzaType.HamMushroom;
			pizza = HamAndMushroomPizza();
			break;
		case PizzaType.Deluxe;
			pizza = DeluxePizza(orderNumber);
			break;
		case PizzaType.Seafood;
			pizza = SeafoodPizza();
			break;
	}
	print(pizza.getPrice());
}

Factory 패턴을 사용한 예제

void main(){
	var userSelectedPizza = PizzaType.HamMushroom;
	String orderNumber = '1234';
	print(Pizza.pizzaFactory(userSelectedPizza,orderNumber).getPrice());
}
// 피자 종류
enum PizzaType { HamMushroom, Deluxe, Seafood }

abstract class Pizza{
	double getPrice();
	// 팩토리 패턴
	static pizzaFactory(PizzaType type, String orderNumber){
		switch (userSelectedPizza){
		case PizzaType.HamMushroom;
			return HamAndMushroomPizza();
			break;
		case PizzaType.Deluxe;
			return DeluxePizza(orderNumber);
			break;
		case PizzaType.Seafood;
			return SeafoodPizza();
			break;
		}
	}
}

Factory 패턴을 사용한 예제에서 Map 타입으로 데이터 넘겨주기

void main(){
	var userSelectedPizza = PizzaType.HamMushroom;
	String orderNumber = '1234';
	Map<String, dynamic> json = {
		'type': PizzaType.HamMushroom,
		'orderNumber' : '1234',
	}
	print(Pizza.pizzaFactory(json).getPrice());
}
// 피자 종류
enum PizzaType { HamMushroom, Deluxe, Seafood }

abstract class Pizza{
	double getPrice();
	// 팩토리 패턴
	static pizzaFactory(Map<String, dynamic> json){
		switch (json['type'] as PizzaType){
		case PizzaType.HamMushroom;
			return HamAndMushroomPizza();
			break;
		case PizzaType.Deluxe;
			return DeluxePizza(json['orderNumber'] as String);
			break;
		case PizzaType.Seafood;
			return SeafoodPizza();
			break;
		}
	}
}

Factory 패턴을 사용한 예제에서 Map 타입으로 데이터 넘겨줄때 Factory 타입으로 리턴

void main(){
	var userSelectedPizza = PizzaType.HamMushroom;
	String orderNumber = '1234';
	Map<String, dynamic> json = {
		'type': PizzaType.HamMushroom,
		'orderNumber' : '1234',
	}
	print(Pizza.fromJson(json).getPrice());
}
// 피자 종류
enum PizzaType { HamMushroom, Deluxe, Seafood }

abstract class Pizza{
	double getPrice();
	// 팩토리 패턴
	Factory Pizza.fromJson(Map<String, dynamic> json){
		switch (json['type'] as PizzaType){
		case PizzaType.HamMushroom;
			return HamAndMushroomPizza();
			break;
		case PizzaType.Deluxe;
			return DeluxePizza(json['orderNumber'] as String);
			break;
		case PizzaType.Seafood;
			return SeafoodPizza();
			break;
		default:
			return SeafoodPizza();
		}
	}
}

참고

https://www.youtube.com/watch?v=ZikfiBnFMk8 

 

728x90

'Study > Flutter' 카테고리의 다른 글

[Flutter] Provider pattern (bloc -> provider )  (0) 2021.06.16
[Flutter] Stateful widget  (0) 2021.06.14
[UI초급] Container, materialApp, Scaffold  (0) 2021.06.07

2021.06.16 - [Usage/Flutter] - [Flutter] Bloc Pattern (stateful -> bloc -> skip event bloc )

 

[Flutter] Bloc Pattern (stateful -> bloc -> skip event bloc )

BLOC Bussiness Logic Component 비즈니스 로직과 ui를 구분 짓고 필요에 따라 원하는 부분만 데이터 처리 실 사용 예제 버튼 클릭시 증감 1. Stateful version ui widget - 버튼 클릭시 증감 //stateless widge..

mugon-devlog.tistory.com

Install package

https://pub.dev/packages/provider

Project 구조

main.dart
src
 - count_home.dart 
 - count_provider.dart
 - count_home_widget.dart // statelesswidget

main.dart

void main() {
  runApp(MyApp());
    // runApp에서 provider를 불러오면 MyApp에서 접근 가능
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MultiProvider(
        providers: [
          ChangeNotifierProvider(
              create: (BuildContext context) => CounterProvider())
        ],
                // child의 모든 하위 요소들은 CounterProvider에 접근 가능
        child: Home(),
      ),
      // home: ChangeNotifierProvider(
      //   create: (BuildContext context) => CounterProvider(),
      //   child: Home(),
      // ),
    );
  }

count_provider.dart

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

// ChangeNotifier 상속
// bloc 에서 stream 처리, sink 주입 했던 것을 처리해줌
class CounterProvider extends ChangeNotifier {
    // private
    // 이곳에서 선언되는 모든 변수는 상태관리 대상
  int _count = 0;
    // get 함수를 사용해서 private한 변수에 접근 가능하도록 만듦
  int get count => _count;
  add() {
    _count++;
        // 상태값이 update 되었다는 신호를 보내줌
    notifyListeners();
  }

  subtract() {
    _count--;
    notifyListeners();
  }
}

count_home.dart

class Home extends StatelessWidget {
  const Home({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
        // Provider.of로 사용할 provider를 가져옴
        // listen을 false로 해주면 현재의 widget은 rebuilding이 안됨
    CounterProvider countProvider =
        Provider.of<CounterProvider>(context, listen: false);
    return Scaffold(
      appBar: AppBar(
        title: Text("Provider"),
      ),
      body: CountView(),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () => countProvider.add(),
          ),
          IconButton(
            icon: Icon(Icons.remove),
            onPressed: () => countProvider.subtract(),
          ),
        ],
      ),
    );
  }

count_home_widget.dart

class CountView extends StatelessWidget {
  const CountView({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
                // Provider.of를 통해 접근해서 count값 가져옴
                // build 전체를 rebuilding 함
        Provider.of<CounterProvider>(context).count.toString(),
      ),
    );
  }
}

// 특정 위젯만 rebuilding
class CountView extends StatelessWidget {
  const CountView({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
     return Center(
            // consumer를 사용해서 특정 위젯만 rebuilding
       child: Consumer<CounterProvider>(
                builder: (context, provider, child) {
         return Text(
           provider.count.toString(),
         );
       }),
    );
  }
}

참고

https://www.youtube.com/watch?v=AmmjdvhQG1s 

 

728x90

'Study > Flutter' 카테고리의 다른 글

[Flutter] Factory 패턴  (0) 2021.07.13
[Flutter] Stateful widget  (0) 2021.06.14
[UI초급] Container, materialApp, Scaffold  (0) 2021.06.07

Rendering 조건

stateful widget에서 rebuild를 초래하는 것은 state 클래스이며 이를 통해 스크린을 랜더링함

  • child 위젯의 생성자를 통해서 새로운 데이터가 전달될때
  • internal state가 바뀔때

왜 StatefulWidget 클래스는 두개로 이루어져 있을까?

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

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Charactor card',
      home: MyPage(),
    );
  }
}

먼저, StatefulWidget클래스는 아래와 같이 widget 클래스를 상속하고 있습니다.

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key? key }) : super(key: key);
  @override
  StatefulElement createElement() => StatefulElement(this);
  @protected
  @factory
  State createState(); // ignore: no_logic_in_create_state, this is the original sin
}

widget클래스는 기본적으로 immutable 즉, 한번 생성되면 state가 변하지 않습니다.

그렇기에 StatefulWidget은 StatelessWiget처럼 immutable한 위젯입니다.

그러나 반드시 statefulwidget은 state의 변화를 반영해야 합니다.

이런 문제를 해결하기 위해 두개의 클래스로 나눠 StatefulWidget인 MyApp 위젯은 immutable한 특징을 유지하고 _MyAppState 클래스는 mutable한 속성을 대신하게 만든 것입니다.

어떻게 두개의 클래스를 연결시킬 수 있을까 - 1

먼저, state 클래스의 코드입니다.

@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
  ...
}

State클래스는 제네릭 타입으로 어떤 타입이 오든지 StatefulWidget을 상속받게 하고 있습니다.

_MyAppState 클래스는 state클래스를 상속 받았고 이제, _MyAppState 클래스는 state 타입이 되었습니다.

class _MyAppState extends State<MyApp>{...}

그리고 상속 받은 state클래스의 제네릭 타입을 MyApp 클래스로 지정해준다면 이 state 클래스는 오직 MyApp타입만을 가질 수 있게 됩니다.

이를 통해 _MyAppState 클래스가 StatefulWidget인 MyApp 위젯에 연결된다고 flutter에 알려줄수 있습니다.

왜 state 클래스는 제네릭 타입을 가지게 되었을까

제네릭 타입의 장점인 코드의 재사용성, 안정성 때문입니다.

check box

Checkbox(value: false, onChanged: (value){})

check box source code

// StatefulWidget을 상속 받고 있음
class Checkbox extends StatefulWidget {
    const Checkbox({...})
    ...
}
// 상속받은 state의 제네릭 타입은 checkbox 타입으로 되어있음
class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, ToggleableStateMixin {...}

즉, state클래스를 제네릭 타입으로 만들어서 필요한 타입을 편하게 지정하고 그 외의 타입을 전달 받지 못하도록 안전장치를 해둔 것

어떻게 두개의 클래스를 연결시킬 수 있을까 - 2

이제, _MyAppState 클래스를 createState Method를 통해 MyApp 위젯 내에서 불러와야합니다.

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

  @override
  State<StatefulWidget> createState() {
    return _MyAppState();
  }
}
class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Charactor card',
      home: MyPage(),
    );
  }
}

createState Method는 state 타입으로 지정되어 있고 제네릭 타입으로 StatefulWidget이 지정되어 있습니다.

즉, createState Method는 반드시 state 타입의 객체를 리턴해야 하는데 결국, StatefulWidget타입만이 올 수있는 객체여야 하며 이 객체는 _MyAppState 클래스에 근거하여 만들어진 것입니다.

StatefulWidget과 StatelessWidget 차이

createState Method는 StatefulWidget이 생성될 때 마다 호출되는 메서드입니다.

StatelessWidget은 build method에서 생성한 객체를 바로 반환하지만 StatefulWidget은 이 createState Method에서 생성한 객체를 반환합니다.

이제 어떻게 State를 변화시켜 랜더링 시킬까

state를 변화시킬려면 반드시 build 메서드를 호출해서 위젯을 rebuild하는 방법 뿐입니다.

단순히 버튼을 누른다고 build메서드를 호출할 수는 없습니다.

TextButton(
  onPressed: () {
      counter++;
  },
  child: Icon(Icons.add),
)

그래서, flutter는 우리 대신 build 메서드를 호출해 줄 수 있는 setState 메서드를 가지고 있습니다.

setState의 역할은 두가지 입니다.

  • 매개 변수로 전달된 함수 호출
  • build 메서드 호출
TextButton(
  onPressed: () {
    setState((){
      counter++;
    })
  },
  child: Icon(Icons.add),
)

정리

flutter는 widget tree를 기반으로 element tree를 생성합니다.

MyApp Stateful widget을 만나면 관련된 MyApp Stateful element를 추가하지만 createState 메서드를 호출해서 MyApp Stateful element와 연결된 MyAppState 객체도 생성합니다.

그런데 이 객체는 MyApp Stateful widget과 간접적으로만 연결되어 있습니다.

여기서 특이점은 MyApp Stateful element는 위젯 관련 중요 정보들을 가지고 있지만, 메모리 상에서 어디에도 종속되지 않는 독립된 객체로서 MyAppState 객체에 대한 정보도 가지게 되는 것입니다.

이제 setState메서드가 호출되고 build 메서드로 인해서 state객체가 rebuild 되면서 새로운 state를 반영한 새로운 MyApp Stateful widget이 rebuild 됩니다. 그러면 MyApp Stateful element에 연결되어 있는 MyAppState객체에 새로운 state가 저장이 되고 이제 MyAppState객체는 새롭게 rebuild된 MyAppStateful widget을 가리키게 됩니다.

왜 MyAppState 객체는 MyAppStateful widget처럼 widget tree상에서 매번 rebuild되지 않는 것일까?

비용문제

state가 변한 state 객체를 비용이 싼 Stateful widget으로 만들어서 계속 rebuild하고 MyAppState 객체는 element tree에서 mutable하게 존재하면서 필요할때마다 새로운 state를 저장함과 동시에 새롭게 rebuild된 Stateful Widget과의 링크만을 업데이트 해줍니다.

참고

https://www.youtube.com/watch?v=OvWrOKMqSG0&list=PLQt_pzi-LLfoOpp3b-pnnLXgYpiFEftLB&index=2 

 

728x90

'Study > Flutter' 카테고리의 다른 글

[Flutter] Factory 패턴  (0) 2021.07.13
[Flutter] Provider pattern (bloc -> provider )  (0) 2021.06.16
[UI초급] Container, materialApp, Scaffold  (0) 2021.06.07

Container

html의 div 역할

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

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.amber, // 배경색 변경
    );
  }
}

MaterialApp

Container의 한종류로서 안드로이드 친화적인 디자인으로 개발한다는 것을 알려줌 이와 대비로 ios 친화적인 cupertinoapp이 있음

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp();
  }
}

Scaffold

기본 뼈대를 가지고 있음 - Appbar, bottomNavigationBar ...

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp( // Android app 만들꺼에요
      home: Scaffold( // 기본 구조를 들고 있어요
        body: Text("hello"),
      ),
    );
  }
}

위의 경우 안드로이드 상단 바 영역을 제대로 표현 못하기에 안전한 영역에만 그리도록 해줄 필요가 있음 -> SafeArea

Scaffold 끝에 커서를 두고 alt(option) + enter → wrap with widget 을 사용하면 쉽게 바꿀 수 있음

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp( // Android app 만들꺼에요
      home: SafeArea(
        child: Scaffold( // 기본 구조를 들고 있어요
          body: Text("hello"),
        ),
      ),
    );
  }
}

AppBar

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp( // Android app 만들꺼에요
      home: SafeArea(
        child: Scaffold( // 기본 구조를 들고 있어요
          appBar: AppBar( //appbar를 그려줌
            backgroundColor: Colors.blue,
          ),
          body: Text("hello"),
        ),
      ),
    );
  }
}

appBar: AppBar(
  backgroundColor: Colors.blue,
  title: Text("First app"),
  leading: Icon(Icons.menu), //appbar의 햄버거 버튼추가
),

FloatingActionButton

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp( // Android app 만들꺼에요
      home: SafeArea(
        child: Scaffold( // 기본 구조를 들고 있어요
          appBar: AppBar(
            backgroundColor: Colors.blue,
            title: Text("First app"),
          ),
          body: Text("hello"),
          floatingActionButton: FloatingActionButton( //하단 floating 버튼
            onPressed: () {  },
            child: Text("button"),
          ),
        ),
      ),
    );
  }
}

BottomNavigationBar

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp( // Android app 만들꺼에요
      home: SafeArea(
        child: Scaffold( // 기본 구조를 들고 있어요
          appBar: AppBar(
            backgroundColor: Colors.blue,
            title: Text("First app"),
          ),
          body: Text("hello"),
          floatingActionButton: FloatingActionButton(
            onPressed: () {  },
            child: Text("button"),
          ),
          bottomNavigationBar: BottomNavigationBar( // 하단 네비게이션바
            backgroundColor: Colors.yellow, 
            items: [ // 네비게이션 바에 들어갈 항목
              BottomNavigationBarItem(
                  label:"hello",
                  icon: Icon(
                    Icons.access_alarm_rounded,
                  )),
              BottomNavigationBarItem(
                  label:"hello",
                  icon: Icon(
                    Icons.access_alarm_rounded,
                  )),
            ],
          ),
        ),
      ),
    );
  }
}
728x90

'Study > Flutter' 카테고리의 다른 글

[Flutter] Factory 패턴  (0) 2021.07.13
[Flutter] Provider pattern (bloc -> provider )  (0) 2021.06.16
[Flutter] Stateful widget  (0) 2021.06.14

+ Recent posts