Dart(2)

Dart(1)에 이어서 객체지향 부분과 Generic 을 정리해보자.


Classes

기본적인 사항들

  • 기본적으로 C++이나 Java의 상식을 크게 벗어나지 않는다.
  • 클래스는 기본적으로 C++이나 Java랑 유사하게 생겼다.
  • private 멤버는 _를 앞에 붙인다.
    1
    2
    3
    class Test {
      int _private = 5;
    }
    
  • 멤버에 접근할때는 .?.을 쓸 수 있다.

Constructors

  • 기본 생성자는 인자를 받지 않고, 부모의 생성자만 콜해준다.
  • C++과 마찬가지로 생성자를 따로 만들면, 기본 생성자는 안생긴다.
  • 생성자는 상속이 되지 않는다.
  • 초기화용 생성자 문법이 있고, 매우 간단하다.
    1
    2
    3
    4
    class Point {
      num x, y;
      Point(this.x, this.y);
    }
    
  • 함수 오버로딩도 안되는데, 당연히 생성자도 오버로딩이 안된다.
    1
    2
    3
    4
    5
    6
    7
    8
    class Point {
      num x, y;
      Point(this.x, this.y);
      Point(int x) {  // error
        this.x = x;
        this.y = x;
      }
    }
    
  • 대신에 Point.sym처럼 이름을 덧붙여서 생성자를 여러개 생성할 수 있다.
    1
    2
    3
    4
    5
    6
    7
    8
    class Point {
      num x, y;
      Point(this.x, this.y);
      Point.sym(int x) {
        this.x = x;
        this.y = x;
      }
    }
    
  • C++의 Initializer List 같은 유사한 문법이 지원된다. Initializer List로 쓰려면 좌변(멤버변수) = 우변(리턴 값이 있는 식) 형태여야 한다.
    1
    2
    3
    4
    5
    6
    class Point {
      num x, y;
      Point(this.x, this.y);
      Point.sym(int x) x = x, y = x;
      }
    }
    
  • 몇가지 Initializer List와 같이 쓸 수 있는 예외가 있다. assert가 그중 하나.
    1
    Point.sym(int x) x = x, y = x, assert(x >= 0);
    
  • super도 Initializer List와 같이 쓸 수 있다. 부모의 생성자가 여러개라면 필연 위와같이 지정을 해주어야 한다.
    1
    Point.sym(int x) x = x, y = x, super.sym();
    
  • Redirecting constructors를 사용하면 다른 Constructor를 재사용 할 수 있다. 다만 Initializer List 및 함께 들어있는 assert와는 혼용 불가능하다. 에러난다.
    1
    2
    3
    4
    5
    6
    7
    class Point {
      num x, y;
      Point(this.x, this.y);
      Point.sym1(int x): this(x,x);
      Point.sym2()     : x=5, this(x,x); // error
      Point.sym3(int x): this(x,x), assert(x >= 0); //error
    }
    
  • factory 키워드를 쓰면 다른 인스턴스를 리턴해 줄 수 있다. Singleton이나 Factory Pattern으로 객체를 작성할 때 유용하다. 여기서 퍼왔음.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class Point implements PointFactory {
      num x, y;
      Point(int x, int y): x=x,y=y;
    }
    
    class PointIn3rd implements PointFactory {
      num x, y;
      PointIn3rd(int x, int y): x=x,y=y;
    }
    
    abstract class PointFactory {
      factory PointFactory(int x, int y) {
        if (x < 0 && y < 0)
          return PointIn3rd(x,y);
        return Point(x,y);
      }
    }
    
    void main() {
      print(PointFactory(1,1));   //Instance of 'Point'
      print(PointFactory(-1,-1)); //Instance of 'PointIn3rd'
    }
    

