Flutter - 로컬 데이터베이스 ③: Singleton, factory

2 분 소요


목차

1: Flutter - 로컬 데이터베이스 ①: sqflite(생성, 삽입, 업데이트, 삭제, 쿼리)

  • sqflite를 사용하여 Specs(id, type, category, method, contents, money, dateTime)라는 로컬 데이터베이스를 생성
  • 로컬 데이터베이스에 접근하는 SpecProvider 클래스를 생성하고, DB에 읽고 쓰는 함수들을 정의

2: Flutter - 로컬 데이터베이스 ②: Future(데이터베이스 화면에 띄우기, FutureBuilder)

  • DB에 접근하는 것은 async한 작업
  • 이를 Future 키워드를 사용해 다루고, FutureBuilder로 화면에 값을 보여줌

3: Flutter - 로컬 데이터베이스 ③: Singleton, factory <현재>

  • DB 인스턴스를 Singleton으로 생성

4: Flutter - 로컬 데이터베이스 ④: 이미지 파일을 데이터베이스에 저장하고 읽기

  • 이미지 파일을 데이터베이스에 저장하고 읽기


그런데, 현재는 데이터베이스를 접근할 때마다 SpecProvider()로 클래스 인스턴스를 새로 생성한다.
즉, 매번 DB를 새로 접근해서 열게 되는데, 이는 불필요한 작업이다.
한 번 커넥션이 생기면 그걸 다른 데서도 공유를 하는 게 좋을 건데, 이럴 때에 Singleton을 사용한다.


Singleton

Singleton

클래스의 객체를 하나만 만들어야 할 때 사용된다. 즉 그 클래스의 인스턴스는 하나뿐이고, 이걸 어디에서든 같은 걸 공유하며 사용된다. 이는 자원 낭비를 막을 수 있다.
인스턴스는 final로 선언하여 변경 불가능하게 한다.


factory

다트 도큐먼트에서 factory에 대해 알아보자

Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. For example, a factory constructor might return an instance from a cache, or it might return an instance of a subtype. Another use case for factory constructors is initializing a final variable using logic that can’t be handled in the initializer list.

매번 클래스의 인스턴스를 새로 생성하지 않는 생성자를 구현할 때 factory 키워드를 사용하도록 한다.


Singleton 클래스 구현하기

class SomeSingleton {
  static final SomeSingleton _someSingletonInstance = SomeSingleton._internal();
  SomeSingleton._internal(){
    // init values...
  }
  factory SomeSingleton() {
    return _someSingletonInstance;
  }
}

_someSingletonInstance라는 객체의 인스턴스를 처음에 만들어두고, factory 키워드로 매번 새로 인스턴스를 생성하지 않게 객체가 생성될 때마다 이미 만들어진 _someSingletonInstance를 반환한다.

SomeSingleton._internal()은 처음에 한 번만 실행되는 생성자다. 물론 _internal 부분은 이름을 마음대로 해도 된다. 해당 생성자 안에서 클래스에서 사용될 변수들을 초기화하면 된다.


await, async한 내부 변수

그런데 생성자는 async할 수 없다. 따라서 데이터베이스와 같이 await할 필요가 있는 변수들을 await하지 못한 채 생성자에서 초기화할 경우, 진짜로 초기화되기 전에 클래스 인스턴스가 먼저 반환되는 문제가 생긴다.

데이터베이스를 옳게 한 번만 초기화하는 방법을 스택 오버플로우에서 찾았다.


이전 코드

class SpecProvider {
  static late Database _database;

  Future<Database?> get database async {
    _database = await initDB();
    return _database;
  }
}

late 키워드는 변수가 늦게 초기화될 때 사용했었다. 그런데 late는 해당 변수가 언제 초기화 되는지는 알 수 없다고 한다.


바꾼 코드

class SpecProvider {
  static final SpecProvider _specProvider = SpecProvider._internal();
  SpecProvider._internal(){
    // init values...
    
    /*
    async cannot be used in constructor,
    so change 'get database'
    */
  }
  factory SpecProvider() {
    return _specProvider;
  }

  static Database? _database;

  Future<Database> get database async => _database ??= await initDB();
}

데이터베이스는 late 키워드 사용 대신 static Database? _database;로 변경한다.
??= 연산자로 null인지 확인 후, 값이 있으면 그냥 반환하고 없으면 await할 수 있다.




굿

댓글남기기