본문 바로가기
Study/Flutter

[Dart] dart 3.0 발표 요약

 
 
 
Double-click
 
Select to translate

 

https://dart.dev/guides/language/evolution

 

Dart language evolution

Notable changes and additions to the Dart programming language.

dart.dev

 

다트가 지난 23년 1월 2.19v로 진화하더니

23년 5월 3.0v로 다시 한번 더욱 발전 하였습니다.

이번 3.0은 100% sound null safety 보장한다고 합니다.

건전한 100% 널 안전성을 보장한다는 의미는 Dart 3.0에서는 모든 라이브러리가 널안전성을 지원하며

예기치못한 null 과 관련한 문제로부터 예외처리, 역참조문제 등을 완전히 피할수 있게 해줍니다.

 

문법적으로는 크게 3가지로 요약할 수 있습니다.

1.Records - 복수 자료형의 리턴

2. Patterns - 값을 일치시키고 분해시키는 패턴

3. Class modifiers - 클래스 제한자

지금부터 하나씩 살펴보겠습니다.

 

1. Record

JavaScript에서 매력적이던 record가 도입되어,

이제부터는 복수개의 자료형을 반환할 수 있게 된다.

 

선언 - 익명 / 이름

void main() {
  // positional params만 있고 named params가 없는 record 선언
  (int, String, bool) myRecord;
  
  myRecord = (0, "hello", true);

  print(myRecord);
  print(myRecord.$3); //익명이므로 $기호로 1번부터 3번까지 파람에 접근가능
  print("{$myRecord.runtimeType}\n");

  // positional과 named를 모두 가진 record 선언
  (bool p, {int i, String n, bool b}) myRecord2 = (true, i: 0, n:"zero", b:true);
  print(myRecord2);
  print(myRecord2.n); // 이름으로 접근가능
  print(myRecord2.runtimeType);
}

이렇게 익명으로 선언하면 $기호를 통해 접근가능하며

이름이 있는 record는 변수명으로 특정 위치에 접근가능하다.

특징

  • Records는 익명성, 불변성, 집계성을 가진 타입이며
  • 다른 컬렉션 유형(List, Set, Map 등등)과 마찬가지로 여러 개체를 하나의 개체로 묶는 타입이다.
  • 하지만 다른 컬렉션 유형과 달리 Record는 크기와 유형이 고정되어 있다.
  • Record라는 클래스가 추상클래스로 존재하지만 사용하지 않으며,
    타입 선언에는 괄호() 안에 들어간 인수 목록을 런타임에서 갖는 ‘익명’ 타입이다.
  • 또한 필드의 길이(갯수), 위치, 이름이 지정된다.
  • 그리고 코드 값과 레코드 유형은 인수 목록 및 단순화된 함수 유형 매개변수 목록과 유사하게 작성된다.

 

 

2. Pattern

패턴은 값이 내가 원하는 형식과 일치하는지 항시 확인한다. 즉, 값이 패턴과 일치하는지 확인하는 것이다.

void main() {
  final [x,y,...rest,z,_] = [1,2,3,4,5,7,8];
  print(x);
  print(y);
  print(rest);
  print(z);
}

List에서 값을 받아 x,y,z, rest 를 초기화하는 방법에 활용되었다.

_ (언더스코어) 와 같은 와일드카드로

위와같이 위치를 조정하는데 사용하거나,

아래와 같이 변수바인딩에 사용할 수 있다.

final record = (1,"abc");
  switch (record) {
    case (int _, "abc" ):
      print('첫번째가 int값 아무거나 가지면 true');
    case (2,  "abc"):
      print('First field is int and second is String.');
  }

 

패턴매칭은 Switch나 if에서 자주 활용될 수 있다.

var isPrimary = switch (color) {
    case Color.red :
    	return true;

    case Color.yellow :
	    return true;

    case Color.blue :
    	return true;

    default:
    	return false;
};

위와 같이 긴 Switch문이  Logical or 패턴으로

아래와 같이 요약될 수 있다.

var isPrimary = switch (color) {
  Color.red || Color.yellow || Color.blue => true,
  _ => false
};

언더스코프는 default 옵션처럼

예외 상황을 포함시키도록 활용한다.

 

대수적 활용

sealed class Shape {}

class Square implements Shape {
  final double length;
  Square(this.length);
}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

double calculateArea(Shape shape) => switch (shape) {
      Square(length: var l) => l * l,
      Circle(radius: var r) => math.pi * r * r
    };

sealed에 대해서는 아래에서 다루겠습니다만,

하위클래스로 상속과 구현이 가능하게하는 클래스 제한자입니다.

 

switch문에 집중해서보면 대수적으로 클래스타입에 해당 속성을 가지면

계산값을 반환하도록 대수적인 활용을 할 수 있습니다.

 

3. 클래스 제한자

기존에 클래스는 어떤 클래스로든 제한이 없이 다채롭게 사용이 가능했지만,

자유도는 그대로 높여두고 OOP적 개념을 더욱 도입하여 이펙티브하게 사용할 수 있도록 제약을 줄수있게 되었습니다.

클래스앞에 사용가능한 한정자는 아래와 같습니다.

  • abstract
  • base
  • final
  • interface
  • sealed
  • mixin

 

abstract

