Dart-02: Language Tour
Dart-02-01   Important concepts
Dart-02-02   Keywords
Dart-02-03   Variables
Dart-02-04   Built-in types
Dart-02-05   Functions
Dart-02-06   Operators
Dart-02-07   Control flow statements
Dart-02-08   Exceptions
Dart-02-09   Classes
Dart-02-10   Generics
Dart-02-11   Libraries and visibility
Dart-02-12   Asynchrony support
Dart-02-13   Generators
Dart-02-14   Callable classes
Dart-02-15   Isolates
Dart-02-16   Typedefs
Dart-02-17   Metadata
Dart-02-18   Comments

Classes

Dart - Classes

Dart는 class와 mixin 기반 상속이 있는 객체 지향 언어이다. 모든 객체는 class의 instance이며, Null을 제외한 모든 class는 Object의 자손이다. mixin 기반 상속은 모든 class(top class인 Object?를 제외한)에 정확히 하나의 superclass가 있지만, class 본문은 여러 class 계층에서 재사용될 수 있음을 의미한다. Extension method는 class를 변경하거나 subclass를 만들지 않고 class에 기능을 추가하는 방법이다.

0. Example

class Spacecraft {
  String name;
  DateTime? launchDate;

  // Read-only non-final property
  int? get launchYear => launchDate?.year;

  // Constructor, with syntactic sugar for assignment to members.
  Spacecraft(this.name, this.launchDate) {
    // Initialization code goes here.
  }

  // Named constructor that forwards to the default one.
  Spacecraft.unlaunched(String name) : this(name, null);

  // Method.
  void describe() {
    print('Spacecraft: $name');
    // Type promotion doesn't work on getters.
    var launchDate = this.launchDate;
    if (launchDate != null) {
      int years = DateTime.now().difference(launchDate).inDays ~/ 365;
    } else {
      print('Unlaunched');
    }
  }
}
var voyager = Spacecraft('Voyager I', DateTime(1977, 9, 5));
voyager.describe();

var voyager3 = Spacecraft.unlaunched('Voyager III');
voyager3.describe();
class Orbiter extends Spacecraft {
  double altitude;

  Orbiter(String name, DateTime launchDate, this.altitude)
      : super(name, launchDate);
}
mixin Piloted {
  int astronauts = 1;

  void describeCrew() {
    print('Number of astronauts: $astronauts');
  }
}

class PilotedCraft extends Spacecraft with Piloted {
  // ...
}
class MockSpaceship implements Spacecraft {
  // ...
}
abstract class Describable {
  void describe();

  void describeWithEmphasis() {
    print('=========');
    describe();
    print('=========');
  }
}

1. Using class members

객체에는 함수와 data(각각 method와 instance 변수)로 구성된 member가 있다. method를 호출할 때, 객체에서 호출한다: method는 해당 객체의 함수와 data에 접근할 수 있다.

dot(.)을 사용하여 instance 변수 또는 method를 참조한다:

var p = Point(2, 2);

// Get the value of y.
assert(p.y == 2);

// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));

가장 왼쪽 피연산자가 null일 때 예외를 방지하려면 . 대신 ?.를 사용한다.

// If p is non-null, set a variabel equal to its y value.
var a = p?.y;

2. Using constructors

생성자를 사용하여 객체를 만들 수 있다. 생성자 이름은 ClassName또는 ClassName.identifier가 될 수 있다. 예를 들어, 다음 코드는 Point()Point.fromJson() 생성자를 사용하여 Point 객체를 만든다.

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

다음 코드는 동일한 효과를 갖지만, 생성자 이름 앞에 option으로 new keyword를 사용한다:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

일부 class는 constant 생성자를 제공한다. constant 생성자를 사용하여 compile-time constant를 생성하려면, 생성자 이름 앞에 const keyword를 입력한다.

var p = const ImmutablePoint(2, 2);

두 개의 동일한 compile-time constant를 구성하면, 단일 표준 instance가 생성된다.

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b));  // They are the same instance!

constant context에서, 생성자나 literal 앞에 const를 생략할 수 있다. 예를 들어, const map을 생성하는 다음 code가 있다:

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

첫 번째로 사용하는 const keyword를 제외하고 나머지는 생략 가능하다:

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

constant 생성자가 constant context 외부에 있고 const 없이 호출되면, non-constant 객체를 생성한다:

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!

