本ページはDartについて学習した内容を記録したものです。

ネームド・コンストラクター(Named Constructor)

前回はクラスとコンストラクターの基本的な内容を確認した。

今回は少し違う形のコンストラクターを確認してみる。

今までは単純にクラスの名前と同じメソッドでした作成できなかったが、Dartではネームド・コンストラクターというのを使える。

ネームド・コンストラクターは通常のコンストラクターと違う形でインスタンスー生成(初期化)したい時に使える。

まずは基本的なクラスとコンストラクターを振り替えてみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main() {
  Point2D pt = Point2D(x: 10, y: 20);
  print(pt.getStringValue());
}

class Point2D {
  int x;
  int y;
  Point2D({
    required this.x,
    required this.y,
  });

  String getStringValue() {
    return '[$x, $y]';
  }
}
1
[10, 20]

今回はある座標の点を表すPoint2Dというクラスを定義して座標のx、yをもとにオブジェクトを生成して座標を画面に出力してみた。

クラスがシンプルなのであまり必要性を感じないけど、リストで初期化したいが、元々のコンストラクターもそのまま使いたい。

その時には、ネームド・コンストラクターがある。

ネームド・コンストラクターはクラス名の後ろに該当メソッドの名前を入力して定義する。

まずはコードをみてみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main() {
  Point2D pt = Point2D(x: 10, y: 20);
  print(pt.getStringValue());
  Point2D pt2 = Point2D.fromList([-10, -15]);
  print(pt2.getStringValue());
}

class Point2D {
  int x;
  int y;
  Point2D({
    required this.x,
    required this.y,
  });

  Point2D.fromList(List<int> values)
      : x = values[0],
        y = values[1];

  String getStringValue() {
    return '[$x, $y]';
  }
}
1
2
[10, 20]
[-10, -15]

fromListというネームド・コンストラクターを作成し、パラメータでは座標の値をリストで受け取るように定義している。

生成する際にはfromListというメソッドを使ってリストを渡して生成する。

イミュータブル(Immutable)

イミュータブルとは不変を意味する言葉であるが、最近よく聞こえるようになった気がする。

どんなものかみてみよう。上ではPoint2Dクラスを定義したが、このクラスに固有の名前も持たせるようにしてみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void main() {
  Point2D pt = Point2D(x: 10, y: 20);
  print(pt.getStringValue());
}

class Point2D {
  int x;
  int y;
  String name;
  Point2D({
    required this.x,
    required this.y,
    this.name = 'First Point',
  });

  String getStringValue() {
    return '$name: [$x, $y]';
  }
}
1
First Point: [10, 20]

名前のメンバー変数を作成してコンストラクターにはデフォルト値を設定している。

生成した後、名前は変えられる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void main() {
  Point2D pt = Point2D(x: 10, y: 20);
  pt.name = 'What\'s Up!';
  print(pt.getStringValue());
}

class Point2D {
  int x;
  int y;
  String name;
  Point2D({
    required this.x,
    required this.y,
    this.name = 'First Point',
  });

  String getStringValue() {
    return '$name: [$x, $y]';
  }
}
1
What's Up!: [10, 20]

メンバー変数に直接アクセスできているのでこれでは想定外のことがいつか起こり得ると思い、変更不可にしたいとこに使えるのがfinalキーワードだ。

finalとconst二つあるが、オブジェクト生成時に値が決まるものはfinalそもそもクラス定義時に決まっているものはconstと覚えておけば良いと思う。

DartPad

名前のところにfinalキーワードと付けたら、生成後、名前を変更しようとしている箇所で怒られている。

constキーワードはコンストラクターにも使える。

DartPad

コンストラクターにconstキーワードをつけたが、生成する箇所は何も起こってない。

ただ、メンバー変数にfinalになってないと怒られるので全てfinalをつけた。

