Dart公式ドキュメントを読んでみよう! Part 4

カズキ
仕事と暮らし

前回の記事ではDart公式ドキュメントを参考に、関数の基本的な事項とパラメーターについてご紹介しました。今回はDartのクラスの基本事項と、コンストラクタについてご紹介いたします。

記事内で参照するページのご紹介(再掲)

・Dartのドキュメント(https://dart.dev/guides/language/language-tour
–> 内容はこのページに沿っています。

Dart Pad(https://dartpad.dev/
–> Dartのコードを試せるplaygroundです。コードをすぐに試せます。

Dartにおけるクラス

Dartでは、Null を除く全てのクラスは Object クラスを継承しています。
クラスは以下のように宣言します。(以下では Person という名前のクラスを宣言しています。)

class Person {
  // インスタンス変数やメソッド、コンストラクタを記述
}

クラスメンバの追加

インスタンス変数・メソッド・コンストラクタを追加したものが以下です。

class Person {
  // インスタンス変数 
  final String firstName;
  final String lastName;
  int age;
  
  // メソッド
  String getFullName() {
    return '$lastName $firstName';
  }

  // コンストラクタ
  Person(this.firstName, this.lastName, this.age);
}

コンストラクタについて

Dartではいくつかの種類のコンストラクタが用意されています。
以下では、上で作成した Person クラスを例に挙げながら、1つずつ理解していきたいと思います。

最も基本的なコンストラクタ

最も基本的なコンストラクタは以下のようなものです。

class Person {
  // インスタンス変数 
  String firstName = '';
  String lastName = '';
  int age = 30;
 
  // 最も基本的なコンストラクタ
  Person(String firstName, String lastName, int age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
}

クラス名と同じ名前のメソッドを追加すると、これがコンストラクタとなります。

仮パラメータの初期化

上記のようなパターンを簡単にするため、以下のようなコンストラクタが用意されています。
実際には、こちらを利用することになるかと思います。

この方法によって、パラメータに初期値を明示的に記述する必要がなく、コンストラクタ自体の記述も簡単になります。(最初の例でもこの方法を使用しています。)

class Person {
  // インスタンス変数 
  final String firstName;
  final String lastName;
  int age;
 

  // コンストラクタ
  Person(this.firstName, this.lastName, this.age);
}


// インスタンス化するとき
Person('Recus', 'Groove', 30);

また、次のように {} を記述することで、インスタンス化する際にインスタンス変数名を明示することができます。パラメータが多い場合などに便利です。

class Person {
  // インスタンス変数 
  final String firstName;
  final String lastName;
  int age;
 

  // コンストラクタ
  Person({
    required this.firstName,
    required this.lastName,
    required this.age,
  });
}


// インスタンス化するとき
Person(firstName: 'Recus', lastName: 'Groove', age: 15);

名前付きコンストラクタ(Named constructors)

これは、複数ケースに対応したインスタンス化の方法を提供するものです。
以下の例では、「firstName: Recus, lastName: Groove, age: 15」 の Person オブジェクトを生成するための名前付きコンストラクタを追加しています。

class Person {
  // インスタンス変数 
  final String firstName;
  final String lastName;
  int age;
 

  // コンストラクタ
  Person({
    required this.firstName,
    required this.lastName,
    required this.age,
  });
  
  Person.recusGroove() : firstName = 'Recus', lastName = 'Groove', age = 15;

  // インスタンス化するとき
  // firstName = 'Recus', lastName = 'Groove', age = 15 となる
  Person.recusGroove();
}

ここで、() : firstName = …… という部分が出てきましたが、これは Initializer list と呼ばれるものです。

Initializer list について

Initializer list を利用すると、コンストラクタを呼び出す前に各フィールドを初期化することができます。以下では、公式ドキュメント にも掲載されている Point クラスを参考にしながら詳しくみていきます。

class Point {
  final double x;
  final double y;

  Point(this.x, this.y);
}

まずは、次のように、パラメータを Map で指定できるコンストラクタ fromJson() を追加してみます。

class Point {
  final double x;
  final double y;
  
  Point(this.x, this.y);
  
  
  // コンストラクタ fromJson を 追加
  Point.fromJson(Map<String, double> json)
    : x = json['x']!,
      y = json['y']! {
    print('In Point.fromJson(): ($x, $y)');
  }
}

このコンストラクタを利用すると、まず x = json[‘x’], y = json[‘y’] が評価されて各値が取り出されます。次にコンストラクタが呼び出されて、 print() により出力されます。

  Point.fromJson({
    'x': 20,
    'y': 50
  });

// --> In Point.fromJson(): (20, 50)

次の使用例は、初期化前に asset を使用することで、渡された値が期待する条件を満たしているかを、コンストラクタ実行前に確認するものです。

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


  // コンストラクタを呼び出す前に assert で x >= 0 を確認する
  Point.withAssert(this.x, this.y) : assert(x >= 0) {
    print('In Point.withAssert(): ($x, $y)');
  }
}

この場合、次のような実行結果になります。(DartPadで実行)

// x >=0 を満たす場合
Point.withAssert(200, 10);  // --> In Point.withAssert(): (200, 10)


// x >= 0 を満たさない場合
Point.withAssert(-10, 10);  // --> Uncaught Error: Assertion failed

次は、他のフィールドの値に依存して値が決まるフィールドがあるとき、Initializer list を利用して計算させる例です。
Point クラスに distanceFromOrigin フィールドを追加しています。

class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;  // フィールド追加
  
  // x, y を用いて、 原点(0, 0) からの直線距離を計算して、
  // distanceFromOrigin フィールドに割り当てる
  Point(this.x, this.y)
      : distanceFromOrigin = sqrt(x * x + y * y) {
        print('In Point (x, y, distanceFromOrigin) = ($x, $y, $distanceFromOrigin)');
      }
}

実行結果は以下のようになります。

Point(3, 4);  // --> In Point (x, y, distanceFromOrigin) = (3, 4, 5)

最後に、親クラスのコンストラクタを呼び出す例です。
このパターンは特によく見かけるかと思います。

下記は、Point クラスを継承した、x = y となる点のクラスです。

class IsoscelesPoint extends Point{
  final double point;
  
  IsoscelesPoint(this.point) : super(point, point);
}

実行すると次のようになります。

  IsoscelesPoint(1);
// --> In Point (x, y, distanceFromOrigin) = (1, 1, 1.4142135623730951)

リダイレクトコンストラクタ

これは、他のコンストラクタを呼び出すコンストラクタです。
名前付きコンストラクタと同様に、特別なパターンのコンストラクタを用意しておくことができます。
以下では、再び Person クラスに戻って例を挙げます。

class Person {
  // インスタンス変数 
  final String firstName;
  final String lastName;
  int age;
  String address;  // 住所フィールドを追加
 

  // コンストラクタ
  Person(this.firstName, this.lastName, this.age, this.address);


  // リダイレクトコンストラクタ
  Person.tokyo(String firstName, String lastName, int age)
    : this(firstName, lastName, age, 'Tokyo');
}


// インスタンス化するとき
Person.tokyo('Recus', 'Groove', 15);  // --> address = 'Tokyo' になる

定数コンストラクタ

生成するオブジェクトをコンパイル時定数( const )としたい場合に使用します。
コンストラクタに const をつければOKです。

class Person {
  // インスタンス変数 
  final String firstName;
  final String lastName;
  final int age;
  final String address;

  // 定数コンストラクタを利用した定数オブジェクト
  static const recusGroove = Person('Recus', 'Groove', 15, 'Tokyo');
    
  // 定数コンストラクタ
  const Person(this.firstName, this.lastName, this.age, this.address);
}

また、注意として、インスタンス化する際にconst キーワードをつけていない場合、同じインスタンスが返されるわけではありません。以下に例を示します。

class Person {
  // インスタンス変数 
  final String firstName;
  final String lastName;
  
  // 定数コンストラクタ
  const Person(this.firstName, this.lastName);
}

上記の定数コンストラクタが実装されたクラスで、以下のように identical により、インスタンスが等しいことを確認してみます。

  final p1 = const Person('Recus', 'Groove');
  final p2 = const Person('Recus', 'Groove');
  
  print(identical(p1, p2));  //--> true

次に、全く同じクラスで、インスタンス化の際に const を除いてみます。

  final p1 = Person('Recus', 'Groove');
  final p2 = Person('Recus', 'Groove');
  
  print(identical(p1, p2));  // --> false

Private コンストラクタ

_ をコンストラクタ名の前につけることで、private なコンストラクタを用意して(また、これ以外のコンストラクタを用意しないようにして)外部からのインスタンス生成をさせないようにするものです。

class HttpStatusCode {
  // private なコンストラクタのみを用意することで、
  // 外部からインスタンスを生成できないようにする
  HttpStatusCode._();
  
  static const success = 200;
  static const notFound = 404;
}

上記クラスでは、コンストラクタが _() のみなので、外部からこのクラスをインスタンス化することができません。
static なメンバ変数やメソッドにアクセスすることを目的としたクラスで利用します。

ファクトリコンストラクタ

コンストラクタに独自のロジックを持たせることができるものです。
コンストラクタに factory とつけることで実装できます。
シングルトンパターンなど、常に新しいインスタンスを生成するとは限らない(特に、複数のインスタンスを生成することを許さない)場合に利用します。

class Person {
  // インスタンス変数 
  final String firstName;
  final String lastName;
  
  static Person? _instance;
  
  
  // ファクトリコンストラクタ
  factory Person(String firstName, String lastName) {
    // _instance が null の場合は新しいインスタンスを生成する 
    // 既に作成されている場合は何もしない
    _instance ??= Person._internal(firstName, lastName);
    
    return _instance!;
  }

  
  // クラス内部からしか呼び出せないコンストラクタ
  Person._internal(this.firstName, this.lastName);
}

一度インスタンスが生成されると、それ以降は同じインスタンスを返し続けるため、以下のようになります。

  final p1 = Person('Recus', 'Groove');
  final p2 = Person('Dart', 'Flutter');
  
  print(p1.firstName);  // --> Recus
  print(p2.firstName);  // --> Recus
  print(p1 == p2);  // --> true

まとめ

今回はコンストラクタに焦点を当ててご紹介いたしました。
豊富な種類のコンストラクタが利用できて便利な分、たくさん理解しないといけないので大変ですが各々のコンストラクタのユースケースやコンセプトも理解することで、しっかり理解して活用していきたいですね。


次回は列挙型(enum)に焦点をあててご紹介できればと思います。

カズキ

山口県生まれ、山口県育ち。超インドアなので外出することは少なめですが、Webを身近に感じていただける技術トピックなどを中心にご紹介できればと思っています。よろしくお願いいたします!

Share

Other Blogs

REC.

  1. HOME
  2. BLOG
  3. 仕事と暮らし
  4. Dart公式ドキュメントを読んでみよう! Part 4

CONTACT US