반응형

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

 

A tour of the Dart language

A tour of all the major Dart language features.

dart.dev

 

 

위 문서로 공부하며 기억해둘만한 내용만 정리

https://dartpad.dev/

 

DartPad

 

dartpad.dev

실습 사이트

 

부가적인 내용이나 다른 코드가 섞여있을 수 있습니다.

 

 

- 메소드

메소드는 객체에 대한 동작을 제공하는 함수

 

인스턴스 메소드 : 인스턴스 변수와 this에 접근할 수 있다. 

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 sqrt(dx * dx + dy * dy);
  }
}

 

 

연산자 선언은 내장 식별자 operator를 이용하여 식별된다.

다음 코드는 벡터 더하기, 빼기를 정의한다.

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

 

 

Getter와 Setter는 객체 속성에 읽기와 쓰기 접근을 제공하는 특별한 메소드다.

각 인스턴스 변수는 암시적으로 getter를 가지고, 적절한 경우에 setter를 가진다.

get과 set 키워드를 사용하여 getter와 setter를 구현함으로써 추가 속성을 만들 수 있다.

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;
  asse

 

getter와 setter를 사용하면, 클라이언트 코드 변경 없이 인스턴스 변수로 시작하여 나중에 메소드로 래핑할 수 있다.

 

 

인스턴스, getter, setter는 추상일 수 있으며, 인터페이스를 정의하지만 구현은 다른 클래스에 맡긴다.

추상 메소드는 오직 추상 클래스에만 존재할 수 있다.

메소드 추상화를 만들기 위해, 메소드 바디 대신 세미콜론(;)을 사용한다.

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...
  }
}

 

 

 

- 추상 클래스

abstract 한정자를 이용하여 인스턴스화할 수 없는 추상 클래스를 정의한다.

추상 클래스는 일부 구현과 함께 인터페이스를 정의하는데 유용하다.

추상 클래스를 인스턴스화할 수 있도록 표시하려면, 팩토리 생성자를 정의하자.

 

추상 클래스는 종종 추상 메소드를 가진다. 

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

  void updateChildren(); // Abstract method.
}

 

 

- Implicit interfaces

모든 클래스는 암시적으로 모든 클래스의 멤버와 클래스가 구현하는 모든 인터페이스들을 포함하는 인터페이스를 정의한다.

클래스 A가 클래스 B의 API를, B의 구현 상속 없이 지원하도록 만드려면, 클래스 A가 클래스 B의 인터페이스를 구현해야 한다.

 

클래스는 implements 절에서 선언한 다음, 인터페이스에 필요한 API를 제공함으로써, 하나 이상의 인터페이스를 구현한다.

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

 

클래스는 여러 인터페이스를 구현할 수도 있다.

class Point implements Comparable, Location {...}

 

 

- Extending a class

하위 클래스를 만드려면 extends를 사용하고, 상위 클래스를 참조하려면 super를 사용한다.

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

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

 

 

서브클래스는 인스턴스 메소드(operators 포함), getter, setter를 재정의할 수 있다.

의도적으로 멤버를 재정의 했음을 나타내기 위해 @override 애노테이션을 사용할 수 있다.

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

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

 

재정의 메소드 선언은, 여러 방법으로 재정의하는 메소드와 일치해야 한다.

반환 타입은 재정의된 메소드의 반환 타입과 동일한 타입(또는 하위 타입)이어야 한다.

인자 타입은 재정의된 메소드의 인자 타입과 동일한 타입(또는 상위 타입)이어야 한다. 위 코드에서 SmartTelevision의 constrast setter는 인자 타입을 int 타입에서 상위 타입인 num 타입으로 변경한다.

재정의된 메소드가 n개의 positional parameter를 허용하면, 재정의하는 메소드도 n개의 positional parameter를 허용해야 한다.

제너릭 메소드는 제너릭이 아닌 메소드를 재정의할 수 없고, 제너릭이 아닌 메소드는 제너릭 메소드를 재정의할 수 없다.

 

때로는 메소드 파라미터 또는 인스턴스 변수의 범위를 좁히고 싶을 수 있다. 이는 일반적인 규칙을 위반하고, 런타임에 타입 에러를 일으킬 수 있다는 점에서 다운 캐스트와 유사하다. 그러나 코드에서 타입 에러가 발생하지 않도록 보장할 수 있는 경우 타입을 좁힐 수 있다. 이 경우 파라미터 선언에서 convariant 키워드를 사용할 수 있다.

 

==를 재정의한다면, Object의 hashCode getter도 재정의해야한다.

 

코드가 존재하지 않는 메소드나 인스턴스 변수를 사용할 때마다 감지하거나 반응하려면, noSuchMethod()를 재정의할 수 있다.

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

 

 

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

수신자가 정적 타입 dynamic을 가지는 경우

수신자는 구현되지 않은 메소드를 정의하는 정적 타입(추상도 OK)을 가지고, 수신자의 동적 타입에는 Object 클래스의 것과 다른 noSuchMethod()의 구현이 있는 경우

 

 

- Extension methods

Extension method는 존재하는 라이브러리에 기능을 추가하는 방법이다. 자신도 모르는 사이에 extension method를 사용하고 있을지도 모른다. 예를 들어, IDE에서 코드 완성을 사용하면 일반 메소드와 함께 확장 메소드를 제안한다.

 

