メインコンテンツにスキップ

クラス修飾子

クラス修飾子は、クラスまたはミックスインが、自身のライブラリ内および定義されているライブラリの外部からどのように使用できるかを制御します。

修飾子は、クラスまたはミックスイン宣言の前に記述します。たとえば、abstract class と書くと抽象クラスが定義されます。クラス宣言の前に記述できる修飾子の完全なセットには、以下が含まれます。

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

ミックスイン宣言の前に記述できるのは base 修飾子のみです。これらの修飾子は、enumtypedefextensionextension type などの他の宣言には適用されません。

クラス修飾子を使用するかどうかを決定する際は、クラスの意図された用途と、クラスが依存できる必要のある動作を考慮してください。

修飾子なし

#

任意のライブラリから無制限の構築またはサブタイプ化を許可するには、修飾子なしの class または mixin 宣言を使用します。デフォルトでは、次のことが可能です。

  • クラスの新しいインスタンスを構築する。
  • 新しいサブタイプを作成するためにクラスを拡張する。
  • クラスまたはミックスインのインターフェイスを実装する。
  • ミックスインまたはミックスインクラスをミックスインする。

abstract

#

インターフェイス全体を完全に具体的に実装する必要がないクラスを定義するには、abstract 修飾子を使用します。

抽象クラスは、自身のライブラリまたは外部ライブラリのいずれからも構築できません。抽象クラスには、多くの場合抽象メソッドがあります。

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

// Error: Can't be constructed.
Vehicle myVehicle = Vehicle();

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

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

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

抽象クラスをインスタンス化可能に見せたい場合は、ファクトリコンストラクタを定義します。

base

#

クラスまたはミックスインの実装の継承を強制するには、base 修飾子を使用します。基底クラスは、自身のライブラリ外での実装を許可しません。これにより、次のことが保証されます。

  • サブタイプのインスタンスが作成されるたびに、基底クラスのコンストラクタが呼び出される。
  • 実装されたすべてのプライベートメンバーがサブタイプに存在する。
  • base クラスの新しい実装メンバーは、すべてのサブタイプが新しいメンバーを継承するため、サブタイプを壊しません。
    • これは、サブタイプが既に同じ名前で互換性のないシグネチャを持つメンバーを宣言している場合を除きます。

基底クラスを実装または拡張するクラスは、basefinal、または sealed とマークする必要があります。これにより、外部ライブラリが基底クラスの保証を壊すことが防がれます。

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

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

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

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

interface

#

インターフェイスを定義するには、interface 修飾子を使用します。インターフェイスの定義ライブラリ以外のライブラリは、インターフェイスを実装できますが、拡張することはできません。これにより、次のことが保証されます。

  • クラスのインスタンスメソッドのいずれかが、this 上の別のインスタンスメソッドを呼び出す場合、常に同じライブラリからの既知の実装が呼び出されます。
  • 他のライブラリは、インターフェイスクラス自身のメソッドが後で予期しない方法で呼び出す可能性のあるメソッドをオーバーライドできません。これにより、壊れやすい基底クラスの問題が軽減されます。
a.dart
dart
interface class Vehicle {
  void moveForward(int meters) {
    // ...
  }
}
b.dart
dart
import 'a.dart';

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

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

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

abstract interface

#

interface 修飾子の最も一般的な用途は、純粋なインターフェイスを定義することです。interfaceabstract 修飾子を組み合わせて abstract interface class を作成します。

interface クラスと同様に、他のライブラリは純粋なインターフェイスを実装できますが、継承することはできません。abstract クラスと同様に、純粋なインターフェイスは抽象メンバーを持つことができます。

final

#

型階層を閉じるには、final 修飾子を使用します。これにより、現在のライブラリ外からのサブタイピングが防止されます。継承と実装の両方を禁止することで、サブタイピングが完全に防止されます。これにより、次のことが保証されます。

  • API に段階的な変更を安全に追加できます。
  • サードパーティのサブクラスでオーバーライドされていないことを認識して、インスタンスメソッドを呼び出すことができます。

最終クラスは、同じライブラリ内で拡張または実装できます。final 修飾子は base の効果を包含するため、すべてのサブクラスも basefinal、または sealed とマークする必要があります。

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

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

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

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

sealed

#

既知の列挙可能なサブタイプのセットを作成するには、sealed 修飾子を使用します。これにより、静的に網羅的であることが保証される、それらのサブタイプに対するスイッチを作成できます。

sealed 修飾子は、クラスが自身のライブラリ外で拡張または実装されることを防ぎます。密封クラスは暗黙的に抽象です。

  • それ自体は構築できません。
  • それらはファクトリコンストラクタを持つことができます。
  • サブクラスが使用するためのコンストラクタを定義できます。

ただし、密封クラスのサブクラスは暗黙的に抽象ではありません。

コンパイラは、直接のサブタイプの可能性のあるすべてを認識しています。それらは同じライブラリにしか存在できないためです。これにより、コンパイラは、スイッチがケースで可能なすべてのサブタイプを網羅的に処理していない場合に警告を出すことができます。

dart
sealed class Vehicle {}

class Car extends Vehicle {}

class Truck implements Vehicle {}

class Bicycle extends Vehicle {}

// ERROR: Can't 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',
  };
}

網羅的なスイッチを望まない場合、または後でサブタイプを追加しても API を壊したくない場合は、final 修飾子を使用します。より詳細な比較については、sealedfinal をお読みください。

修飾子の組み合わせ

#

レイヤード制限のために一部の修飾子を組み合わせることができます。クラス宣言は、順序で次のようになります。

  1. (オプション) abstract。クラスが抽象メンバーを含むことができるかどうかを記述し、インスタンス化を防ぎます。
  2. (オプション) baseinterfacefinal、または sealed のいずれか。クラスをサブタイプ化する他のライブラリに対する制限を記述します。
  3. (オプション) mixin。宣言をミックスインできるかどうかを記述します。
  4. class キーワード自体。

矛盾、冗長、またはその他の相互排他的であるため、一部の修飾子を組み合わせることはできません。

  • abstractsealed。 a密封クラスは暗黙的に抽象です。
  • interfacefinal、または sealedmixin。これらのアクセス修飾子はミックスインを防ぎます。

クラス修飾子の組み合わせに関するさらなるガイダンスについては、クラス修飾子リファレンスを確認してください。