読者です 読者をやめる 読者になる 読者になる

夢とガラクタの集積場

落ちこぼれ三流エンジニアである管理人の夢想=『夢』と、潰えた夢=『ガラクタ』の集積場です。

駄目ネタで試すScala Actor 〜魔法少女まどか☆マギカ編〜解説

というわけで、前回投稿中のコードを解説。

■Homo.scala

abstract class Homo(name: String) {
  /**
   * 名前を文字列型で返す抽象メソッド
   */
  def getName(): String
}


オブジェクト指向言語ではおなじみの抽象クラスです。
コンストラクタとして、String型の引数を渡す構成になっています。
尚、コンストラクタの引数にvalを付けると継承メソッド側でエラーとなります。
valはfinalと同様の扱いのため、後から変更することが出来ないためです。

その上で、名前を取得するgetNameメソッドを持ちます。
メソッド本体を定義しなければ自動的に抽象メソッド化されるため、
メソッド側には特にabstractは記述していません。

■Puella.scala

class Puella(name: String) extends Homo(name) {

  /**
   * 初期化時の名前を取得し、返す。
   */
  def getName(): String = { this.name; }
}


人間クラスを継承した少女クラスです。
親クラスのコンストラクタをそのまま使用しています。

尚、getNameメソッドの中ではインスタンスフィールドnameを使用していますが、
これはscalaコンストラクタの引数に指定した変数名と同名のprivateフィールドを
自動生成して保持するため使用可能になっています。

■Incubator.scala

object Incubator extends Actor {

  /**
   * メッセージ待ち受けメソッド
   */
  def act() {
    loop {
      receive {
        case puellaMagi: PuellaMagi =>
          Console.println(puellaMagi.getName() + "、やがて「魔女」になる君たちのことは「魔法少女」と呼ぼう。");
        case puella: Puella =>
          Console.println(puella.getName() + "、僕と契約して魔法少女になってよ。");
        case homo: Homo =>
          Console.println(homo.getName() + "、残念ながら君には用は無いなぁ。");
        case "exit" =>
          Console.println("それじゃあね。"); exit;
          exit;
        case any: Any =>
          Console.println("訳が分からないよ。");
      }
    }
  }
}

objectのインキュベータークラスです。
loopの中のcase分岐を見ればわかるように、
与えられたメッセージクラスに応じて挙動を変えられるようになっています。
マッチングはcase分岐の上から行われ、マッチした処理が実行されます。

Mainメソッド側を見ればわかりますが、
caseにHomoを指定していた場合、子クラス全てがマッチします。

■IncubatorMain.scala

  def main(args: Array[String]): Unit = {
   
    // Actor起動
    Incubator.start();
   
    Incubator ! new Puella("鹿目 まどか")
   
    Incubator ! new PuellaMagi("美樹 さやか")
   
    Incubator ! new Puer("上条 恭介")
   
    Incubator ! "UnKnown"
   
    Incubator ! "exit"
  }

}

Incubatorクラスをstartで起動し、
そこにオブジェクトのメッセージを渡すことで挙動を確認しています。
渡したクラスに応じてActorの挙動が変わっていることが分かりますね。

処理をディスパッチしたり、唯一のデータアクセスオブジェクトを定義したりと、
なかなか面白い使い方が出来そうです。Actor。