다음 코드는 string_apis.dart에 정의된 parseInt()라는 String의 확장 메소드를 사용한다.

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

 

 

- Enumerated types

종종 enumerations 또는 enum이라고 불리는 열거 타입은, 고정된 수의 상수 값을 나타내는 특별한 클래스다.

 

모든 enum은 Enum 클래스를 자동으로 상속한다. 이들은 하위 클래스화, 구현, 믹스인, 또는 명시적으로 인스턴스화할 수 없다.

추상 클래스와 믹스인은 명시적으로 Enum을 구현 또는 확장할 수 있지만, enum 선언에 의해 구현되거나 믹스되지 않는 한, 어떤 객체도 실제로 해당 클래스 또는 믹스인의 타입을 구현할 수 없다.

 

간단히 열거형 타입을 구현하기 위해, enum 키워드를 사용하고 원하는 값들을 열거한다.

enum Color { red, green, blue }

 

Dart는 enum 선언이 필드, 메소드, const 생성자가 있는 클래스를 선언할 수 있다. 이는 알려진 상수 인스턴스의 고정된 수로 제한된다.

향상된 enum을 선언하는 것은 class와 유사하지만, 추가적인 요구 사항이 필요하다.

인스턴스 변수는 final(믹스인으로 추가된 변수를 포함)이다.

모든 generative 생성자는 constant여야 한다.

팩토리 생성자는 알려진 enum 인스턴스 중 하나만 반환할 수 있다.

Enum은 자동으로 확장되므로, 다른 클래스는 확장될 수 없다.

index, hashCode, ==는 재정의될 수 없다.

values라는 멤버는 열거형에서 선언될 수 없다. 자동으로 생성된 정적 values getter와 충돌하기 때문.

enum의 모든 인스턴스는 선언부의 시작 부분에서 선언되어야 하고, 적어도 하나 이상의 인스턴스가 선언되어야 한다.

enum Vehicle implements Comparable<Vehicle> {
  car(tires: 4, passengers: 5, carbonPerKilometer: 400),
  bus(tires: 6, passengers: 50, carbonPerKilometer: 800),
  bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);

  const Vehicle({
    required this.tires,
    required this.passengers,
    required this.carbonPerKilometer,
  });

  final int tires;
  final int passengers;
  final int carbonPerKilometer;

  int get carbonFootprint => (carbonPerKilometer / passengers).round();

  @override
  int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;
}

 

 

스태틱 변수처럼 열거된 값에 접근할 수 있다.

final favoriteColor = Color.blue;
if (favoriteColor == Color.blue) {
  print('Your favorite color is blue!');
}

 

 

enum의 각 값은 0부터 시작하는 인덱스 getter를 가진다.

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

 

 

enum의 values 상수를 이용하여 모든 열거된 값의 리스트를 얻을 수 있다.

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 this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

 

 

열거형 값의 이름에 접근하려면 .name 속성을 사용한다.

print(Color.blue.name); // 'blue'

 

 

 

- 클래스에 기능 추가하기 : 믹스인

믹스인은 여러 클래스 계층에서 클래스 코드를 재사용하기 위한 방법이다.

믹스인을 사용하기 위해 with 키워드 뒤에 하나 이상의 믹스인 이름을 사용한다.

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

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

 

 

믹스인을 구현하기 위해, 오브젝트를 확장하고 생성자가 없는 클래스를 만든다.

일반적인 클래스로 사용하지 않으려면, mixin 키워드를 사용한다.

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

 

 

믹스인을 사용하는 타입을 제한하고 싶을 수도 있다.

믹스인은 믹스인이 정의하지 않은 메소드를 호출할 수 있는지 여부에 따라 달라질 수 있다.

다음 코드는 on 키워드를 사용하여 요구되는 수퍼 클래스를 지정하고, 믹스인 사용을 제한한다.

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

위 코드에서는 Musician을 구현 또는 확장한 클래스만 Musician 클래스를 사용할 수 있다.

SingerDancer는 Musician을 확장했기 때문에 믹스할 수 있다.

 

 

- 클래스 변수와 클래스 메소드

static 키워드를 사용하여 클래스 전체 변수 및 메소드를 구현할 수 있다.

 

정적 변수는 클래스 전체 상태 및 상수를 나타내는데 유용하다.

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

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

정적 변수는 사용되기 전까지 초기화되지 않는다.

 

정적 메소드(클래스 메소드)는 인스턴스에서 동작하지 않으므로, this에 접근할 수 없다.

하지만 정적 변수에는 접근할 수 있다.

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

 

 

일반적이거나 널리 사용되는 유틸리티와 기능은, 정적 메소드 대신 최상위 함수를 고려하자.

 

컴파일 타임 상수로서 정적 메소드를 만들 수도 있다. 예를 들어, constant 생성자에 정적 메소드를 파라미터로 전달할 수 있다.

반응형

'플러터' 카테고리의 다른 글

Dart 문법 공부 - 6  (0) 2022.06.01
Dart 문법 공부 - 5  (0) 2022.05.30
Dart 문법 공부 - 3  (0) 2022.05.29
Dart 문법 공부 - 2  (0) 2022.05.28
Dart 문법 공부 - 1  (0) 2022.05.28

+ Recent posts