본문 바로가기
기타개발/Flutter

Dart #5 : Classes, Constructor

by 궝테스트 2020. 8. 28.

Dart : https://dart.dev/guides/language/language-tour

  • 다트는 클래스와 Mixin 기반 상속이있는 객체 지향 언어이다
  • 모든 객체는 클래스의 인스턴스이며 모든 클래스는 Object 의 자식이다
  • 믹스인 기반 상속은 모든 클래스 (Object 제외) 에 하나의 수퍼 클래스가 있지만,
    클래스 본문은 여러 클래스 계층에서 재사용 될 수 있다
  • 확장 메서드는 클래스를 변경하거나 하위 클래스를 만들지 않고 클래스에 기능을 추가하는 방법이다

 

1. Class members

  • 객체는 함수와 데이터 (각각 메서드 및 인스턴스 변수) 로 구성된 멤버가 있다
  • 메소드를 호출하면 객체에서 invoke 한다: 메소드는 해당 객체의 함수와 데이터에 액세스 할 수 있다
var p = Point(2, 2);

// nullable 인스턴스 변수 y 에 non-null 일 경우 값 3을 셋팅한다
p?.y = 3;

// y 의 값을 가져와서 비교한다 -> true
assert(p?.y == 3);

// p 에서 distanceTo() 를 호출한다 (invoke)
double distance = p.distanceTo(Point(4, 4));

 

1-1. Instance varaiables

  • 초기화되지 않은 인스턴스 변수의 값은 null 이다
  • 생성자 또는 메서드가 아닌 선언된 인스턴스 변수를 초기화하는 경우,
    이 값은 인스턴스가 생성될 때 설정되며, 이는 생성자와 initializer 목록이 실행되기 전이다
class Point {
  double x; // 초기화되지 않은 인스턴스 변수 x의 값은 null 이다
  double y; // 초기화되지 않은 인스턴스 변수 y의 값은 null 이다
  double z = 0;
}

 

1-2. Static variables

  • static 키워드를 사용하여 클래스 전체 변수 및 메서드를 구현할 수 있다
  • static 변수는 사용되기 전까지 초기화되지 않는다
class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

 

1-3. Instance methods

  • 객체의 인스턴스 메서드는 인스턴스 변수에 액세스 할 수 있다
import 'dart:math';

class Point {
  double x, y;

  Point(this.x, this.y);

  // 인스턴스 메소드
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

 

1-4. Static methodes

  • static 메서드 (클래스 메서드) 는 인스턴스에서 작동하지 않으므로 this 액세스 권한이 없다
  • static 메서드는 컴파일 타임 상수로 사용할 수 있다
    • ex) static 메서드를 상수 생성자에 매개 변수로 전달할 수 있다
  • 일반적이거나 여러곳에서 사용되는 유틸리티 같은 함수는 static 메서드 대신 top-level 함수를 사용하는게 좋다
import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

 

 

1-5. Getters and setters

  • 객체 속성에 대한 읽기 및 쓰기 접근을 제공하는 메서드이다
  • 모든 인스턴스 변수는 암시적으로 getter 메서드를 생성한다
  • non-final 인스턴스 변수는 암시적으로 setter 메서드가 생성된다
  • get 및 set 키워드를 사용하여 getter 및 setter 를 구현하여 추가 속성을 만들 수 있다
  • getter 와 setter를 사용하면 클라이언트 코드를 변경하지 않고도 나중에 인스턴스 변수를 메소드로 wrapping 할 수 있다
class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // right 을 계산하여 값을 반환하는 getter 함수
  double get right => left + width;
  
  // right 을 계산하여 값을 할당하는 setter 함수
  set right(double value) => left = value - width;
  
  // bottom 을 계산하여 값을 반환하는 getter 함수
  double get bottom => top + height;
  
  // bottom 을 계산하여 값을 할당하는 setter 함수
  set bottom(double value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

 

2. Getting an object's type

  • 런타임에 객체의 타입을 가져 오려면 Type 객체를 반환하는 Object 의 runtimeType 속성을 사용할 수 있다
print('The type of a is ${a.runtimeType}');

 

3. Constructors

  • 생성자를 사용하여 객체를 만들 수 있다
  • 생성자 이름은 클래스와 이름이 같거나 ClassName.identifier 이다
  • 가장 일반적인 형태의 생성자인 generative constructor 는 클래스의 새로운 인스턴스를 생성한다
// Point() 및 Point.fromJson() 생성자를 사용하여 Point 객체를 만든다
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

// 위와 동일하게 객체를 만들지만 앞에 new 키워드를 사용할 수 있다
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

 

3-1. Default constructors

  • 생성자를 선언하지 않으면 기본 생성자가 제공된다
  • 기본 생성자에는 인수가 없으며 super 클래스에서 인수가 없는 생성자를 호출한다
  • 생성자는 상속되지 않는다
    • 하위 클래스는 super 클래스에서 생성자를 상속하지 않는다
    • 생성자를 선언하지 않은 하위 클래스에는 기본(인수, 이름 없음) 생성자만 있다

 

3-2. Named constructors

  • 이름이 지정된 생성자를 사용하여 여러 생성자를 구현하거나 명확성을 제공할 수 있다
  • 생성자는 상속되지 않으므로 super 클래스의 이름이 지정된 생성자가 하위 클래스에서 상속되지 않는다
  • super 클래스의 이름이 지정된 생성자로 서브 클래스를 생성하려면 해당 생성자를 서브 클래스에서 구현해야한다
class Point {
  double x, y;

  Point(this.x, this.y);

  // 이름이 지정된 생성자
  Point.origin() {
    x = 0;
    y = 0;
  }
}

 

3-3. Constant constructors

  • 클래스가 변경되지 않는 객체를 생성하는 경우 객체를 컴파일 타임 상수로 만들 수 있다
  • 생성자 이름 앞에 const 키워드를 넣고 모든 인스턴스 변수가 final 인지 확인해야한다
class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}
var p = const ImmutablePoint(2, 2);

