目次

クラス修飾子

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

修飾子キーワードは、クラスまたはミックスイン宣言の前に記述します。たとえば、abstract class と記述すると抽象クラスが定義されます。クラス宣言の前に記述できる修飾子の完全なセットには、次のようなものがあります。

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

base 修飾子のみがミックスイン宣言の前に記述できます。修飾子は、enumtypedefextension、または extension 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 クラスは、自身のライブラリの外部での実装を許可しません。これにより、次のことが保証されます。

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

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

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

  • クラスのインスタンスメソッドの 1 つが 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 修飾子の最も一般的な使用法は、純粋なインターフェースを定義することです。interface 修飾子と abstract 修飾子を 組み合わせると、abstract interface class になります。

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

final

#

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

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

final クラスは、同じライブラリ内で拡張または実装できます。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 修飾子を使用します。これにより、網羅的であることが静的に保証されるこれらのサブタイプに対する switch を作成できます。

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

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

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

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

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 のいずれか 1 つ。他のライブラリがクラスをサブタイプ化する際の制限を記述します。
  3. (オプション) mixin。宣言をミックスインできるかどうかを記述します。
  4. class キーワード自体。

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

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

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