factory

factory는 새로운 인스턴스를 생성하고 싶지 않을 때 사용하는 생성자이다. 이와 같은 개념은 새로운 게 아니다. 소프트웨어 디자인 패턴 중 '싱글톤 패턴'을 따른 것이다.

Singleton 패턴

전역 변수를 사용하지 않고 객체를 하나만 생성 하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴

싱글톤 패턴의 사용하는 이유

메모리 측면

최초 한번의 new 연산자를 통해서 고정된 메모리 영역을 사용하기 때문에 추후 해당 객체에 접근할 때 메모리 낭비를 방지

이미 생성된 인스턴스를 활용하니 속도 측면에서도 이점

데이터 공유가 쉽다

싱글톤 인스턴스가 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들이 접근하여 사용할 수 있다

도메인 관점에서 인스턴스가 한 개만 존재하는 것을 보증하고 싶은 경우 싱글톤 패턴을 사용

factory의 특징

  • 기존에 이미 생성된 인스턴스가 있다면 return 하여 재사용한다.
  • 하나의 클래스에서 하나의 인스턴스만 생성한다(싱글톤 패턴).
  • 서브 클래스 인스턴스를 리턴할 때 사용할 수 있다.
  • Factory constructors 에서는 this 에 접근할 수 없다.

factory는 '클래스와 같은 타입의 인스턴스' 또는 '메서드를 구현하는 인스턴스'를 리턴하기만 하면 된다. 이렇게 생성된 인스턴스는기존에 생성된 인스턴스가 아니라면 새롭게 생성되고, 기존 인스턴스가 있다면 기존 것을 리턴한다.

factory 보단 warehouse 창고의 의미와 유사하다.

실사용 예

class NewsArticle {
  final String title, description, urlToImage, url;

  NewsArticle({this.title, this.description, this.urlToImage, this.url});

  factory NewsArticle.fromJSON(Map<String, dynamic> json) {
    return NewsArticle(
      title: json["title"],
      description: json["description"],
      urlToImage: json["urlToImage"],
      url: json["url"],
    );
  }
}
class AccountModel {
  AccountModel({
    this.avatar,
    this.id,
    this.iso6391,
    this.iso31661,
    this.name,
    this.includeAdult,
    this.username,
  });

  Avatar? avatar;
  int? id;
  String? iso6391;
  String? iso31661;
  String? name;
  bool? includeAdult;
  String? username;

  factory AccountModel.fromJson(Map<String, dynamic> json) => AccountModel(
        avatar: Avatar.fromJson(json["avatar"]),
        id: json["id"],
        iso6391: json["iso_639_1"],
        iso31661: json["iso_3166_1"],
        name: json["name"],
        includeAdult: json["include_adult"],
        username: json["username"],
      );

  Map<String, dynamic> toJson() => {
        "avatar": avatar!.toJson(),
        "id": id,
        "iso_639_1": iso6391,
        "iso_3166_1": iso31661,
        "name": name,
        "include_adult": includeAdult,
        "username": username,
      };
}
class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}
728x90

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

[Dart] future, async, await, 실행순서  (0) 2021.06.15
[Dart] const 와 final 이해하기  (0) 2021.06.15
[Dart] Future, Isolate  (0) 2021.06.08
[Dart] 상속을 쓰는 이유  (0) 2021.06.07
[Dart] 상속  (0) 2021.06.07

Thread

프로세스 내에서 실행되는 흐름의 단위

Event loop

Dart는 Single Thread로 운영됨 → Event loop를 통해 복잡한 작업들을 효율적으로 처리

Flutter 앱을 실행시키는 순간 isolate라고 불리는 새로운 thread process가 하나 생성됨

이 thread가 앱 전체를 총괄하는 단일 thread가 됨

