클래스 제어자 혹은 수정자 (class modifier)
- 클래스 혹은 믹스인이 자체 라이브러리 내 혹은 정의된 라이브러리 외부에서 사용되는 방식을 제어한다.
- 제어자 키워드는 클래스 혹은 믹스인 선언 앞에 온다.
- 이를테면,
abstract class
는 추상 클래스를 정의한다.
- 이를테면,
class
앞에 올 수 있는 것들
abstract
base
final
interface
sealed
base
만 믹스인 선언 전에 올 수 있다. 제어자들은enum
,typedef
,extension
과 같은 다른 선언에 적용할 수 없다.클래스 제어자 사용 여부는 클래스의 의도된 용도와 클래스가 의존할 수 있어야 하는 동작을 고려해야 한다.
제어자 없음
- 어떤 라이브러리에서든 생성자나 하위 타입에 대한 권한을 제한하고 싶지 않다면, 제어자 없이
class
나mixin
을 사용하면 된다. - 기본적으로 아래와 같은 동작이 가능하다.
- 클래스의 새 인스턴스를 생성
- 새 하위 타입을 만들기 위해 클래스를 확장 (extend)
- 클래스 혹은 믹스인의 인터페이스를 구현
- 믹스인 혹은 믹스인 클래스에서 믹스
abstract
- 전체 인터페이스의 구체적인 구현이 필요 없는 클래스를 정의할 때
abstract
제어자를 사용한다. - 추상 클래스는 어떤 라이브러리에서도 생성(construct)이 불가능하다.
- 추상 클래스는 종종 추상 메서드를 갖는다.
- 추상 클래스를 인스턴스화할 수 있는 것처럼 보이게 하려면 팩토리 생성자를 정의해야 한다.
// Library a.dart
abstract class Vehicle {
void moveForward(int meters);
}
// Library b.dart
import 'a.dart';
// Error: 추상 클래스 자체를 생성하는 것은 불가능하다.
Vehicle myVehicle = Vehicle();
// 추상 클래스는 클래스 확장(extend)이 가능하다.
class Car extends Vehicle {
int passengers = 4;
// ···
}
//t
class MockVehicle implements Vehicle {
@override
void moveForward(int meters) {
// ...
}
}
base
- 클래스 혹은 믹스인 구현의 상속을 강제한다.
- 자체 라이브러리의 외부 구현을 허용하지 않는다.
- 보장되는 것들:
- base 클래스의 생성자는 클래스의 하위 타입의 인스턴스가 생성될 때마다 호출된다.
- 구현된 모든 비공개 멤버가 서브타입에 존재한다.
- 모든 서브타입이 새 멤버를 상속하므로, 베이스 클래스에서 새로 구현된 멤버는 서브타입을 손상시키지 않는다.
- 하위 타입이 이미 동일한 이름과 호환되지 않는 시그니쳐를 가진 멤버를 선언하지 않는 한 참이다.
base
클래스를 구현하거나 확장하는 모든 클래스는base
,final
,sealed
로 표시해야 한다.- 외부 클래스가
base
클래스가 보장하는 것들을 위반하는 것을 방지할 수 있다.
- 외부 클래스가
// Library a.dart
base class Vehicle {
void moveForward(int meters) {
// ...
}
}
// Library b.dart
import 'a.dart';
// 생성 가능
Vehicle myVehicle = Vehicle();
// 확장 가능
base class Car extends Vehicle {
int passengers = 4;
// ...
}
// ERROR: 구현의 상속이 강제됐기 때문에 오버라이드 할 수 없다.
base class MockVehicle implements Vehicle {
@override
void moveForward() {
// ...
}
}
interface
- 인터페이스를 정의하기 위해 사용한다.
- 외부 라이브러리는 인터페이스를 구현할 수는 있지만 확장할 수는 없다.
- 보장되는 것들:
- 클래스 인스턴스 메서드 중 하나가 다른 인스턴스 메서드를 호출하면 항상 동일한 라이브러리에서 알려진 메서드 구현을 호출한다.
- 다른 라이브러리는 인터페이스 클래스의 자체 메서드가 나중에 예상치 못하게 호출할 수 있는 메서드를 재정의할 수 없다.
- 이건 fragile base class 문제 를 줄이는데 도움이 된다.
// Library a.dart
interface class Vehicle {
void moveForward(int meters) {
// ...
}
}
// Library b.dart
import 'a.dart';
// 생성 가능
Vehicle myVehicle = Vehicle();
// ERROR: 인터페이스를 상속받을 수 없다
class Car extends Vehicle {
int passengers = 4;
// ...
}
// 인터페이스를 구현하는 것은 가능
class MockVehicle implements Vehicle {
@override
void moveForward(int meters) {
// ...
}
}
abstract interface
interface
제어자의 가장 흔한 용례다.- 순수한 인터페이스를 정의하기 위해 사용된다.
interface
와abstract
제어자를 합쳐서(Combine)abstract interface class
를 만든다.interface
클래스 같이, 구현은 가능한데, 상속은 불가능하다. 순수한 인터페이스가 된다.abstract
클래스 같이, 순수한 인터페이스는 추상적인 멤버를 가질 수 있다.
final
- 계층 구조를 닫는다.
- 현재 라이브러리 외부 클래스에서 하위 타입을 만드는 것을 방지할 수 있다.
- 상속과 구현을 막는다.
- 보장되는 것들:
- API 에 증분적인 변화가 가능하다.
- 서드파티 서브 클래스에서 덮어씌워지지 않았다는 것을 알고 인스턴스 메서드를 호출할 수 있다.
final
클래스는 같은 라이브러리 내에서 확장되거나 구현될 수 있다.final
제어자는base
의 효과를 포함한다.- 모든 하위 클래스에도
base
,final
,sealed
로 표시되어 있어야 한다.
- 모든 하위 클래스에도
// Library a.dart
final class Vehicle {
void moveForward(int meters) {
// ...
}
}
// Library b.dart
import 'a.dart';
// Can be constructed
Vehicle myVehicle = Vehicle();
// ERROR: 상속 불가
class Car extends Vehicle {
int passengers = 4;
// ...
}
class MockVehicle implements Vehicle {
// ERROR: 구현 불가
@override
void moveForward(int meters) {
// ...
}
}
sealed
- 하위타입의 enumerable 한 집합을 만들려면
sealed
제어자를 사용한다. - 정적으로 완전한 하위 유형에 대한 스위치를 만들 수 있다.
sealed
제어자는 라이브러리 외부에서 클래스가 확장되거나 구현되는 것을 막는다.sealed
클래스는 묵시적으로abstract
이다.- 생성이 불가능하다.
- factory 생성자를 가질 수 있다.
- 하위 클래스가 사용할 생성자를 만들 수 있다.
sealed
클래스의 하위 클래스는 묵시적으로 추상적이지 않다.- 같은 라이브러리에만 존재할 수 있기 때문에, 컴파일러는 어떤 직접 접근 가능한 하위타입들을 안다.
- 컴파일러는 스위치가 가능한 모든 하위 타입을 완전히 처리하지 못할 때 사용자에게 경고를 표시할 수 있다.
sealed class Vehicle {}
class Car extends Vehicle {}
class Truck implements Vehicle {}
class Bicycle extends Vehicle {}
// ERROR: 인스턴스화 불가능
Vehicle myVehicle = Vehicle();
// 서브 클래스는 인스턴스화 가능
Vehicle myCar = Car();
String getVehicleSound(Vehicle vehicle) {
// ERROR: Bicycle 서브타입에 대한 케이스를 정의하거나 디폴트 케이스가 필요함
return switch (vehicle) {
Car() => 'vroom',
Truck() => 'VROOOOMM',
};
}
exhaustive switching 을 원하지 않거나 API 를 깰 필요 없이 하위 타입을 나중에 추가할 수 있기 원한다면,
final
제어자를 사용하는 것이 좋다.
더 깊이 있는 비교를 원한다면, sealed versus final 을 읽어보면 좋다.
제어자 합성하기
- 제어자를 결합할 수 있다.
- 아래의 순서대로 사용할 수 있다.
- (Optional)
abstract
: 클래스가abstract
멤버를 포함할지, 인스턴스화를 금지할지에 대해 기술한다. - (Optional)
base
,interface
,final
,sealed
중 하나를 선택하여 다른 라이브러리에서 클래스의 하위 타입을 만드는 것을 제한할지 기술한다. - (Optional)
mixin
: 선언이 믹스인 될 수 있을지에 대해 기술한다. class
키워드 입력
- 모순되거나 중복되거나 상호 배타적이기 때문에 결합할 수 없는 제어자들도 있다.
abstract
와sealed
sealed
는 항상 묵시적으로abstract
이다.
interface
,final
,sealed
를mixin
과 함께 사용- 세 접근자는
mixing in
을 금지한다.
- 세 접근자는
반응형
'Dart' 카테고리의 다른 글
다트 (Dart) 언어의 생성자 (Constructors) 정리 (0) | 2023.10.13 |
---|---|
다트(Dart) 언어의 클래스(Class) 정리 (0) | 2023.10.12 |
다트(Dart) 언어의 확장 메서드 (Extension methods) 정리 (0) | 2023.10.10 |
다트의 오류 처리 방식 (1) | 2023.10.08 |
다트 (Dart) 변수, 상수 선언 방식 (0) | 2023.10.08 |