티스토리 뷰

반응형

클래스 수정자는 클래스나 믹스인이 어떻게 사용될 수 있는지를 제어하며, 이는 해당 라이브러리 내부와 라이브러리 외부에서 모두 적용된다.

 

수정자 키워드는 클래스나 믹스인 선언 전에 사용된다. 예를 들어, 추상 클래스를 정의하려면 abstract class가 된다. 클래스 선언 전에 나타날 수 있는 수정자의 전체 목록은 다음과 같다.

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

이 수정자들은 enum, typedef, extension 또는 extension type과 같은 다른 선언에는 사용할 수 없다.

 

클래스 수정자를 사용할지 여부를 결정할 때는 클래스의 의도된 사용 방법과 클래스가 의존해야 하는 동작을 고려하자.

 

 

 

수정자 없음

수정자가 없으면, 어떤 라이브러리에서든 생성할 수 있고 서브타입을 만들 수 있다.

 

기본적으로 다음을 할 수 있다.

  • 클래스의 새 인스턴스를 생성
  • 새로운 서브 타입을 만들기 위해 클래스를 확장
  • 클래스나 믹스인의 인터페이스를 구현
  • 믹스인이나 믹스인 클래스를 혼합

 

 

 

abstract

클래스에 구체적인 구현을 하지 않고, 서브 클래스에서 구현을 하게 하려면 abstract 수정자를 사용하자. 이를 추상 클래스라고 한다.

 

추상 클래스는 인스턴스를 생성할 수 없다. 추상 클래스는 추상 메서드를 가지고 있다.

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

// 오류: 생성할 수 없습니다.
Vehicle myVehicle = Vehicle();

// 확장할 수 있습니다.
class Car extends Vehicle {
  int passengers = 4;

  @override
  void moveForward(int meters) {
    // ...
  }
}

// 구현할 수 있습니다.
class MockVehicle implements Vehicle {
  @override
  void moveForward(int meters) {
    // ...
  }
}

 

추상 클래스가 인스턴스화 가능한 것처럼 보이기를 원하면 팩토리 생성자로 정의하세요.

 

 

 

base

클래스나 믹스인의 구현을 상속으로 강제하려면 base 수정자를 사용하자. base 클래스는 자신의 라이브러리 외부에서 구현을 금지한다. 이는 다음을 보장한다.

  • 서브 타입의 인스턴스가 생성될 때 base 클래스 생성자가 호출된다.
  • 모든 private 멤버가 서브 타입에 존재한다.
  • base 클래스에서 새로운 구현 멤버가 추가되어도 서브 타입이 깨지지 않는다. 이는 서브 타입이 동일한 멤버가 이미 선언되어 있지 않는 한 유효하다.
  • base 클래스를 확장하는 모든 클래스는 base, final, sealed로 표시해야 한다. 이는 외부 라이브러리가 base 클래스의 보장을 깨뜨리는 것을 방지한다.
// a.dart
base class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}

// b.dart
import 'a.dart';

// 생성할 수 있습니다.
Vehicle myVehicle = Vehicle();

// 확장할 수 있습니다.
base class Car extends Vehicle {
  int passengers = 4;
  // ...
}

// 오류: 구현할 수 없습니다.
base class MockVehicle implements Vehicle {
  @override
  void moveForward() {
    // ...
  }
}

 

 

 

interface

인터페이스를 정의하려면 interface 수정자를 사용하자. 인터페이스는 외부 라이브러리에서도 구현할 수 있지만 확장할 수는 없다. 이는 다음을 보장한다.

  • 클래스의 인스턴스 메서드 중 하나가 this를 통해 다른 인스턴스 메서드를 호출할 때, 항상 동일한 라이브러리의 알려진 구현을 호출한다.
  • 다른 라이브러리가 인터페이스 클래스의 메서드를 예상치 못한 방식으로 오버라이드할 수 없다. 이는 취약한 기반 클래스 문제를 줄인다.
// a.dart
interface class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
// b.dart
import 'a.dart';