thread가 생성되는 순간 dart는 자동적으로 3가지 작업을 하게됨

  1. 내부적으로 First In First Out(FIFO)방식으로 MicroTask 와 Event 준비 MicroTask : event queue로 넘어가기 전에 아주 짧은 시간 동안 비동기적으로 먼저 실행되고 끝나는 작은 작업
  2. 앱의 시작점인 main 함수 실행
  3. Event loop 실행

Dart는 하나의 단일 thread가 실행되는 순간 내부적으로 event loop process를 통해 순서대로 대기열에 있는 micro task와 event를 처리

더 이상 내부적으로 진행할 micro task가 없다면 event loop는 외적으로 전달되는 각종 이벤트 (gesture, drawing, reading files, fetching data, button tap, future, stream 등)를 순서대로 처리하게 됨

각종 외적인 이벤트들이 이벤트 큐에 등록되고 이벤트 루프를 통해 처리

Future

비동기 방식으로 미래의 어느시점에 완성되어서 실제적인 데이터가 되거나 에러를 반환하는 객체

코드상에서 새로운 future를 객체화 시키면

  1. 다트에 의해서 future 객체가 내부적인 배열에 등록
  2. Future 관련해서 실행되어야하는 코드들이 이벤트 큐(대기열)에 등록
  3. 불완전한 future 객체가 반환
  4. Synchronous 방식으로 실행되어야 할 코드 먼저 실행
  5. 최종적으로 실제적인 data 값이 future 객체로 전달

Async method

async 키워드를 사용한 순간

  1. 메서드를 통해서 나오는 결과물들은 future라는 것을 알게 됨
  2. await 키워드를 만날때까지 synchronous 방식으로 코드 처리
  3. await 키워드를 만나면 future가 완료될 때까지 대기
  4. future가 완료 되자마자 그 다음 코드들을 실행

전체 앱의 실행이 중지되지 않고 future 부분은 건너띄고 그 아래에 있는 코드들을 synchronous하게 처리

예시

String createOrderMessage(){
    var order = fetchUserOrder();
    return 'Your order is: $order';
}

Future<String> fetchUserOrder(){
    return Future.delayed(
        Duration(second: 2), ()=>'Large Latte',
    );
}

void main(){
    print('Fetching user order...');
    print(createOrderMessage());
}

실행시 에러 → main()의 두번째 print에서 createOrderMessage()는 미완성의 객체를 반환하고 있기 때문

fetchUserOrder()에서 2초를 기다리고 fetchUserOrder()가 반환해야 하는데 기다리지 않고 즉시 반환하기 때문 → 이를 해결하기 위해 async 키워드 사용

createOrderMessage() 수정

  1. 비동기 방식으로 실행된 결과의 String 값을 리턴해줘야하므로 타입 변경 String → Future
  2. order 변수에 fetchUserOrder 함수의 리턴값을 할당해줄려면 즉시할당하는 synchronous 방식이 아니라 기다렸다가 값이 전달되는 비동기 방식어야함 createOrderMessage()가 비동기 방식으로 처리되어야함을 dart에게 알려줘야함 → async 키워드 추가
  3. fetchUserOrder()의 실행이 끝날때까지 기다렸다가 order 변수에 값을 할당해줘야함 → await 추가
// 1
Future<String> createOrderMessage() async{ // 2
              // 3
    var order = await fetchUserOrder();
    return 'Your order is: $order';
}

main() 수정

  1. main()에서 호출되는 createOrderMessage()가 비동기 방식으로 처리되는 함수이므로 main()도 비동기 방식으로 처리됨을 알려줘야함 → async 추가
  2. createOrderMessage()의 리턴값이 출려되려면 order변수의 값이 할당될 때까지 기다려야함 → await 추가
// 1
void main() async{
    print('Fetching user order...');
    // 2
    print(await createOrderMessage());
}

정리

async 키워드는 future 자체를 대신하는 것이 아니라 비동기 방식으로 실행되는 함수라는 것을 dart에게 알려주는 기능을 하는 것이며 await 키워드와 함께 미래의 어느 시점에 전달될 값을 기다리는 것