3. Getting an object’s type

runtime에 객체의 type을 가져오려면, Type 객체를 return하는 Object의 property runtimeType을 사용한다.

print('The type of a is ${a.runtimeType}');

runtimeType으로 객체의 type을 test하는 대신 type test operator(as, is)를 사용한다. production 환경에서는, object is Type test가 object.runtimeType == Type test 보다 더 안정적이다.

4. Instance variables

instance 변수를 선언하는 방법은 다음과 같다.

class Point {
  double? x;  // Declare instance variable x, initially null.
  double? y;  // Declare y, initially null.
  double z = 0; // Declare z, initially 0.
}

초기화되지 않은 모든 instance 변수에는 null 값이 있다.

모든 instance 변수는 암시적으로 getter method를 생성한다. non-final instasnce 변수와 initializer가 없는 late final instance 변수도 암시적 setter method를 생성한다.

late가 아닌 instance variable을 초기화하는 경우, 값은 instance가 생성될 때 설정되며, 이는 생성자와 initializer list가 실행되기 전이다.

class Point {
  double? x;  // Declare instance variable x, initially null.
  double? y;  // Declare y, initially null.
}

void main() {
  var point = Point();
  point.x = 4;  // Use the setter method for x.
  assert(point.x == 4);  // Use the getter method for x.
  assert(point.y == null);  // Values default to null.
}

instance 변수는 final이 될 수 있는데, 이 경우 정확히 한 번 설정해야 한다. 생성자 parameter를 사용하거나 생성자의 initializer list를 사용하여 선언 시, final이면서 late가 아닌 instance 변수를 초기화한다.

class ProfileMark {
  final String name;
  final DateTime start = DateTime.now();

  ProfileMark(this.name);
  ProfileMark.unnamed() : name = '';
}

생성자 본문이 시작된 후에 final instance 변수의 값을 할당하려면, 다음 중 하나를 사용할 수 있다.

  • factory 생성자를 사용한다.
  • late final을 사용하지만, 주의해야 한다: initializer가 없는 late final은 API에 setter가 추가된다.

5. Constructors

class와 이름이 같은 함수를 생성하여 생성자를 선언한다. (또는 선택적으로 Named constructor에 설명된 추가 식별자) 생성자의 가장 일반적인 형태인 generative constructor는 class의 새로운 instance를 만든다.

class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    // See initializing parameters for a better way
    // to initialize instance variables.
    this.x = x;
    this.y = y;
  }
}

this keyword는 현재 instance를 나타낸다.

이름 충돌이 있는 경우에만 this를 사용한다. 그렇지 않으면, Dart style은 this를 생략한다.

A. Initializing parameters

instance 변수에 생성자 argument를 할당하는 패턴은 매우 일반적이므로, Dart는 이를 쉽게 하기 위해 초기화 parameter가 있다.

초기화 parameter는 non-nullable이거나 final인 instance 변수를 초기화하는데 사용할 수 있다. 둘 다 초기화되어 있거나 default 값을 제공해야 한다.

class Point {
  final double x;
  final double y;

  // Sets the x and y instance variables
  // before the constructor body runs.
  Point(this.x, this.y);
}

B. Default constructors

생성자를 선언하지 않으면, default 생성자가 제공된다. default 생성자는 argument가 없으며, superclass에서 argument가 없는 생성자를 호출한다.

C. Constructors aren’t inherited

subclass는 superclass에서 생성자를 상속하지 않는다. 생성자를 선언하지 않는 subclass에는 default(no argument, no name) 생성자만 있다.

D. Named constructors

named constructor를 사용하여 class에 대해 여러 생성자를 구현하거나 추가 명확성을 제공한다.

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin()
    : x = xOrigin,
      y = yOrigin;
}

생성자는 상속되지 않는다는 것을 기억해야 한다. 이는 superclass의 named constructor가 subclass에 의해 상속되지 않는다는 것을 의미한다. superclass에 정의된 named constructor로 subclass를 생성하려면, subclass에서 해당 생성자를 구현해야 한다.

E. Invoking a non-default superclass constructor

