クラス class

クラスとオブジェクトとは何か

クラスとは「分類」の概念で、身の回りに存在する事物を抽象化して一つの型として定義したものです。例えば地球上の人間をまとめて「人間」というクラスに抽象し、BobやTomといった一人一人の具体的な人物がオブジェクトとなります。

クラスは「設計図」あるいは「テンプレート」のような役割を持ち、オブジェクトが備える構造と振る舞いを定義します。

先ほどの人間クラスを例に挙げます。人間クラスはテンプレートに相当し、人間には名前、手足などの器官が備わり、会話や思考といった動作が可能です。名前や年齢といった情報はクラスの属性、話す・考えるといった動作はクラスの振る舞いと分類されます。つまりクラスは現実世界の事物から共通の要素を抽出した雛形であり、このクラスを元にJack、Jimといった個別のオブジェクトを次々と作成できます。

クラスの属性(氏名、年齢)と振る舞い(会話、思考)は、それぞれ変数と関数で表現します。

クラスには、オブジェクトが保有すべき 属性(変数)メソッド(関数) が記述されます。

クラスはパンを焼く際に使う型に似ており、それ自体は実体のある物体ではありません。同じ型から複数の実物のパンを作り出すように、クラスから複数のオブジェクトを生成できます。

プログラミングの歴史を遡ると、初期の言語にはクラスの概念が存在しませんでした。C言語がその代表例で、関数、変数、各種構文だけで記述する手続き型プログラミングが主流でした。ソフトウェアの機能が複雑化するにつれ、手続き型のコードは重複記述が多く、コードの管理が煩雑で階層構造が曖昧になる課題が浮き彫りになりました。後に考案されたのがオブジェクト指向プログラミングで、現実世界の仕組みのようにロジックを個々のオブジェクトに抽象化する手法です。会社を例にすると、起業当初は社長が全ての業務を一人で担いますが、人員を採用した後は経理業務は財務担当、採用業務は人事担当に分担させることで業務が整理されるのと同じ理屈です。

オブジェクトはクラスの インスタンス(Instance) です。

newキーワードまたはコンストラクタを実行すると、クラスからオブジェクトが一つ生成されます。

オブジェクトは実行時に実体として存在し、データの保存やメソッドの呼び出しが可能です。

クラスとオブジェクトの関係

  • クラスは 設計図、オブジェクトは 設計図をもとに建築された実際の建物 です。
  • 一つのクラスから複数のオブジェクトを作成可能で、それぞれのオブジェクトは独立したデータを保有します。
  • 同一クラスから生成された全てのオブジェクトは、クラス内に定義された振る舞い(メソッド)を共有します。

ここからDart言語に話を移します

以下の内容を学習していきます

Dartは完全なオブジェクト指向言語で、全ての値がクラスのインスタンスとなります。Nullを除く全てのクラスは基底クラスのObjectを継承します。

DartはクラスとMixinを組み合わせて継承機能を実現します

単一継承:各クラスは親クラスを一つだけ持てますが、Mixinを活用することで複数のクラスのロジックを再利用できます

拡張メソッド:元のクラスを編集したりサブクラスを作成せずに、クラスに新しい機能を追加できます

クラス修飾子:他のライブラリが当該クラスを継承・実装できるか制御します

これよりDartのクラスについて学習を始めましょう

クラスメンバー

クラスメンバーとはメソッドと変数の総称で、クラス内部を構成する要素のことを指します。

.演算子でインスタンスメンバーにアクセスし、?.はNull安全アクセス演算子でNullポインタ例外を回避します

下記は完全なサンプルコードで、クラスの定義、クラスからのオブジェクト生成、オブジェクト名によるメンバー呼び出しの流れを示しています

// クラスを定義、型の雛形を作成する作業に相当 
class Point {
  double x, y;
  Point(this.x, this.y);

  // インスタンスメソッド:2点間の距離の二乗を計算
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return dx * dx + dy * dy;
  }
}

void main() {
  // Pointクラスからオブジェクトpを作成、オブジェクト経由で各種処理を実行
  var p = Point(2, 2);

  // インスタンス変数を参照(暗黙的getter)
  print(p.y); //2.0

  // インスタンスメソッドを呼び出す
  double dist = p.distanceTo(Point(4, 4));
  print(dist); // 8.0

  // ?. によるNull安全アクセス
  Point? nullablePoint;
  var a = nullablePoint?.y; // オブジェクトがnullの場合aにnullが代入され例外は発生しない
  print(a); // null
}Code language: JavaScript (javascript)

コンストラクタ(Constructors)