future는 하나의 객체로서 객체가 생성되는 순간에는 미완성으로 존재하다가 미래의 어느 시점에 데이터를 전달받은 온전한 객체가 되거나 데이터에 문제가 생긴다면 에러를 반환

심화 예제

void main() async {
  methodA(); // 1
  await methodB(); // 3
  await methodC('main'); // 9
  methodD(); // 12
}

methodA(){
  print('A'); // 2
}

methodB() async {
  print('B start'); // 4
  await methodC('B'); // 5
  print('B end'); // 8
}

methodC(String from) async {
  print('C start from $from'); // 6 'b' // 10 'main'

  Future((){
    print('C running Future from $from'); // 14 'b' // 16 'main'
  }).then((_){
    print('C end of Future from $from'); // 15 'b' // 17 'main'
  });

  print('C end from $from'); // 7 'b' // 11 'main'
}

methodD(){
  print('D'); // 13
}

//////
A
B start
C start from B
C end from B
B end
C start from main
C end from main
D
C running Future from B
C end of Future from B
C running Future from main
c runngin Future from main
///

참고

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

 

728x90

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

[Dart] factory 패턴  (0) 2021.06.24
[Dart] const 와 final 이해하기  (0) 2021.06.15
[Dart] Future, Isolate  (0) 2021.06.08
[Dart] 상속을 쓰는 이유  (0) 2021.06.07
[Dart] 상속  (0) 2021.06.07

final 과 const 변수 이해하기

변수

void main(){
    int age;
    age = 20;
}

변수 age의 역할은 20이라는 숫자가 저장된 위치를 포인트 즉, 가리키고 있는 것

20이 저장된 위치의 주소를 저장하고 있는 것이 변수

제어자

final과 const 같은 keyword를 modifier 제어자 라고 함

제어자는 클래스, 변수, 함수를 정의할때 함께 쓰여서 이것들을 사용하기 위한 옵션을 정의해주는 역할

void main(){
    final int myFinal = 30;
    const int myConst = 70;
}

접근제어자

final,const : 변수 값이 한번 초기화되면 바꿀 수 없게 하는 것

void main(){
    final int myFinal = 30;
    const int myConst = 70;

    myFinal = 20; // 에러
    myConst = 50; // 에러
}

final 변수 초기화

  1. 선언할때 초기화
void main(){
    final int myFinal = 30;
}
  1. 객체 생성시에 외부데이터를 받아 생성자를 통해 초기화
class Person {
    final int age;
    String name;

    Person(this.age, this.name);
}

void main(){
    Person p1 = new Person(21, 'Tom'); // 생성자를 통해 할당 -> 이후로 age는 변경 불가
    print(p1.age); // 21
} 

2번이 가능한 이유

final은 초기화되는 시점이 앱이 실행되는 시점이기 때문 ⇒ run time constant

response 변수는 컴파일시에 초기화 되지 않고 앱이 실행된 후 웹 상에서 데이터가 전송될 때까지 기다렸다가 그 후에 값이 저장

Const

compile-time constant는 컴파일 시에 상수가 됨

const 변수는 선언과 동시에 초기화

void main(){
    const time = DateTime.now() // 에러
}

현재 시간은 매번 호출될 때마다 그 값이 변경되기 때문에 런타임시에 값이 지정되어야하므로 const 키워드는 오류 발생

정리

  1. const 변수는 컴파일 시에 상수화
  2. final 변수는 런타임 시에 상수화
  3. Compile-time constant = Run-time constant
    컴파일 시에 상수화는 런타임에도 상수화가 유지됨을 의미
  4. final 변수는 rebuild 될 수 있음

참고

https://www.youtube.com/watch?v=akc51-j84os&list=PLQt_pzi-LLfoOpp3b-pnnLXgYpiFEftLB&index=6

 

728x90

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