// 생성할 수 있습니다.
Vehicle myVehicle = Vehicle();

// 오류: 상속할 수 없습니다.
class Car extends Vehicle {
  int passengers = 4;
  // ...
}

// 구현할 수 있습니다.
class MockVehicle implements Vehicle {
  @override
  void moveForward(int meters) {
    // ...
  }
}

 

 

 

abstract interface

인터페이스 수정자의 가장 일반적인 사용은 순수 인터페이스를 정의하는 것이다. 이것은 abstract interface로 구현할 수 있다.

 

abstract interface는 interface class처럼 구현할 수 있지만, 상속할 수는 없다. 그리고 추상 클래스처럼 추상 멤버만 가질 수 있다.

 

 

 

final

타입을 상속할 수 없고 구현할 수 없게 하려면 final 수정자를 사용하자. 이는 현재 라이브러리 외부에서 클래스로부터 서브타이핑을 방지한다. 이는 다음을 보장한다.

  • API에 점진적인 변경 사항을 안전하게 추가할 수 있다.
  • 외부에서 해당 API를 사용 시, 외부에서 재정의되지 않음을 보장한다.

 

파이널 클래스는 동일한 라이브러리 내에서 확장되거나 구현될 수 있다. final 수정자는 base의 효과를 포함하므로 모든 서브클래스는 base, final 또는 sealed로 표시해야 한다.

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

// 생성할 수 있습니다.
Vehicle myVehicle = Vehicle();

// 오류: 상속할 수 없습니다.
class Car extends Vehicle {
  int passengers = 4;
  // ...
}

class MockVehicle implements Vehicle {
  // 오류: 구현할 수 없습니다.
  @override
  void moveForward(int meters) {
    // ...
  }
}

 

 

 

sealed

알려진 열거형 서브타입 세트를 만들려면 sealed 수정자를 사용하자. 이는 해당 서브타입에 대한 switch를 만들면 모든 서브타입이 처리될 것을 보장한다. 모든 서브타입을 처리하지 못할 경우 컴파일러에서 경고해 준다.

 

sealed 수정자는 클래스가 자신의 라이브러리 외부에서 확장되거나 구현되지 않도록 한다. sealed 클래스는 암묵적으로 추상적이다. 

  • 자체적으로 생성될 수 없다.
  • 팩토리 생성자를 가질 수 있다.
  • 서브클래스에서 사용할 생성자를 정의할 수 있다.

컴파일러는 모든 서브 타입을 알고 있는데, 이는 동일한 라이브러리 내에서만 존재할 수 있기 때문이다.

sealed class Vehicle {}

class Car extends Vehicle {}

class Truck implements Vehicle {}

class Bicycle extends Vehicle {}

// 오류: 인스턴스화할 수 없습니다.
Vehicle myVehicle = Vehicle();

// 서브클래스는 인스턴스화할 수 있습니다.
Vehicle myCar = Car();

String getVehicleSound(Vehicle vehicle) {
  // 오류: 스위치가 Bicycle 서브타입이나 기본 케이스를 처리하지 않았습니다.
  return switch (vehicle) {
    Car() => 'vroom',
    Truck() => 'VROOOOMM',
  };
}

 

 

 

수정자 결합

일부 수정자는 계층화된 제한을 위해 결합할 수 있다. 클래스 선언은 다음 순서로 될 수 있다.

  • (선택 사항) abstract, 추상 멤버를 포함할 수 있고 인스턴스화가 불가능함을 나타낸다.
  • (선택 사항) base, interface, final, sealed 중 하나, 다른 라이브러리에서 클래스의 서브타이핑을 제한하는 것을 나타낸다.
  • (선택 사항) mixin, 선언이 혼합될 수 있는지를 설명한다.
  • 클래스 키워드

일부 수정자는 모순되거나, 중복되거나, 상호 배타적이기 때문에 결합할 수 없다.

  • abstract와 sealed - sealed 클래스는 암묵적으로 추상적이다.
  • interface, final, sealed, mixin
반응형
댓글
공지사항