default로, subclass의 생성자는 superclass의 name과 argument가 없는 생성자를 호출한다. superclass의 생성자는 생성자 본문의 시작 부분에서 호출된다. initializer list를 사용 중인 경우, superclass가 호출되기 전에 실행된다. 요약하면 실행 순서는 다음과 같다.

  1. initializer list
  2. superclass’s no-arg constructor
  3. main class’s no-arg constructor

superclass에 이름과 argument가 없는 생성자가 없으면, superclass의 생성자 중 하나를 수동으로 호출해야 한다. colon(:) 뒤, 생성자 본문(있는 경우) 바로 앞에 superclass 생성자를 지정한다.

다음 예제에서 Employee class의 생성자는 superclass인 Person에 대한 named constructor를 호출한다.

class Person {
  String? firstName;

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

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

void main() {
  var employee = Employee.fromJson({});
  print(employee);
  // Prints:
  // inPerson
  // in Employee
  // Instance of 'Employee'
}

superclass 생성자에 대한 argument는 생성자를 호출하기 전에 평가되기 때문에, argument는 함수 호출과 같은 expression이 될 수 있다:

class Employee() extends Person {
  Employ() : super.fromJson(fetchDefaultData());
  // ...
}

superclass 생성자에 대한 argument에는 this에 대한 접근 권한이 없다. 예를 들어, argument는 static method를 호출할 수 있지만, instance method는 호출할 수 없다.

F. Initializer list

superclass 생성자를 호출하는 것 외에도, 생성자 본문이 실행되기 전에 instance 변수를 초기화할 수도 있다. initializer는 comma로 구분한다.

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json)
    : x = json['x']!,
      y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}

initializer의 오른쪽에는 this에 대한 접근 권한이 없다.

개발 중에, initializer list에 assert를 입력하여 입력의 유효성을 검사할 수 있다.

Point.withAssert(this.x this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

initializer list는 최종 field를 설정할 때 편리하다. 다음 예제에서는 initializer list에서 세 개의 final field를 설정한다.

import 'dart:math';

class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point(double x, double y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

void main() {
  var p = Point(2, 3);
  print(p.distanceFromOrigin);
}

G. Redirecting constructors

때때로 생성자의 유일한 목적은 동일한 class의 다른 생성자로 redirection 하는 것이다. rediriection constructor의 본문은 비어 있으며, 생성자 호출(class 이름 대신에 this를 사용)이 콜론(:) 뒤에 표시된다.

class Point {
  double x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

H. Constant constructors

class가 절대 변경되지 않는 객체를 생성하는 경우, 이러한 객체를 compile-time constant로 만들 수 있다. 이렇게 하려면, const constructor를 정의하고 모든 instasnce 변수가 final이어야 한다.

class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

constant constructor가 항상 constant를 생성하는 것은 아니다.

I. Factory Constructors

항상 해당 class의 새 instance를 생성하지 않는 생성자를 구현할 때, factory keyword를 사용한다. 예를 들어, factory constructor는 cache에서 instance를 return하거나, subtype의 instance를 return할 수 있다. factory constructor의 또 다른 사용 사례는 initializer list에서 처리할 수 없는 논리를 사용하여 final 변수를 초기화하는 것이다.

final 변수의 늦은 초기화를 처리하는 또 다른 방법은 late final을 사용하는 것이다. (carefully!)

다음 예제에서, Logger factory constructor는 cache에서 객체를 return 하고, Logger.fromJson factory constructor는 JSON 객체에서 final 변수를 초기화한다.

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);
  }
}

factory constructor는 this에 접근할 수 없다.

다른 생성자와 마찬가지로 factory constructor를 호출한다.

var logger = Logger('UI');
logger.log('Button clicked');

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

6. Methods

method는 객체에 대한 동작을 제공하는 함수이다.

A. Instance methods

객체의 instance method는 instance 변수 및 this에 접근할 수 있다. 다음 예시의 distanceTo() method는 instance method의 예이다:

import 'dart:math';

class Point {
  final double x;
  final double y;

  Point(this.x, this.y);

  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqsrt(dx * dx + dy * dy);
  }
}

B. Operators

연산자는 특별한 이름을 가진 instance method이다. Dart를 사용하면 다음 이름으로 연산자를 정의할 수 있다.

       
< + | >>>
> / ^ []
<= ~/ & []=
>= * << ~
- % >> ==