[Dart] factory 패턴  (0) 2021.06.24
[Dart] future, async, await, 실행순서  (0) 2021.06.15
[Dart] Future, Isolate  (0) 2021.06.08
[Dart] 상속을 쓰는 이유  (0) 2021.06.07
[Dart] 상속  (0) 2021.06.07

Future, Isolate

다트에서 이벤트 루프에 등록되는 애들이 있음

보통은 다 바로 시작이 되는데 이벤트 루프에 등록되는 애들은 특징이 main()이 종료되어야지 실행

  1. network
  2. I/O (file 읽기 쓰기)
  3. Future(약속)

Future

import 'dart:io';

void download(){
    sleep(Duration(seconds:5));
    print('다운완료');
}

main(){
    download();
    print("메인종료");
}

----
다운완료
메인종료
----
  • 실행 순서
import 'dart:io';

void download(){
Future.delayed(Duration(seconds:5),(){
        print('이벤트 실행됨'): // 3
    });
    print('다운완료'); // 1
}

main(){
    download();
    print("메인종료"); // 2
}

----
다운완료
메인종료
이벤트 실행됨
----
  • Future return
import 'dart:io';

Future<String> download(){
// Future는 빈박스이다.
// 먼저 value는 빈박스(null)를 만들고 5초뒤 return을 받음
Future<String> value = Future.delayed(Duration(seconds:5),(){
        return '사과'; // 5초뒤에 return을 받음
    });
return value; // Futurn data는 Future로 받아야함
}

// 첫번째 방법
// await를 사용하기 때문에 async 키워드 필요
main() async {
    //빈박스를 먼저 받아오기 때문에 Future로 받아야함
    //그렇기에 기다릴 필요가 있음 -> await 사용 -> async 키워드 필요
    // Future<String> value = download();
    // 기다렸다 받기 때문에 Future로 받을 필요없음
    String value = await download();
    print(value);
}

// 두번째 방법
main(){
    download().then((value){
        // 빈박스가 채워져야 실행됨
        print(value);
    });
//메인 종료 후 download return 받음
}

Isolate

  • 동시에 실행
void download(var msg){
    Future((){
        for(int i = 0; i < 5; i++){
            sleep(Duration(seconds:1));
            print("download함수 : ${i}");
        }
    });
}

main(){
    // 독립적인 스레드
    Isolate.spawn(download,'msg');
    for(int i = 0; i < 5; i++){
        sleep(Duration(seconds:1));
        print("main함수 : ${i}");
    }
}

isolate는 다른 언어의 스레드와 똑같음

다른 점은 heap을 공유하지 않음. 독립적인 heap이 있음

int num = 10;

void change(var value){
    num = value;
    print("change : ${num}");
}

void check(var value){
    print("check : ${num}");
}

void main(){
    Isolate.spawn(change,20);
    Isolate.spawn(check,"");
}

---
check : 10
change : 20
---

어떤 이벤트가 들어왔을때 이 이벤트 때문에 오래 기다려야할 땐 Future

동시 실행 하고 싶을땐 isolate

728x90

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

[Dart] future, async, await, 실행순서  (0) 2021.06.15
[Dart] const 와 final 이해하기  (0) 2021.06.15
[Dart] 상속을 쓰는 이유  (0) 2021.06.07
[Dart] 상속  (0) 2021.06.07
[Dart] 접근지정자, 생성자, getter, setter  (0) 2021.06.04

메모리, heap, stack

위의 그림처럼

먼저, RAM 메모리에 적재 되어야 CPU에서 연산이 가능합니다.

메모리에는 Heap과 Stack 영역이 존재하는데 Dart에서 Heap은 class를 제외한 상단에서 선언한 모든 메소드 및 변수 그리고 main 클래스가 가장 먼저 적재됩니다. 