// 두 개의 동일한 컴파일 타임 상수를 생성하면 표준 인스턴스 하나가 생성된다
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 둘은 같은 인스턴스!
  • 상수 컨텍스트 내에서 생성자 또는 리터럴 앞에 const를 생략 할 수 있다
// const 키워드가 너무 많다
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

// 상수 context 를 설정하는 한개의 const 키워드만 있다
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
  • 상수 생성자가 상수 컨텍스트 외부에 있고 const없이 호출되면, 상수가 아닌 일반 객체를 생성한다
var a = const ImmutablePoint(1, 1); // 상수 생성자
var b = ImmutablePoint(1, 1); // 상수 아님

assert(!identical(a, b)); // 같은 인스턴스가 아님

 

3-4. Factory constructors

  • 클래스의 새 인스턴스를 만들지는 않는 생성자를 구현할 때 factory 키워드를 사용한다
  • ex1) 팩토리 생성자는 캐시에서 인스턴스를 반환하거나 하위 타입의 인스턴스를 반환 할 수 있다
  • ex2) 이니셜 라이저 목록에서 처리 할 수없는 로직을 사용하여 final 변수를 초기화한다
  • 아래 예제에서 Logger 팩토리 생성자는 캐시에서 객체를 반환한다
    • Logger.fromJson 팩토리 생성자는 JSON 객체에서 final 변수를 초기화한다
  • 팩토리 생성자는 this 권한이 없다
class Logger {
  final String name;
  bool mute = false;

  // _cache 는 library-private 하다.
  static final Map<String, Logger> _cache = <String, Logger>{};

  // factory 생성자인 아래 Logger 를 통해 log() 를 호출할 수 있다
  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);
  }
}

// 아래처럼 생성하고 호출할 수 있다
var logger = Logger('UI');
logger.log('Button clicked');

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);

 

3-5. Constructor argument

// this 를 이용하여 클래스의 x, y 인스턴스에 값을 할당할 수 있다
class Point {
  double x, y;

  Point(double x, double y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

// 아래와 같이 사용할 수도 있다
class Point {
  double x, y;

  // 생성자 내부가 실행되기 전에 인스턴스 x, y 에 값이 할당된다
  Point(this.x, this.y);
}

 

3-6. Invoking a non-default superclass constructor

  • 기본적으로 하위 클래스의 생성자는 super 클래스의 이름이 지정되지 않고 인수 없는 생성자를 호출한다
  • super 클래스의 생성자는 생성자 본문의 시작 부분에서 호출됩니다
  • initializer 목록도 사용중인 경우 수퍼 클래스가 호출되기 전에 실행된다
  • 기본 실행 순서
    1. initializer list
    2. super 클래스의 인수없는 생성자
    3. main 클래스의 인수없는 생성자
  • super 클래스에 이름이 지정되지 않은 인수없는 생성자가 없으면, 수퍼 클래스의 생성자 중 하나를 수동으로 호출해야한다
  • 생성자 본문 (있는 경우) 바로 앞에 콜론 (:) 뒤에 수퍼 클래스 생성자를 지정한다
class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person 클래스는 기본 생성자가 없다
  // 따라서 super.fromJson(data) 를 호출해야만 한다
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});
}


// result
in Person
in Employee
  • 슈퍼 클래스 생성자에 대한 인수는 해당 생성자를 호출하기 전에 할당되기 때문에 인수는 함수 호출과 같이 표현식이 될 수 있다
  • 슈퍼 클래스 생성자에 대한 인수는 'this' 로 액세스 할 수 없다
class Employee extends Person {
  Employee() : super.fromJson(defaultData);
  // ···
}

 

3-7. Initializer list

  • 슈퍼 클래스 생성자를 호출하는 것 외에도 생성자 본문이 실행되기 전에 인스턴스 변수를 초기화 할 수 있다
  • 이니셜 라이저는 쉼표로 구분한다
Point.fromJson(Map<String, double> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

// initializer list 에 assert 를 사용하여 검증할 수 있다
Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}
  • 이니셜라이저는 final 필드를 셋팅할 때 유용하다
class Point {
  final num x;
  final num y;
  final num z;

  Point(x, y)
      : x = x,
        y = y,
        z = x + y;
}

main() {
  var p = new Point(2, 3);
  print(p.z);
}

// result
5

 

3-8. Redirecting constructors

  • 생성자의 목적으로 동일한 클래스의 다른 생성자로 리디렉션하는 경우도 있다
  • 리디렉션 할 생성자의 본문이 비어 있으면 콜론 (:) 뒤에 생성자 호출을 작성한다
class Point {
  double x, y;

  // 메인 생성자
  Point(this.x, this.y);

  // 메인 생성자로 랜딩
  Point.alongXAxis(double x) : this(x, 0);
}

댓글