전체 인터페이스의 완전하고 구체적인 구현이 필요하지 않은 클래스를 정의하려면 abstract 한정자를 사용합니다 .

추상 클래스에는 종종 추상 메서드가 있습니다 .

// Library a.dart
abstract class Vehicle {
  void moveForward(int meters);
}
// Library b.dart
import 'a.dart';

// Error: Cannot be constructed
Vehicle myVehicle = Vehicle();

// Can be extended
class Car extends Vehicle {
  int passengers = 4;
  // ···
}

// Can be implemented
class MockVehicle implements Vehicle {
  @override
  void moveForward(int meters) {
    // ...
  }
}

 

 

  • 추상클래스는 구현없이 인스턴스를 만들수 없습니다.
  • Extends (상속)과 Implements(구현) 가능합니다
  • 구현시에는 추상클래스에 선언된 함수들을  오버라이드하여 구현 해주어야 합니다.

 

base

Class , Mixin의 상속만 가능한 base수정자

// Library a.dart
base class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
// Library b.dart
import 'a.dart';

// Can be constructed
Vehicle myVehicle = Vehicle();

// Can be extended
base class Car extends Vehicle {
  int passengers = 4;
  // ...
}

// ERROR: Cannot be implemented
base class MockVehicle implements Vehicle {
  @override
  void moveForward() {
    // ...
  }
}

 

  • 상속만 할수 있도록 제약하여, 부모 자식의 관계를 가집니다.
  • BASE 를 사용했다면, 상속한 모두가 같은 부모 타입에도 속하게 됩니다.

 

interface

// Library a.dart
interface class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
// Library b.dart
import 'a.dart';

// Can be constructed
Vehicle myVehicle = Vehicle();

// ERROR: Cannot be inherited
class Car extends Vehicle {
  int passengers = 4;
  // ...
}

// Can be implemented
class MockVehicle implements Vehicle {
  @override
  void moveForward(int meters) {
    // ...
  }
}

 

  • 인터페이스는 말그래도 implements (구현)해야 하는 클래스 제한자 입니다. 
  • 상속 확장은 불가능합니다.
  • 그래서 abstract 와 interface를 결합하여 상속을 막고 구현만 가능하도록 한정하여 종종 사용할 수 있습니다.

 

final

final 계층 구조를 모두 막을수 있습니다.

클래스에서 하위 유형을 만들어 낼 수 없습니다. 

상속과 구현을 모두 허용하지 않으면 하위 유형 지정이 완전히 방지하면 아래 내용을 보장합니다.

  • API에 증분 변경 사항을 안전하게 추가할 수 있습니다.
  • 타사 하위 클래스에서 덮어쓰지 않았음을 알고 인스턴스 메서드를 호출할 수 있습니다
// Library a.dart
final class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
// Library b.dart
import 'a.dart';

// Can be constructed
Vehicle myVehicle = Vehicle();

// ERROR: Cannot be inherited
class Car extends Vehicle {
  int passengers = 4;
  // ...
}

class MockVehicle implements Vehicle {
  // ERROR: Cannot be implemented
  @override
  void moveForward(int meters) {
    // ...
  }
}

 

 

sealed

알려진 enumerable (열거 가능한) 하위 유형 집합을 만들려면 sealed 한정자를 사용합니다. 

수정 sealed자는 클래스가 자체 라이브러리 외부에서 확장되거나 구현되는 것을 방지합니다. 

sealed class Vehicle {}

class Car extends Vehicle {}

class Truck implements Vehicle {}

class Bicycle extends Vehicle {}

// ERROR: Cannot be instantiated
Vehicle myVehicle = Vehicle();

// Subclasses can be instantiated
Vehicle myCar = Car();

String getVehicleSound(Vehicle vehicle) {
  // ERROR: The switch is missing the Bicycle subtype or a default case.
  return switch (vehicle) {
    Car() => 'vroom',
    Truck() => 'VROOOOMM',
  };
}

 

  • sealed는  클래스의 enum화를 가능하게 하는 키워드입니다.
  • 상속과 구현을 허용하고 자기자신은 인스턴스를 가지지 못하는게 Abstract와 같습니다.
  • 열거형으로 Switch문에 유용하게 활용되어 하위클래스 타입케이스를 철처히 검사하게 합니다.

 

Mixin

한번에 여러클래스를 상속받을 수 있는 mixin키워드

mixin, mixin class 는 다르다.

mixin은 혼자 인스턴스화 될 수 없고, 믹스인 클래스는 믹스인 가능한 클래스이다.

믹스인은 Mixin을 붙여 만들어주며, with 키워드를 통해 혼합시킬수 있습니다.

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은 혼자 인스턴스화 될 수 없고,

믹스인 클래스는 믹스인 가능한 클래스이다.

 

 

여러 클래스의 mixin 예제

mixin A {
  String getMessage() => 'A';
}

mixin B {
  String getMessage() => 'B';
}

class P {
  String getMessage() => 'P';
}

class AB extends P with A, B {}

class BA extends P with B, A {}

void main() {
  // 같은메소드를 가진 클래스를 mixin하면 
  // 마지막 클래스 기준으로 오버라이딩
  String result = '';

  AB ab = AB();
  result += ab.getMessage();
  print(result);

  BA ba = BA();
  result += ba.getMessage();
  print(result);
}