これから通常のインスタンスとconstで生成したインスタンスと比較してみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void main() {
  Point2D pt = Point2D(x: 1, y: 2);
  Point2D pt2 = Point2D(x: 1, y: 2);
  Point2D pt3 = const Point2D(x: 1, y: 2);
  Point2D pt4 = const Point2D(x: 1, y: 2);
  print(pt == pt2);
  print(pt == pt3);
  print(pt3 == pt4);
}

class Point2D {
  final int x;
  final int y;
  final String name;
  const Point2D({
    required this.x,
    required this.y,
    this.name = 'First Point',
  });

  String getStringValue() {
    return '$name: [$x, $y]';
  }
}
1
2
3
false
false
true

通常のインスタンスとconstキーワードで生成したインスタンスをそれぞれ2つ作ってみた。設定する値はそれぞれ同じものだ。

通常のインスタンスはそれぞれ違うメモリを使うので同じものではないと表示される。もちろん通常のインスタンスとconstインスタンスとも違う。でもconstインスタンス同士は値が同じであれば、同じメモリになるので比較結果は同じとなる。

これはプログラムの効率を上げるために考案されたものだ。Flutterでよく見えるものであるので慣れていくと思う。

ゲッター、セッター(getter, setter)

上でメンバー変数を変更できないようにするためにfinalキーワードを使った。

クラスのメンバーにはpublic、privateというのがあってpublicはクラスメンバーだけでなく、外部からアクセスできるものでprivateはクラスのメンバーのみアクセスできるものだ。変数、メソッドの頭にアンダースコア(_)をつければprivateメンバーになる。

だが、DartPadでは全てが一つのファイルになるので適用されない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void main() {
  Point2D pt = Point2D(x: 1, y: 2);
  print(pt.getStringValue());
  pt._x = 10;
  print(pt.getStringValue());
}

class Point2D {
  int _x;
  int _y;
  String name;
  Point2D({
    required int x,
    required int y,
    this.name = 'First Point',
  })  : _x = x,
        _y = y;

  String getStringValue() {
    return '$name: [$_x, $_y]';
  }
}
1
2
First Point: [1, 2]
First Point: [10, 2]

このようなprivateメンバー変数に対して外部からアクセスできるようにするのがgetter、setterである。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void main() {
  Point2D pt = Point2D(x: 1, y: 2);
  print(pt.getStringValue());
  pt.x = 10;
  print(pt.x);
}

class Point2D {
  int _x;
  int _y;
  String name;
  Point2D({
    required int x,
    required int y,
    this.name = 'First Point',
  })  : _x = x,
        _y = y;
  
  int get x => _x;
  set x(int value) {
    _x = value;
  }

  String getStringValue() {
    return '$name: [$_x, $_y]';
  }
}
1
2
First Point: [1, 2]
10

privateメンバー変数をgetter、setterを使って外部からオブジェクトのプロパティのように使える。

インヘリタンス(Inheritance)

OOPが多く広がっている理由の一つはこのインヘリタンスだ。相続という意味で一つのクラスの全てを他のクラスで受け継いで使えるようになる機能だ。

上で定義したPoint2Dクラスを相続してPoint3Dというクラスを定義してみる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void main() {
  Point2D pt = Point2D(x: 1, y: 2);
  print(pt.getStringValue());
  Point3D pt2 = Point3D(x: 1, y: 2, z: 3);
  pt2.memberPrint();
}

class Point2D {
  int x;
  int y;
  String name;
  Point2D({
    required this.x,
    required this.y,
    this.name = 'First Point',
  });

  String getStringValue() {
    return '$name: [$x, $y]';
  }
}

class Point3D extends Point2D {
  int z;
  Point3D({
    required super.x,
    required super.y,
    required this.z,
  });
  
  void memberPrint() {
    print('[$x, $y, $z]');
  }
}
1
2
First Point: [1, 2]
[1, 2, 3]

