Dart クラスとオブジェクト

クラスはオブジェクト指向プログラミングの中核となる概念であり、現実世界の事物を抽象的に記述したものです。

オブジェクトはクラスの具体的なインスタンスです。この章では、Dart におけるクラスの定義、コンストラクタ、メンバ変数とメソッド、getter/setter、静的メンバについて紹介します。


クラスの定義とインスタンス化

クラスはオブジェクトを作成するためのテンプレートであり、オブジェクトが持つ属性(データ)と振る舞い(メソッド)を定義します。

例

// User クラスを定義する
class User {
  // メンバ変数(属性)
  String name = '';
  int age = 0;

  // メンバメソッド(振る舞い)
  void introduce() {
    print('こんにちは、$name です。今年 $age 歳です。');
  }

  bool isAdult() {
    return age >= 18;
  }
}

void main() {
  // インスタンス化: new キーワードを使用(Dart 2.0+ では new は省略可能)
  var user1 = User();
  user1.name = 'サンプル';
  user1.age = 10;

  // メンバ変数とメソッドにアクセス
  print('ユーザー名: ${user1.name}');
  user1.introduce();
  print('成人かどうか: ${user1.isAdult()}');

  // 別のオブジェクトを作成(互いに独立)
  var user2 = User();
  user2.name = '太郎';
  user2.age = 20;
  user2.introduce();
}
用户名: サンプル
你好,我是 サンプル,今年 10 岁。
是否成年: false
你好,我是 小明,今年 20 岁。

Dart 2.0 以降、インスタンス化の際の new キーワードは省略可能です。コミュニティではコードを簡潔に保つために new を省略する慣習があります。本チュートリアルの以降の例ではすべて new を省略します。


コンストラクタ

コンストラクタは、オブジェクトを作成するときに自動的に呼び出される特別なメソッドで、オブジェクトの状態を初期化するために使用されます。

Dart には複数のコンストラクタ形式が用意されており、非常に柔軟です。

デフォルトコンストラクタ

コンストラクタを定義しない場合、Dart は自動的に引数のないデフォルトコンストラクタを提供します。

通常のコンストラクタ

例

class Student {
  String name;
  int age;

  // 通常のコンストラクタ: クラス名と同じ
  Student(String name, int age) {
    this.name = name;
    this.age = age;
  }

  void printInfo() {
    print('学生: $name, 年齢: $age');
  }
}

void main() {
  var s1 = Student('サンプル', 10);
  var s2 = Student('太郎', 15);
  s1.printInfo();
  s2.printInfo();
}
学生: サンプル, 年龄: 10
学生: 小明, 年龄: 15

シンタックスシュガーコンストラクタ

コンストラクタの引数をメンバ変数に直接代入する場合、Dart は簡潔な省略構文を提供します。

例

class Student {
  String name;
  int age;

  // シンタックスシュガー: this.引数名 で引数を同名のメンバ変数に自動的に代入する
  // この方法は手動で this.name = name と書くよりも簡潔
  Student(this.name, this.age);

  void printInfo() {
    print('サンプル 学生: $name, 年齢: $age');
  }
}

void main() {
  var s = Student('サンプル', 10);
  s.printInfo();
}
サンプル 学生: サンプル, 年龄: 10

名前付きコンストラクタ

1 つのクラスに複数の名前付きコンストラクタを持たせ、異なる初期化ロジックに対応できます。

例

class Point {
  double x;
  double y;

  // デフォルトコンストラクタ
  Point(this.x, this.y);

  // 名前付きコンストラクタ: 原点を作成
  Point.origin()
      : x = 0,
        y = 0;

  // 名前付きコンストラクタ: 単一の値から作成(x と y が同じ)
  Point.diagonal(double value)
      : x = value,
        y = value;

  @override
  String toString() => 'Point($x, $y)';
}

void main() {
  var p1 = Point(3, 4);
  var p2 = Point.origin();
  var p3 = Point.diagonal(5);

  print('サンプル 座標: $p1, $p2, $p3');
}
サンプル 坐标: Point(3.0, 4.0), Point(0.0, 0.0), Point(5.0, 5.0)

初期化リスト

初期化リストはコンストラクタ本体が実行される前に実行され、final フィールドの初期化や引数の検証に使用されます。

例

class Person {
  final String name;    // final フィールドはコンストラクタ内で初期化する必要がある
  final int birthYear;
  final int age;

  // 初期化リスト: コロンの後、関数本体の前
  // 初期化リストはコンストラクタ本体より先に実行される
  Person(this.name, this.birthYear)
      : age = 2026 - birthYear,          // 計算プロパティ
        assert(birthYear > 1900, '誕生年が不正です');  // アサーションによる検証

  void printInfo() {
    print('サンプル ユーザー: $name, 誕生年: $birthYear, 年齢: $age');
  }
}

void main() {
  var p = Person('サンプル', 2016);
  p.printInfo();

  // 次の行は assert 失敗を引き起こします(開発モード時)
  // var p2 = Person('error', 1800);
}
サンプル 用户: サンプル, 出生: 2016, 年龄: 10

assert は開発モード(debug mode)でのみ有効で、本番環境(release mode)では無視されます。開発段階で論理エラーを早期に発見するために使用されます。


