クラスについて
続いてDartでのクラス定義について説明します。
クラスの構造
まず、Dartのクラス構造の基本的な形式を紹介します。DartにはJavaのように、privateやpublicといったアクセス区分を定義するキーワードはありません。そのため、クラスやメソッドは常にアクセス可能です。ただし、フィールドだけはアクセス制限ができます。そして、そのフィールドにアクセスするゲッター・セッターを定義することができます。
リスト7はクラス定義のコード例です。
class UserName {
String _name; // (1) privateフィールド
// (2)setter
set name(String value){
_name = value;
}
// (3)getter
String get name{ // ()は記述しません
return _name;
}
// String get name => _name; // (4)
String getDisplayName(){
return _name + "-San";
}
}
変数名が(1)のように_(アンダーバー)から始まっている場合は、他のソースコードからアクセスはできないため、制限付きのメンバ変数として利用できます。これは名前を使った暗黙のルールになります。それ以外のメンバ変数ではどこからでもアクセス可能です。
変数に値を設定するセッターは(2)の通りに設定します。また、インスタンス変数にアクセスする場合は、this._nameのようにthisを明示的に指定できます。しかし、Flutterでは一般的にthisは省略されることが多いです。
ゲッターを指定する場合には、(3)のように記述します。ゲッターでの()は記述しません。また、一般的には、(4)のように1行関数形式で記述するケースが多いです。また、メソッドは関数を定義する場合と同様に定義が可能です。
_(アンダーバー)から始まるメンバ変数の制限について
「_」から始まるメンバ変数は、クラス外からアクセスできないプライベート変数のように紹介されますが、厳密には、同じソースコード内であればアクセス可能です。
1つのクラスを1つのファイルで定義している場合は、プライベート変数として扱うことができます。一方で、複数のクラスを1つのソースコード内に定義している場合は、同じソースコードであれば別クラスからであってもインスタンス変数にアクセス可能です。
つまり、privateでもなく、publicでもないメンバ変数が作れます。Java言語で言えば、protectedと似たような役割として扱うことも工夫次第で可能になります。ただし、特別な理由を除いてプライベート変数として扱ったほうが管理もしやすいですし、わかりやすくなります。
コンストラクタ
先ほどのクラスの例では、コンストラクタの記述を省略しましたが、DartでのコンストラクタはJavaやJavaScriptなどに比べても少々複雑になります。ただし、ルールを知れば決して難しいものではありません。
class Hello{
String _greeting;
String _lang;
Hello(String greeting,String lang){
this._greeting = greeting;
this._lang = lang;
}
// (1) 同じ意味の省略形式
// Hello(this._greeting,this._lang)
}
クラス名と同じ名前のメソッドがコンストラクタとして利用されます。また、(1)のように記述することで、ほぼ同じ意味のコンストラクタとして定義可能です。Dartでは、一般的に後者の記述形式が利用されることが多いです。
コンストラクタでの値の初期化
続いて、先ほどの_greetingと_langという変数をfinal指定した場合です。finalとして指定した場合は、コンストラクタで値を初期化する必要があります。リスト9が記述例です。
class Hello{
final String _greeting;
// 省略
// Hello(this._greeting,this._lang); // (1)よく使われるコンストラクタの記述方法
Hello() : _greeting = 'Hello' , _lang = 'en'; // (2)初期化リストにて初期値の指定
}
(1)は、先ほどのサンプルの省略形式です。省略形式ではない書き方はfinalの場合は使えません。また、引数で指定しない変数をコンストラクタ時に指定する場合は、(2)のように:(コロン)の後に、カンマ区切りで値を初期化することができます。
名前付きコンストラクタ
Dartでは異なる引数を持つ同名コンストラクタを複数定義できません。そこで、名前付きコンストラクタという機能が存在します。この機能を使うことで、目的別にコンストラクタを複数用意することができます。
class Hello{
// (省略)
Hello(this._greeting,this._lang);
// (1)異なるデフォルトの値を持つコンストラクタ
Hello.empty() : _greeting = '', _lang = '';
Hello.english() : _greeting = 'Hello', _lang = 'en';
Hello.japanese() : _greeting = 'こんにちは' , _lang = 'ja';
// (2)引数を指定した名前付きコンストラクタ
Hello.withGreeting(this._greeting) : this._lang = 'en';
// (3)初期値設定での別のコンストラクタを利用する
Hello.withLang(String lang) : this('',lang);
}
main(){
// (4)別名コンストラクタの呼び方
var h1 = Hello.empty();
var h2 = Hello.withGreeting('good morning');
}
名前付きコンストラクタが利用される例として、(1)のように異なるデフォルト値を持つインスタンスを作成する場合があります。(2)のように一部の引数を指定するケースでもよく利用されます。その場合には慣習的にwithというプレフィックスを用いられる場合が多くなっています。
また、(3)のように:(コロン)の後に別のコンストラクタを指定することも可能です。例ではthisのみを使って通常のコンストラクタを呼んでいますが、ここには別名コンストラクタの指定も可能で、リダイレクトコンストラクタという名称で呼ばれています。これらのコンストラクタを使ってインスタンス化する際には、(4)の通りに実行します。
クラスの継承
既存のクラスを継承する場合には、リスト11のようにextendsキーワードを利用してクラスを定義します。
class Hello{
// (省略)
}
class HelloName extends Hello{
String _name;
// (1)コンストラクタ
HelloName(this._name,String greeting,String lang) : super(greeting,lang);
// (2)メソッドの上書き
String sayHello(){
String value = super.sayHello();
return '$value $_name';
}
}
継承したクラスでコンストラクタが必要になる場合は、(1)のようにsuperを用いて親クラスのコンストラクタが実行できます。また、(2)のように親クラスのメソッドを呼ぶ場合にも、同様にsuperを使って親クラスのメソッドが利用できます。
注意点として、親クラスのコンストラクタが自動的に継承されることはありません。別名コンストラクタなどの機能もあり、自動的に安全に呼ぶことは難しいですが、このようなところは少々不便を感じる部分でもあります。
抽象クラスとインターフェース
Dartにはインターフェースというものは存在せず、クラスは通常クラスか、抽象クラスのどちらかになります。インターフェース相当のクラスを作りたい場合は、実装がない抽象クラスを作れば、同じ効果を持つクラスが作成できます。
そして、すべてのメソッドを再定義を強制するimplementsキーワードが存在します。これらを組み合わせることで目的が達成できます。少々、わかりにくいのでリスト12のサンプルコードを用いて説明します。
// (1)abstractを用いた抽象クラスの作成
abstract class AbstractHello{
String sayHello();
}
// (2)(インターフェースとして扱う)抽象クラスを利用した実装
class Hello implements AbstractHello{
String sayHello() {
return 'Hello';
}
}
// (3)継承(extends)を利用した場合
class HelloDefault extends AbstractHello{
String sayHello() {
return 'Hello';
}
}
// (4)extendsを利用した場合
class HelloEnglish extends Hello {}
// (5) implementsを利用した場合
class HelloJapanese implements Hello{
String sayHello() {
return 'こんにちは';
}
}
抽象クラスを作成する場合には、(1)のようにabstractキーワードをclassの前に付けます。そして、(2)のようにimplementsキーワードを用いた場合には、元となるクラスのメソッドを実装する必要があります。
例えば、(3)のようにextendsを用いてもこの場合にはほぼ同様になりますが、extendsとimplementsが異なるのは、extendsは不足分を実装しなければならないということであり、implementsは元となるメソッドを再定義しなければなりません。
そのため、(4)のように既に単体として完結しているクラスをextendsした場合には、実装を記述する必要はありませんが、(5)のようにimplementsを使用した場合には、再度メソッドを再定義する必要があります。説明だと少々難解に思えますが、組み合わせ方法で同じことができます。
まとめ
今回、関数とクラスの基本について紹介しました。これらは、他の言語と非常に似ていますが、よりコードの記述を少なくするためのちょっとしたアイデアが含まれています。一方で、それらを知らないと疑問に思う部分も出てきてしまいます。Dartにはクラスのコードの再利用を可能にするミックスイン機能やnullセーフなコード記述など、コード記述が少なくなるアイデアが他にも多々あります。
次回は、今回紹介しきれなかったコンストラクタの他の機能に加え、それらの機能を紹介します。