相続するときに使うのがextendsキーワードだ。Point3Dにはx、yメンバー変数がないが、自分のもののように使える。ここ新しく出たキーワードがsuperだ。superは親クラスを指すキーワードである。上ではsuperキーワードを使って親クラスのPoint2Dのメンバー変数にアクセスしている。

getStringValueメソッドも使えるのではないか。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void main() {
  Point2D pt = Point2D(x: 1, y: 2);
  print(pt.getStringValue());
  Point3D pt2 = Point3D(x: 1, y: 2, z: 3);
  print(pt2.getStringValue());
}

class Point2D {
  int x;
  int y;
  String name;
  Point2D({
    required this.x,
    required this.y,
    this.name = 'First Point',
  });

  String getStringValue() {
    return '$name: [$x, $y]';
  }
}

class Point3D extends Point2D {
  int z;
  Point3D({
    required super.x,
    required super.y,
    required this.z,
  });
  
  void memberPrint() {
    print('[$x, $y, $z]');
  }
}
1
2
First Point: [1, 2]
First Point: [1, 2]

使えるのは分かったが、少し残念な結果になった。

こんな時に使うのがoverrideである。overrideアノテーションを使うと親クラスのメソッドを再定義できる。

getStringValueを再定義してみよう。ついでに名前も変えておく。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void main() {
  Point2D pt = Point2D(x: 1, y: 2);
  print(pt.getStringValue());
  Point3D pt2 = Point3D(x: 1, y: 2, z: 3);
  print(pt2.getStringValue());
}

class Point2D {
  int x;
  int y;
  String name;
  Point2D({
    required this.x,
    required this.y,
    this.name = 'Point2D',
  });

  String getStringValue() {
    return '$name: [$x, $y]';
  }
}

class Point3D extends Point2D {
  int z;
  Point3D({
    required super.x,
    required super.y,
    required this.z,
    super.name = 'Point3D',
  });
  
  void memberPrint() {
    print('[$x, $y, $z]');
  }
  
  @override
  String getStringValue() {
    return '$name: [$x, $y, $z]';
  }
}
1
2
Point2D: [1, 2]
Point3D: [1, 2, 3]

ここでそれぞれのインスタンスのタイプを比較してみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void main() {
  Point2D pt = Point2D(x: 1, y: 2);
  print(pt.getStringValue());
  Point3D pt2 = Point3D(x: 1, y: 2, z: 3);
  print(pt2.getStringValue());
  
  print(pt is Point2D);
  print(pt is Point3D);
  print(pt2 is Point2D);
  print(pt2 is Point3D);
}

class Point2D {
  int x;
  int y;
  String name;
  Point2D({
    required this.x,
    required this.y,
    this.name = 'Point2D',
  });

  String getStringValue() {
    return '$name: [$x, $y]';
  }
}

class Point3D extends Point2D {
  int z;
  Point3D({
    required super.x,
    required super.y,
    required this.z,
    super.name = 'Point3D',
  });
  
  void memberPrint() {
    print('[$x, $y, $z]');
  }
  
  @override
  String getStringValue() {
    return '$name: [$x, $y, $z]';
  }
}
1
2
3
4
5
6
Point2D: [1, 2]
Point3D: [1, 2, 3]
true
false
true
true

親クラスであるPoint2DのインスタンスptのタイプはPoint2Dではあるが、Point3Dではない。当たり前だ。

子クラスであるPoint3Dのインスタスpt2のタイプはPoint2DでもPoint3Dでもある。相続されたクラスは親と自分の両方を持つのでこのような結果になることを覚えておこう。

上のコードをDartPadで実行すると当たり前のことをするなとワーニングが表示されるが、気にせず事実を確認してみよう。

静的変数(Static Variable)

メンバー変数は静的変数として宣言できる。

通常のメンバー変数と違いは通常のメンバー変数がインスタンスに属する反面、静的変数はクラスに属するものである。