Getter와 Setter

  • getter와 setter를 get, set 키워드로 정해줄 수 있다. 이 함수들은 마치 변수처럼 사용할 수 있다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Point {
      int x, y;
      Point(this.x, this.y);
      int get dist => x*x + y*y;
      set both(int x) => this.x = this.y = x;
    }
    
    void main() {
      Point p = Point(1,2);
      print(p.dist);  // 5
      p.both = 3;
      print(p.dist);  // 18
    }
    

상속과 다형성

  • 상속과 다형성은 거의 Java와 비슷하다.
  • 상속은 extends 키워드를 통해서 한다. 다중상속 안됨.
  • 인터페이스를 강제할 때는 implements 키워드를 통해서 한다.
    • dart는 따로 interface 키워드가 없다. 때문에 class를 인터페이스로 전달한다.
    • implements 로 지정된 subclass는 전달된 class의 모든 함수와 변수를 재정의해야한다. 단 생성자는 제외이다.
  • abstract를 활용해서 추상 클래스를 만든다.
  • @override annotation을 통해서 오버라이딩을 명시한다.
  • 원래 오버라이딩은 원본과 모든 인자의 타입이 같아야한다. 하지만 예외적으로 covariant 키워드를 통해서 타입을 더 좁힐(자녀 클래스) 수 있다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Animal {
      void chase(Animal x) { ... }
    }
    
    class Mouse extends Animal { ... }
    
    class Cat extends Animal {
      void chase(covariant Mouse x) { ... }
    }
    
  • mixin을 활용하면 Feature를 추가할 수 있다. 다중으로 Feature를 사용할 수 있다.
    • mixinclass와 거의 유사하게 사용하되, 생성자가 없다.
    • class 선언 시 with 키워드를 통해서 사용한다.
      1
      2
      class Maestro extends Person
      with Musical, Aggressive, Demented {
      

Const, Static with class

  • const로 생성한 경우, 상수 취급이 되고, 모든 멤버가 const이다.
  • const로 생성한 Instance의 모든 필드가 같을 경우, 같은 오브젝트 취급한다.
    1
    2
    3
    4
    var a = const ImmutablePoint(1, 1);
    var b = const ImmutablePoint(1, 1);
    
    assert(identical(a, b)); // They are the same instance!
    
  • const를 붙인 생성자는 아예 const Instance를 생성해버린다. 대신 모든 멤버는 final로 선언되어 있어야 한다.
    1
    2
    3
    4
    class ImmutablePoint {
      final num x, y;
      const ImmutablePoint(this.x, this.y);
    }
    
  • static으로 지정하면 정적 함수나 변수가 된다. Instance화 시키지 않고 사용할 수 있다. C++의 그것과 똑같다.

Generics

  • Java의 Generic과 거의 같다.
  • Java에서 처럼 타입 안정성, 코드 중복 제거를 위한 목적으로 쓴다.
    1
    2
    3
    var names = <String>['Seth', 'Kathy', 'Lars'];
    names.add("123");
    names.add(123); // error
    
  • Java와 달리 모든 타입이 Object이기 때문에 굳이 Wrapper들을 안써도 된다.
  • 코드 중복 제거의 예시는 아래와 같다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Vector<T extends num> {
      T x, y;
      Vector(this.x, this.y);
      T get w => x*x + y*y;
    }
    
    void main() {
      var intVector = Vector(1,2);
      // 다만 아래와 같이 쓰는게 권장된다.
      // Vector<int> intVector = Vector<int>(1,2);
      var floatVector = Vector(2.5, 1.5);
      print(intVector.w);
      print(floatVector.w);
    }
    

Asynchrony support

  • Flutter와 연관이 깊어지는 비동기 부분이다.

Future

  • 비동기 값을 저장할 때 쓴다.
  • JavaScript에서 쓰던 것 처럼, thencatchError로 콜백을 넘기면 된다.
    1
    2
    3
    Future<int> future = getFuture();
    future.then((value) => handleValue(value))
      .catchError((error) => handleError(error));