!=와 같은 일부 연산자가 이름 목록에 없는 것을 확인할 수 있다. 이들은 단지 syntactic sugar이기 때문이다. 예를 들어, e1 != e2 expression은 !(e1 == e2)에 대한 syntactic sugar이다.

연산자 선언은 내장 식별자 operator를 사용하여 선언한다. 다음 예제에서는 vector addition(+)과 subtraction(-)를 정의한다.

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown.
  // ...
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

C. Getters and setters

Getter와 Setter는 객체 property에 대한 읽기 및 쓰기 접근을 제공하는 특수 method이다. 각 instance 변수에는 암시적 getter와 적절한 경우 setter가 있다. getset keyword를 사용하여 getter 및 setter를 구현하여 추가 property를 만들 수 있다.

class Rectangle {
  double left, top, width, height;

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

  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom => top + height;
  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);
}

getter 및 setter를 사용하면, client code를 변경하지 않고 instance 변수로 시작하여 나중에 method로 wrapping 할 수 있다.

increment(++)와 같은 연산자는 getter가 명시적으로 정의되었는지 여부에 관계없이 예상한 방식으로 작동한다. 예기치 않은 부작용을 피하기 위해, 연산자는 getter를 정확히 한 번 호출하여 값을 임시 변수에 저장한다.

D. Abstract methods

instance, getter, setter method는 추상(abstract)일 수 있으며, interface를 정의하지만 구현은 다른 class에 맡긴다. 추상 method는 추상 class에만 존재할 수 있다.

method를 추상화하려면, method 본문 대신 semicolon(;)을 사용한다.

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}

7. Abstract classes

abstract modifier를 사용하여 instance화 할 수 없는 class인 abstract class를 정의한다. 추상 클래스는 종종 일부 구현과 함께 interface를 정의하는 데 유용하다. 추상 클래스를 instance화 할 수 있도록 표시하려면 factory constructor를 정의한다.

추상 class에는 종종 추상 method가 있다. 다음은 추상 method가 있는 추상 class를 선언하는 예이다:

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren();  // Abstract method.
}

8. Implicit interfaces

모든 class는 class의 모든 instance member와 class가 implement하는 모든 interface를 포함하는 interface를 암시적으로 정의한다. B의 implement를 상속하지 않고 B class의 API를 지원하는 A class를 생성하려면, A class가 B interface를 implement 해야 한다.

class는 하나 이상의 interface를 implements절에서 선언한 다음, interface에 필요한 API를 제공하여 implement 한다. 예를 들어:

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final String _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  String get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

/* result
Hello, Bob. I am Kathy.
Hi Bob. Do you know who I am?
*/

다음은 class가 여러 interface를 구현하도록 지정하는 예이다:

class Point implements Comparable, Location {...}

9. Extending a class

subclass를 만들기 위해 extends를 사용하고, superclass를 참조하는 데 super를 사용한다.

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ...
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ...
}

A. Overriding members

subclass의 instance method(연산자 포함), getter, setter를 재정의할 수 있다. @override annotation을 사용하여 의도적으로 member를 재정의할 수 있다:

class Television {
  // ...
  set contrast(int value) {...}
}

class SmartTelevision extends Television {
  @override
  set contrast(num value) {...}
  // ...
}

overriding method 선언은 이러한 방식으로 override 하는 method(혹은 methods)와 일치해야 한다.

  • return type은 override된 method의 return type과 동일한 type(or a subtype of)이어야 한다.
  • argument type은 override된 method의 argument type과 동일한 type(or a subtype of)이어야 한다. 앞의 예에서, SmartTelevisioncontrast setter는 argument type을 int에서 supertype인 num으로 변경한다.
  • override된 method가 n개의 positional parameter를 수락하면, override 하는 method도 n개의 positional parameter를 수락해야 한다.
  • generic method는 generic이 아닌 method를 override 할 수 없고, generic이 아닌 method는 generic method를 override 할 수 없다.

때로는 method parameter 또는 instance 변수의 type을 좁히고 싶을 수 있다. 이는 일반 규칙을 위반하며, runtime시 type error를 일으킬 수 있다는 점에서 downcast와 유사하다. 그러나, code에서 type error가 발생하지 않도록 보장할수 있는 경우, type을 좁힐 수 있다. 이 경우, parameter 선언에 convariant keyword를 사용할 수 있다.

==을 override 하는 경우, Object의 hashCode getter도 override 해야 한다.