言葉で説明するより、コードをみてみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main() {
  Student tanaka = Student('田中');
  Student nakamura = Student('中村');
  tanaka.printName();
  nakamura.printName();
  Student.school = 'ブドウヶ丘高校';
  tanaka.printName();
  nakamura.printName();
}

class Student {
  static String? school;
  String name;
  Student(this.name);
  
  void printName() {
    print('私の名前は$nameです。私は$schoolに通っています。');
  }
  
  static printSchool() {
    print('私たちは$schoolの学生です。');
  }
}
1
2
3
4
私の名前は田中です。私はnullに通っています。
私の名前は中村です。私はnullに通っています。
私の名前は田中です。私はブドウヶ丘高校に通っています。
私の名前は中村です。私はブドウヶ丘高校に通っています。

Studentというクラスを定義し、学校名を静的変数にした。

二人の学生を作ってprintNameで表示すると学校名を設定してなかったのでnullが表示される。

次に学校名を設定する。ここも覚えてほしいが、静的変数へのアクセスはクラスでする。

その後の出力を見ると二人の学校が同じものになっている。

これが静的変数だ。

静的メソッドもある。それを実行してみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void main() {
  Student tanaka = Student('田中');
  Student nakamura = Student('中村');
  tanaka.printName();
  nakamura.printName();
  Student.school = 'ブドウヶ丘高校';
  tanaka.printName();
  nakamura.printName();
  Student.printSchool();
}

class Student {
  static String? school;
  String name;
  Student(this.name);
  
  void printName() {
    print('私の名前は$nameです。私は$schoolに通っています。');
  }
  
  static printSchool() {
    print('私たちは$schoolの学生です。');
  }
}
1
2
3
4
5
私の名前は田中です。私はnullに通っています。
私の名前は中村です。私はnullに通っています。
私の名前は田中です。私はブドウヶ丘高校に通っています。
私の名前は中村です。私はブドウヶ丘高校に通っています。
私たちはブドウヶ丘高校の学生です。

抽象クラス(Abstract Class)

上のインヘリタンスでは親クラスの全てを子クラスが受け継いで使えるようになった。

これは親クラスの全メンバー変数、メソッド(機能)を持つことになる。

全機能を受け継ぐのではなく、構造のみを強制する方法がある。

それが抽象クラスである。コードで理解してみよう。

1
2
3
4
5
6
abstract class Student {
  String school;
  String name;
  Student(this.name, this.school);
  void sayHello();
}

上のコードは通常のクラス宣言と何かが違う。

まずはabstractキーワードだ。これはこのクラスは抽象クラスであることを宣言するものだ。

次にメソッド。リターン・タイプとメソッド名のみである。これが許されるのはabstractで宣言されているからだ。

このように抽象クラスは構造のみを定義する。

抽象クラスを使うにはextendsキーワードではなく、implementsキーワードを使う。

DartPad

implementsキーワードを使った途端、怒られたいる。

内容はすぐにStudentの構造を作れとのことだ。

DartPad

エラーは無くなったが、インフォーメーションが表示されている。再定義なのにoverrideアノテーションがないということだ。うざいが、つけてやろう。

ついでにインスタンス化してみる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void main() {
  Student jojo = JojoStudent('東方', 'ぶどうヶ丘高校');
  jojo.sayHello();
}

abstract class Student {
  String school;
  String name;
  Student(this.name, this.school);
  void sayHello();
}

class JojoStudent implements Student {
  @override
  String school;
  @override
  String name;
  
  JojoStudent(this.name, this.school);
  
  @override
  void sayHello() {
    print('私の名前は$nameです。$schoolの学生です。');
  }
}
1
私の名前は東方です。ぶどうヶ丘高校の学生です。

えっ、何か違和感を感じているか?

抽象クラスはインターフェースともいう。Studentの構造を持っていれば、Studentをimplementsしたクラスは何てもStudentで宣言できる。

複雑な理論的な説明より、上のようなコードで覚えておこう。

タグ:

カテゴリー:

更新日時:

コメントする