メンバ変数とメソッド

メンバ変数はオブジェクトの状態を格納し、メンバメソッドはオブジェクトの振る舞いを定義します。

例

class BankAccount {
  // プライベートメンバ: アンダースコアで始まり、このファイル内でのみアクセス可能
  String _accountNumber;
  double _balance = 0;

  // パブリックメンバ
  String ownerName;

  BankAccount(this.ownerName, this._accountNumber);

  // パブリックメソッド
  void deposit(double amount) {
    if (amount > 0) {
      _balance += amount;
      print('¥$amount を預け入れ、現在の残高: ¥$_balance');
    }
  }

  bool withdraw(double amount) {
    if (amount > 0 && amount <= _balance) {
      _balance -= amount;
      print('¥$amount を引き出し、現在の残高: ¥$_balance');
      return true;
    }
    print('残高不足のため、引き出しに失敗しました');
    return false;
  }

  // プライベートメソッド: クラス内部でのみ使用
  void _logTransaction(String type, double amount) {
    print('[内部ログ] $type: ¥$amount');
  }
}

void main() {
  var account = BankAccount('サンプル', '6222-0000-1234');

  account.deposit(1000);
  account.withdraw(300);
  account.withdraw(800);  // 残高不足

  // 外部からプライベートメンバにはアクセスできない
  // print(account._balance); // エラー: _balance はプライベート
}
存入 ¥1000,当前余额: ¥1000
取出 ¥300,当前余额: ¥700
余额不足,取款失败

Getter と Setter

Getter と Setter は、メンバ変数への読み書き操作を行う特別なメソッドです。

これらを使用すると、プロパティにアクセスする際に追加のロジックを実行できる一方、プロパティアクセスの簡潔な構文を維持できます。

例

class Circle {
  double _radius;  // プライベート変数。半径を格納する

  Circle(this._radius);

  // Getter: 直径を取得(計算プロパティ)
  double get diameter => _radius * 2;

  // Getter: 面積を取得
  double get area => 3.14159 * _radius * _radius;

  // Getter と Setter を組み合わせて、外部に radius プロパティを公開する
  double get radius => _radius;

  set radius(double value) {
    // Setter には検証ロジックを追加できる
    if (value <= 0) {
      throw ArgumentError('半径は 0 より大きくなければなりません');
    }
    _radius = value;
  }

  // 読み取り専用の Getter: 対応する Setter なし
  String get info => '円(半径: $_radius)';
}

void main() {
  var circle = Circle(5);

  // Getter を使用(通常のプロパティにアクセスするかのように)
  print('サンプル 円: ${circle.info}');
  print('直径: ${circle.diameter}');
  print('面積: ${circle.area.toStringAsFixed(2)}');

  // Setter を使用
  circle.radius = 10;
  print('変更後の直径: ${circle.diameter}');

  // 不正な値を設定しようとすると例外がスローされる
  // circle.radius = -1; // ArgumentError がスローされる
}
サンプル 圆形: 圆(半径: 5.0)
直径: 10.0
面积: 78.54
修改后直径: 20.0

Getter と Setter の利点は、呼び出し側が radius の背後に単純なフィールドがあるのか計算プロパティがあるのかを知る必要がないことです。将来 radius に検証ロジックを追加する必要が生じた場合、フィールドを getter/setter に変更するだけで、呼び出し側のコードを変更する必要はありません。これが「カプセル化」の利点です。


静的メンバ

静的メンバはクラス自体に属し、特定のインスタンスには属しません。

すべてのインスタンスが同じ静的変数を共有します。静的メソッドはオブジェクトを作成しなくても呼び出せます。

例

class Counter {
  // 静的変数: すべてのインスタンスで共有される
  static int totalCount = 0;

  // インスタンス変数: インスタンスごとに独立
  String label;

  Counter(this.label) {
    // インスタンスが作成されるたびに、静的カウンタを 1 増やす
    totalCount++;
  }

  // 静的メソッド: クラス名から呼び出す
  static void printTotal() {
    print('サンプル 総インスタンス数: $totalCount');
  }

  // 静的メソッドはユーティリティ関数によく使用される
  static bool isValidLabel(String label) {
    return label.isNotEmpty && label.length <= 20;
  }
}

void main() {
  // 静的メソッド: インスタンスを作成せずにクラス名から直接呼び出す
  print('ラベルが有効かどうか: ${Counter.isValidLabel('test')}');

  var c1 = Counter('カウンターA');
  Counter.printTotal();  // 1

  var c2 = Counter('カウンターB');
  var c3 = Counter('カウンターC');
  Counter.printTotal();  // 3

  // 静的変数: クラス名からアクセスする
  print('最終カウント: ${Counter.totalCount}');
}
标签是否有效: true
サンプル 总实例数: 1
サンプル 总实例数: 3
最终计数: 3

静的メソッド内ではインスタンスメンバにアクセスできません(this がないため)。静的メソッドはユーティリティ関数やファクトリロジックに適していますが、使いすぎには注意が必要です。静的メソッドはサブクラスでオーバーライドできないため、過度に使用するとコードのテスト容易性や柔軟性が低下します。

その他の拡張