B. noSuchMethod()

code가 존재하지 않는 method나 instance 변수를 사용하려고 할 때마다 감지하거나 반응하기 위해, noSuchMethod()를 override 할 수 있다.

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: '
        '${invocation.memberName}');
  }
}

다음 중 하나에 해당하지 않는 한 구현되지 않은 method를 호출할 수 없다.

  • receiver가 static type dynamic을 가지고 있다
  • receiver에는 구현되지 않은 method를 정의하는 static type이 있고(abstract is OK), receiver의 dynamic type에는 Object class에 있는 것과는 다른 noSuchMethod()의 구현이 있다.

10. Extension methods

extension method는 기존 library에 기능을 추가하는 방법이다. 자신도 모르는 사이에 extension method를 사용할 수 있다. 예를 들어, IDE에서 code completion을 사용하면, 일반 method와 함께 extension method를 제안한다.

다음은 string_apis.dart에 정의된 parseInt()라는 이름의 String extension method를 사용하는 예시이다:

import 'string_apis.dart';
...
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.

11. Enumerated types

enumerations 또는 enums라고도 하는 열거형 type은 고정된 수의 constant 갑승ㄹ 나타내는 데 사용되는 특별한 종류의 class이다.

enum keyword를 사용하여 열거형을 선언한다.

enum Color { red, green, blue }

열거형을 선언할 때 trailing comma를 사용할 수 있다.

열거형의 각 값에는 열거형 선언에 있는 값의 0부터 시작하는 위치를 반환하는 index getter가 있다. 예를 들어 첫 번째 값의 index는 0이고, 두 번째 값의 index는 1이다.

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

열거형의 모든 값 list를 얻으려면, 열거형의 values constant를 사용한다.

list<Color> colors = Color.values;
assert(colors[2] == Color.blue);

switch문에서 열거형을 사용할 수 있으며, 열거형의 모든 값을 처리하지 않으면 warning이 표시된다.

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default:  // Without thisk, you see a WARNING.
    print(aColor);  // 'Color.blue'
}

열거형에는 다음과 같은 제한이 있다.

  • 열거형을 subclass화 하거나 mix in 하거나, implement 할 수 없다.
  • 열거형을 명시적으로 instance화 할 수 없다.

12. Adding features to a class: mixins

mixin은 여러 class 계층에서 class code를 재사용하는 방법이다.

mixin을 사용하려면, with keyword 뒤에 하나 이상의 mixin 이름을 입력한다. 다음 예제에서는 mixin을 사용하는 두 개의 class를 보여준다.

class Musician extends Performer with Musical {
  // ...
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

mixin을 구현하려면, Object를 extend하고 생성자를 선언하지 않는 class를 만든다. mixin을 일반 class로 사용하지 않으려면, class 대신 mixin keyword를 사용한다. 예를 들어:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

때때로 mixin을 사용할 수 있는 type을 제한하고 싶을 수 있다. 예를 들어, mixin은 mixin이 정의하지 않은 method를 호출할 수 있는지 여부에 따라 달라질 수 있다. 다음 예제에서 볼 수 있듯이, on keyword를 사용하여 필요한 superclass를 지정하여 mixin의 사용을 제한할 수 있다.

class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer {
  // ...
}

앞의 code에서 class를 extend하거나 implement하는 Musician class만 mixin MusicalPerformer를 사용할 수 있다. SingerDancerMusician을 extend 했기 때문에, SingerDancerMusicalPerformer를 mixin 할 수 있다.

13. Class variables and methods

static keyword를 사용하여 class-wide 변수 및 method를 구현한다.

A. Static variables

static 변수(class 변수)는 class-wide state 및 constant에 유용하다.

class Queue {
  static const initialCapacity = 16;
  // ...
}

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

static 변수는 사용될 때까지 초기화되지 않는다.

B. Static methods

static method(class method)는 instance에서 작동하지 않으므로, this에 접근할 수 없다. 그러나, static 변수에는 접근할 수 있다. 다음 예제에서 볼 수 있듯이, class에서 직접 static method를 호출한다.

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);
}

일반적이거나 널리 사용되는 utility 및 기능에 대해 static method 대신 top-level 함수를 사용하는 것을 고려해봐야 한다.

태그:

카테고리:

업데이트:

댓글남기기