그러나 메서드의 중괄호 { } 안의 영역은 stack 영역으로써, 호출될 때 stack 영역이 만들어지며 중괄호 { }안의 메서드 및 변수가 적재되고 중괄호의 종료와 함께 없어집니다.

main 클래스 밖에서 선언된 class는 객체를 Animal a = Animal(); 와 같이 생성할때 heap영역에 class안의 변수와 메서드가 적재됩니다.

위와 같이 메서드가 호출될때 stack 영역 또한 생성됩니다.

상속 특징 1. 다형성

Dog 객체가 extends를 통해 animal을 상속 받음으로써 Animal 타입을 사용할 수 있습니다.

추가 예시로

햄버거 <=> 더블햄버거 extends 햄버거
위와 같이 패티 2장인 더블햄버거를 만들때 패티 1장인 햄버거를 상속받아 만들 경우 부모 자식 모두 햄버거 인것은 변함 없습니다.

 

다양한 이름으로 불릴 수 있어야 합니다.

상속 특징 2. 데이터가 아닌 타입을 물려줍니다.

위의 그림의 알고리즘에서 손님 Love가 추가될 때 원래 손님인 Ssar을 변경해야 하는 알고리즘의 수정이 필요합니다.

상속 특징 3. 추상화를 위해 사용합니다.

손님과 직원의 개념을 추상화 시키면서 ssar, love에게 손님의 추상화 된 개념을 주입하고, cos에게 직원의 추상화 된 개념을 주입함을 통해 알고리즘의 수정을 막고 재사용성을 증대 시킵니다.

 

참고

https://www.youtube.com/watch?v=H41XvJKJ4Tk&list=PL93mKxaRDidGEaUXprXqhNvSW02xCjLZI&index=17 

 

728x90

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

[Dart] const 와 final 이해하기  (0) 2021.06.15
[Dart] Future, Isolate  (0) 2021.06.08
[Dart] 상속  (0) 2021.06.07
[Dart] 접근지정자, 생성자, getter, setter  (0) 2021.06.04
[Dart] 클래스  (0) 2021.06.04

상속

아이스크림 가게에서 주문 중..

손님

cos.dart

//손님
import 'icecream.dart';
import 'ssar.dart';

class Cos {
    //책임 = 주문
    void order(Ssar s){
        Icecream i = s.makeIcecream();
        print("아이스크림을 받았습니다.")
    }
}

직원

ssar.dart

import 'icecream.dart';

class Ssar {
    //책임 = 아이스크림을 만들어야하는 !!
    Icecream makeIcecream(){
        return Icecream(50, 30, 20);
    }
}

물건

icecream.dart

//물건
class Icecream{
    int ice;
    int milk;
    int sugar;

    Icecream(this.ice, this.milk, this.sugar);
}

아이스크림 가게

main.dart

import 'cos.dart';
import 'ssar.dart';

main() {
    Cos cos = Cos(); //손님
    Ssar ssar = Ssar(); //직원 
    // 손님 cos가 직원 ssar에게 주문
    cos.order(ssar);
}

직원 추가 고용

직원 hoho

hoho.dart

import 'icecream.dart';

class Hoho {
    //책임 = 아이스크림을 만들어야하는 !!
    Icecream makeIcecream(){
        return Icecream(50, 30, 20);
    }
}

main.dart

import 'cos.dart';
import 'ssar.dart';

main() {
    Cos cos = Cos(); //손님
    Ssar ssar = Ssar(); //직원 
    Hoho hoho = Hoho(); //직원 hoho
    // 손님 cos가 직원 ssar에게 주문
    cos.order(ssar);
    // 손님 cos가 직원 hoho에게 주문
    cos.order(hoho); // 에러 !! -> 이것을 해결하기 위해 상속 필요
}

에러 해결 → 상속

emp.dart

import 'icecream.dart';

//직원
class Emp {
    Icecream makeIcecream(){}

}

상속 주입 extends

ssar.dart

import 'icecream.dart';
import 'emp.dart';