通常コンストラクタ

Dartは他のC系言語と仕様が異なり、名前付きコンストラクタを利用できます。一般的なC系言語はクラス名と同名のコンストラクタしか定義できないのに対し、Dartはクラス名と別の識別子でコンストラクタを作成可能です。

記法:クラス名() / クラス名.識別子()

newキーワードは省略可能(Dart2以降で対応)

class Point {
  double x, y; //2つの変数はクラスメンバーでクラスの属性を保持 

  // デフォルトコンストラクタ、上記xとyを初期化
  Point(this.x, this.y);

  // 名前付きコンストラクタ Point.fromJson:JSONデータからx,yを初期化
  Point.fromJson(Map<String, double> json)
      : x = json['x']!,
        y = json['y']!;

}

void main() {
  var p1 = Point(2, 2);//デフォルトコンストラクタを呼び出し2,2を渡しx=2,y=2に初期化

  var p2 = Point.fromJson({'x': 1, 'y': 2}); //fromJsonの名前付きコンストラクタにJSONを渡す

  // newの有無で動作に差異はない
  var p3 = new Point(3, 3); //同じくデフォルトコンストラクタを使用

  print("p1:(${p1.x},${p1.y}), p2:(${p2.x},${p2.y}), p3:(${p3.x},${p3.y})");
  //出力:p1:(2.0,2.0), p2:(1.0,2.0), p3:(3.0,3.0)
}Code language: JavaScript (javascript)

定数コンストラクタ const

コンストラクタにconstを付与するとコンパイル時定数オブジェクトが作成され、引数が完全に一致する定数は同一インスタンスを共有します

class ImmutablePoint {
  final double x, y;
  // 定数コンストラクタのルール:全メンバーにfinalを指定する必要がある
  const ImmutablePoint(this.x, this.y);
}

void main() {
  // 定数インスタンスの作成
  var a = const ImmutablePoint(1, 1); // 定数オブジェクト生成時にもconstが必須 
  var b = const ImmutablePoint(1, 1);
  print(identical(a, b)); // true 同一のオブジェクトを参照

  var m = const ImmutablePoint(1, 1); 
  var n = const ImmutablePoint(2, 1);
  print(identical(m, n)); // false xの値が異なるため別オブジェクト

  // constを付けない通常オブジェクトはインスタンスを共有しない
  var c = ImmutablePoint(1, 1);
  print(identical(a, c)); //false

  // constのスコープ内では内部のconstを省略可能
  const pointMap = {
    'p1': [ImmutablePoint(0, 0)],
    'p2': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)]
  };

  print(pointMap); //{p1: [Instance of 'ImmutablePoint'], p2: [Instance of 'ImmutablePoint', Instance of 'ImmutablePoint']}

  print(identical(p1, p2)); // false
  print(identical(p2, p3)); // false
  print(identical(p1, p3)); // false
}Code language: PHP (php)
定数コンストラクタは引数が全て一致した場合のみ同一インスタンスを再利用し、一つでも引数が異なれば独立したオブジェクトとなります

実行時型を取得 runtimeType

オブジェクト.runtimeTypeから実行時の型情報を取得できます。runtimeTypeによる比較より、型判定演算子isの使用を推奨

class Point {
  double x, y;
  Point(this.x, this.y);

  // インスタンスメソッド:2点間距離二乗計算
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return dx * dx + dy * dy;
  }
}

void main() {
  var p = Point(2, 3);
  print("pの型:${p.runtimeType}"); // Point

  // 推奨される安定した記述
  if (p is Point) {
    print("pはPoint型です");
  }

  // 本番環境での使用は非推奨
  if (p.runtimeType == Point) {
    print("pはPoint型です ");
  }
}Code language: JavaScript (javascript)

インスタンス変数 Instance Variables

  1. Nullable型で初期化していない変数は既定値null、非Nullable型は必ず初期化が必要
  2. 全てのインスタンス変数に暗黙的なgetterが生成され、finalではない・初期化されていないlate final変数には自動でsetterが作成されます
  3. 通常の変数初期化式ではthisを参照できません。late修飾子付き変数の初期化処理ではthisの利用が許可されます
  4. finalのインスタンス変数には一度だけ値を代入可能:宣言時・コンストラクタ引数・初期化リストのいずれかで設定

暗黙的get/set

class Point {
  double? x; // 初期値null、自動でgetter+setter生成
  double? y;
  double z = 0; // 初期値0
}

void main() {
  var point = Point();
  point.x = 4; // 暗黙的setterを呼び出し
  assert(point.x == 4); // 暗黙的getterを呼び出し
  assert(point.y == null);
  print(point.z); // 0
}
Code language: JavaScript (javascript)

