SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Flutterで始めるモバイルアプリ開発

Dart言語でのプログラム設計でワンランク上を目指そう

Flutterで始めるモバイルアプリ開発 第6回


  • X ポスト
  • このエントリーをはてなブックマークに追加

ミックスイン

 Dartでは、単一継承の制限があります。つまり、直接の親クラスは1つでなければなりません。しかし、他のクラスのメソッドを共有したい場合があります。そういった場合に利用するのがミックスインです。

図3:ミックスイン
図3:ミックスイン

 通常のクラス継承は、継承することで目的を具体化していくイメージで作っていくのに比べ、ミックスインでは機能を組み合わせて目的のクラスを作っていくイメージです(図3)。例えば、外部サービスとの連携機能やユーティリティ系の機能、デバッグやログ機能を共有したい場合のほか、クラスをまたがって機能を共有したい場合に利用されます。

 リスト4は、toHash/generateメソッドをミックスインとして定義し、これを別のクラスに組み込む例です。

[リスト4]ミックスインクラスの利用例(mixins.dartの抜粋)
// (省略)
// (1) ミックスインクラス
mixin Hash{
  // (省略)
  String toHash(){
    var hmac = Hmac(sha256,utf8.encode(key));
    return hmac.convert(utf8.encode(data)).toString();
  }
}
mixin RandomGenerator{
  String generate(){
    //  実際にはルールに沿ったランダムな値を生成
    return "987654321";
  }
}
// (2) mixinクラスを利用する
class Password with Hash , RandomGenerator{
  // (省略)
  Password.random(){
    // (3) mixinのメソッドを使う場合にはsuperを使う
    data = "pw" + super.generate();
    key = "this_is_key";
  }
  // (省略)
}
main(){
  // (4) 利用例
  var pass1 = Password("1234567");
  print(pass1.toHash());
}

 (1)のようにミックスインを作成する場合は、classキーワードの代わりにmixinキーワードを指定します。ミックスインでは、クラスと異なりコンストラクタは持てません。(2)のようにwithキーワードを使うことで、作成したミックスインのメソッドを新しいクラスで利用できるようにします。また、ミックインで定義したメソッドを新しいクラス内で使うには(3)の通り、superを利用します。

名前が重複した場合

 ミックスインを利用するようになると、メソッドの名前がミックスイン/親クラスとの間で重複してしまう場合があります。その場合の優先度は以下の通りです。

  1. インスタンスクラス
  2. ミックスイン(後に指定したものが優先)
  3. 親クラス

 しかし、このような優先度による解釈を利用せず、重複しないように設計する方が望ましいと、著者は考えます。言語によっては「多重継承」を制限している言語もあります。例えば、Javaはその言語の1つです。

 それは先ほど紹介した、同じメソッド名の扱いだけではなく、同じデータにアクセスした場合の考慮など、実際の利用シーンでは複雑になってしまうケースがあるからです。そういった問題を解決するには「Adapterパターン」という手法があります。本項では割愛するので、興味がある方は調べてみてください。

非同期処理

 非同期処理とは、処理の結果が遅延して返ってくる処理のことで、図4の通り通常処理の同期処理と非同期処理を比べるとその違いがよくわかります。ただし、Dartの非同期処理はシングルスレッドで動作しています。図では並列で処理が動くイメージで記していますが、実際は複数の処理を切り替え、実行することで、ネットワーク処理などの待ち時間を有効に使っているだけです。その性質上、CPUを占有する処理では高速化を期待できません(効果があるのは、ネットワーク通信のようにI/O処理によって待ち時間が発生する処理だけです)。

図4:同期処理と非同期処理
図4:同期処理と非同期処理

 同期処理とは(1)のように1つの処理が終わってから、次の処理をします。例えば、ネットワーク経由でなんらかの処理を他に委ねている間、他の処理は行いません。委ねた処理の終了を待ってから、次の処理を行います。

 一方、非同期処理とは(2)のように処理を他に頼りますが、その間も手元では別の処理を行う処理です。別の処理とは、UI操作の受付などです。非同期処理を用いることで、処理を並行に実行できるため、総合的な処理時間を短くできる場合があります。Dartでは非同期処理を扱うためのクラスとしてFutureが用意されています。

Futureクラス

 非同期処理の基本的な機能はFutureクラスを理解する必要があるため、Futureクラスの使い方をまず説明します。JavaScriptを知っている方であれば、Promiseとほぼ同様と認識いただいて問題ないです。