class Ssar extends Emp {
    //책임 = 아이스크림을 만들어야하는 !!
    Icecream makeIcecream(){
        return Icecream(50, 30, 20);
    }
}

hoho.dart

import 'icecream.dart';
import 'emp.dart';

class Hoho extends Emp {
    //책임 = 아이스크림을 만들어야하는 !!
    Icecream makeIcecream(){
        return Icecream(50, 30, 20);
    }
}
hoho, ssar 는 extends로 emp를 상속받아 각자 자신의 이름외에 emp를 가지게 됨 이를 다형성이라고 함

이제 손님은 직원이름으로 뿐만 아니라 emp로 주문 가능

cos.dart

//손님
import 'icecream.dart';
import 'ssar.dart';

class Cos {
    //책임 = 주문
    void order(Emp e){
        Icecream i = e.makeIcecream();
        print("아이스크림을 받았습니다.")
    }
}

main.dart

import 'cos.dart';
import 'ssar.dart';
import 'emp.dart';

main() {
    Cos cos = Cos(); //손님
    Ssar ssar = Ssar(); //직원 ssar, emp
    Hoho hoho = Hoho(); //직원 hoho,emp
    Emp ssar = Ssar();

    // 손님 cos가 직원 ssar에게 주문
    cos.order(ssar);
    // 손님 cos가 직원 hoho에게 주문
    cos.order(hoho); // 에러 !! -> 이것을 해결하기 위해 상속 필요 -> 해결
}

왜 ?

main.dart

import 'cos.dart';
import 'ssar.dart';
import 'emp.dart';

main() {
    Cos cos = Cos(); //손님
    Ssar ssar = Ssar(); //직원 ssar, emp
    Hoho hoho = Hoho(); //직원 hoho,emp
    Emp ssar = Ssar();

    // 손님 cos가 직원 ssar에게 주문
    cos.order(ssar);
    // 손님 cos가 직원 hoho에게 주문
    cos.order(hoho); // 메모리에 hoho, emp 둘다 적재
}

메모리에 hoho, emp 둘다 적재 되었을때

emp.dart

import 'icecream.dart';

//직원
class Emp {
    // 무효화됨 (오버라이드, Override)
    Icecream makeIcecream(){}

}

hoho.dart

import 'icecream.dart';
import 'emp.dart';

class Hoho extends Emp {
    //오버라이드
    Icecream makeIcecream(){ // 재정의, 동적바인딩
        return Icecream(50, 30, 20);
    }
}
자식이 부모의 함수를 가지고 다시 작성할때 가져온 부모의 함수는 무효화 !! == Override
실행시에 동적으로 부모 메서드를 무시하고 무효화시켜버리고 자식의 메서드가 실행되는 것 == 동적바인딩

추상화

상속을 위한 껍데기 클래스인 emp 클래스를 추상화 → abstract

emp.dart

import 'icecream.dart';

//직원
abstract class Emp {
    // 무효화됨 (오버라이드, Override)
    Icecream makeIcecream(){} // 몸체가 없는 뼈대 추상메서드 만들 수 있음
}
// 이제 extends로 emp를 가져오면 makeIcecream 메서드가 없을때 오류 발생

정리

상속은 같은 타입으로 묶기 위해서 사용

추상화는 상속을 했을때 강제성을 부여하여 상속받은 클래스의 뼈대를 만들기 위해 사용

참고

https://www.youtube.com/watch?v=Pe4oLca1dF4&list=PL93mKxaRDidGEaUXprXqhNvSW02xCjLZI&index=16

 

728x90

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

[Dart] Future, Isolate  (0) 2021.06.08
[Dart] 상속을 쓰는 이유  (0) 2021.06.07
[Dart] 접근지정자, 생성자, getter, setter  (0) 2021.06.04
[Dart] 클래스  (0) 2021.06.04
[Dart] final 과 const 차이  (0) 2021.06.04

+ Recent posts