初期化式におけるthis参照制限

double globalX = 1.5;
class Point {
  // 許可:外部のグローバル変数を参照、thisに依存しない
  double? x = globalX;

  // コンパイルエラー:通常の初期化処理でthisは使用不可
  // double? y = this.x;

  // 許可:late変数の初期化ではthisを参照可能
  late double? z = this.x;

  // 許可:コンストラクタ引数の糖衣構文this.xは初期化式に該当しない
  Point(this.x, this.y);
  double? y;
}

void main() {
  var p = Point(null, 5);
  print(p.z); // null
}
Code language: JavaScript (javascript)

finalインスタンス変数の3種類の初期化方法

class ProfileMark {
  // 方法1:宣言時に直接初期化
  final DateTime start = DateTime.now();
  final String name;

  // 方法2:コンストラクタ引数で代入
  ProfileMark(this.name);

  // 方法3:コンストラクタ初期化リストで代入、名前付きコンストラクタunnamedはnameに空文字を既定代入
  ProfileMark.unnamed() : name = '';
}

void main() {
  var m1 = ProfileMark("user1");
  var m2 = ProfileMark.unnamed();
  print(m1.name);
  print(m2.name);
}
Code language: PHP (php)

暗黙的インターフェース Implicit interfaces

Dartの各クラスには暗黙的なインターフェースが付属し、自身の公開された全インスタンスメンバーを含みます。

  • implements:インターフェースを実装する、インターフェースの全メソッドをオーバーライドする必要がある
  • extends:継承、親クラスの実装を再利用可能
  • アンダーバーから始まるプライベート変数_xxxはインターフェースに公開されない

1 単一インターフェースの実装

class Person {
  final String _name;
  Person(this._name);
  // インターフェース公開メソッド
  String greet(String who) =&gt; "Hello $who, I'm $_name";
}

// Personインターフェースを実装、全てのインターフェースメンバーをオーバーライド必須
class Impostor implements Person {
  String get _name =&gt; "";
  @override
  String greet(String who) =&gt; "Hi $who, guess who?";
}

String greetBob(Person p) =&gt; p.greet("Bob");

void main() {
  print(greetBob(Person("Kathy")));
  print(greetBob(Impostor()));
}
Code language: PHP (php)

サブクラスで親インターフェースのメソッドを上書きする際は@overrideアノテーションを付記します。Javaと記法が似ており、implementsキーワードを使用してインターフェースを実装します

2 複数インターフェースの実装

//抽象クラス
abstract class Comparable {
  int compareTo(Object other);
}

//抽象クラス
abstract class Location {
  double get x;
  double get y;
}

//2つのインターフェースを同時実装
class Point implements Comparable, Location {

  @override
  double x, y;

  Point(this.x, this.y);

  @override
  int compareTo(Object other) {
    if (other is Point) {
      return (x + y).compareTo(other.x + other.y);
    }
    return -1;
  }
}

void main() {
  var p = Point(2, 3);
  print(p.x);
  print(p.compareTo(Point(1, 1)));
}
Code language: PHP (php)

@override
double x, y;

このアノテーションは、この2つのプロパティが親インターフェースLocationに定義された抽象getterを実装していることを示します。

クラス静的変数 & 静的メソッド static

1. 静的変数 static(クラス変数、グローバルに唯一の実体)

  • クラス自身に属しインスタンスに属さない。遅延初期化が適用され、初回アクセス時に生成されます
  • 定数型静的変数はキャメルケース小文字で命名するのが推奨
class Queue {
  static const initialCapacity = 16; //静的定数変数
  static int count = 0; //静的変数
  Queue() {
    count++;
  }
}

void main() {
  assert(Queue.initialCapacity == 16); //アクセス時に初期化、既定値16
  Queue(); //countを1加算
  Queue();
  print(Queue.count); // 2
}
Code language: JavaScript (javascript)

2. 静的メソッド static(クラスメソッド)

  • thisを持たず、インスタンスメンバーにアクセス不可。静的変数のみ参照可能
  • クラス名.メソッド()の形式で直接呼び出す
  • コンパイル時定数として定数コンストラクタに渡すことが可能
import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  // 静的メソッド:2点間の直線距離を計算
  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var dist = Point.distanceBetween(a, b);
  print(dist); // 約2.828
}Code language: JavaScript (javascript)

静的メソッドはクラスに属するため、クラス名を経由して呼び出し、引数として2つのPointインスタンスを渡します。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です