[リスト5] Futureクラスのメソッドを利用したコード例(future.dartの抜粋)
future_sample1(){
  (1) Futureの取得
  var f = Future.delayed(new Duration(seconds: 5));
  //  (2) 結果が準備された実行
  f.then((value) => {
    print("5 sec later") // (3)
  });
  print("function end"); // (4)
}

 Futureクラスのdelayedメソッドは、指定した時間を待つためのメソッドで、(1)では5秒間待つための処理を指定しています。結果は5秒経過すると、(2)のthenで指定した関数が呼ばれるようになっています。この場合、(3)の処理が実行されます。

 しかし、このコードには注意点があり、(4)の処理は(3)の処理の前に実行されます。Futureで管理された処理の終了を待たずに、Dartは次の処理を実行するためです。これが非同期に実行される、という意味です。

async/awaitキーワードを使った非同期処理

 上の例を見てもわかるように、Futureによるコードでは、処理の実行順序が前後するため、処理がわかりにくくなります。そこで、非同期処理を通常の同期処理と同じように書けるようにしたものが、async/awaitキーワードを使った方法です。

[リスト6] Futureクラスのコードをasync/awaitを使って書き直したコード例(future.dartの抜粋)
future_sample2() async{  // (1) asyncキーワードをつける
  await Future.delayed(new Duration(seconds: 5)); // (2) awaitキーワードをつける
  print("5 sec later");
  print("function end");
}

 非同期処理をする場合には、関数に(1)のようにasyncキーワードをつけます。これは、この関数が非同期処理を扱う、という意味です。そして、非同期処理の実行では(2)のようにawaitキーワードをつけます。awaitキーワードを付与することで、その時点で一度処理が中断され(可能であれば、別の処理を並行して行います)、そして処理が終了したところで残りの処理を再開します。async/awaitによって、実行順のコードの記述順が一致するので、まるで同期処理の場合と同じ感覚で記述できることがわかります。

入れ子地獄も解消される

 Futureを使うともう1つわかりにくくなるのが、非同期処理を入れ子にするケースです。非同期処理の結果を元に次の非同期処理を行うケースがあります。

 リスト7に、Futureクラスのメソッドを使って非同期処理を行う場合とasync/awaitキーワードを使って書き換えたものを示します。今回は、非同期で取得した値の扱いにも注意していただきたいので、非同期処理で値を返す例を考えます。

[リスト7] async/awaitを使ったコード例(future_stack.dartの抜粋)
// (1) 引数で設定した秒数待って、待った秒数を返す
Future asyncValue(int sec){
  // (省略)
}
// (2) 今までのFutureを使った場合
future_sample2(){
  var f1 = asyncValue(2); // 2秒待つ
  f1.then((v1){
    print("exec - ${v1}");  // "exec - 2" と表示
    var f2 = asyncValue(1); // 1秒待つ
    f2.then((v2){
      print("exec - ${v2}"); // "exec - 1"と表示
    });
  });
}
// (3) await/asyncを使って書き換えた場合
future_sample2() async{ // (4) asyncキーワードをつける
  var v1 = await asyncValue(2);  // (5) awaitキーワードをつける
  print("exec - ${v1}");
  var v2 = await asyncValue(1);
  print("exec - ${v2}");
}

 今までは、指定した秒数だけ待つ処理でしたが、今回は(1)のように指定した秒数を待って、その値を返す非同期処理を前提に説明します。今回はこのメソッドの中身については重要でないため、実際のコードの説明は割愛します。詳しく知りたい方はサンプルコードを参照してください。

 (2)が、Futureを使った場合のコードです。最初に行った非同期処理が終わってから次の非同期処理を行っています。Futureではコールバック関数で非同期処理を終えた後の処理を表しているその性質上、非同期処理が続けば入れ子の階層も深く、わかりにくくなります。

 これをasync/awaitキーワードを使って書き換えたのが(3)です。こちらは2個の非同期処理(v1、v2)がフラットに連なっているだけであり、非同期処理の戻り値も普通の戻り値と同じように扱えるので、コードはぐんとスッキリしました。

まとめ

 Factoryコンストラクタやミックスインは設計にもかかわることなので、実際に利用する場合はどう使っていいかわからないケースがあるかもしれません。また、必ずしも使わないと実現できないものではないので、無理に利用する必要はありません。しかし、慣れてくると、他の方法で実現するよりも自然に、かつ、シンプルに表現できる記法でもあります。そんな時に、ふと本稿を思い出してみてください。

 次回は、ここまでで紹介できなかった、長くなりがちなコードを省略し記述することができる「シンタックスシュガー」を中心に紹介します。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Flutterで始めるモバイルアプリ開発連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 小林 昌弘(コバヤシ マサヒロ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/13665 2022/09/05 20:11

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング