1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Scalaでオブジェクト指向を学ぶ(その1)

Posted at

はじめに

これまでの続きです。
その1で終わるかもしれないけど、結局わかってないオブジェクト指向を噛み砕きます。

やること

前回の内容をオブジェクト化した形に改良します。

完成したコード

だいぶスッキリしました。

import scala.util.Random
import io.StdIn.readLine

//じゃんけんクラス
class Janken(name: String, hand: Int) { //フィールドを引数のような形で指定する
    val handName: String = name
    val handVal: Int = hand

    def vs(that: Janken) = {
        ((this.handVal - that.handVal + 3) % 3) match {        
            case 0 => "Draw"
            case 1 => "Lose"
            case 2 => "Win"
        }
    }
}

//じゃんけんのシングルトンオブジェクト
object Janken {
    val handMapJp: Map[String, Int] = Map("グー" -> 0, "チョキ" -> 1, "パー" -> 2)

    def createHandFromJapanese(): Janken = {
        print("手を出してね:")
        val readHand: String = readLine()
        
        if (handMapJp.contains(readHand)) {
            new Janken(readHand, handMapJp(readHand))
        }
        else createHandFromJapanese()
    }

    def createRandomHand(): Janken = {
        val hand: String = Random.shuffle(List("グー", "チョキ", "パー")).apply(0)
        new Janken(hand, handMapJp(hand))
    }

    def resultToJapanese(mine: Janken, yours: Janken): String = {
        mine vs yours match {
            case "Win" => "わたし: " + mine.handName + "、あなた: " + yours.handName + " => 私の勝ち!"
            case "Lose" => "わたし: " + mine.handName + "、あなた: " + yours.handName + " => 私の負け..."
            case "Draw" => "わたし: " + mine.handName + "、あなた: " + yours.handName + " => あいこ。"
        }
    }
}

val myHand: Janken = Janken.createRandomHand() //自分(CPU)はランダム
val yourHand: Janken = Janken.createHandFromJapanese() //相手(人間)はユーザ自身が決める
println(Janken.resultToJapanese(myHand, yourHand))

詳細

Jankenクラス

オブジェクト => 「要素と処理の集合」と考えています。今回のじゃんけんクラスだと、

  • グー / チョキ / パーの3つの要素
  • じゃんけんの構成要素で優劣を決める=勝負

と考えることができますね。

//じゃんけんクラス
class Janken(name: String, hand: Int) {
    val handName: String = name
    val handVal: Int = hand

    def vs(that: Janken) = {
        ((this.handVal - that.handVal + 3) % 3) match {        
            case 0 => "Draw"
            case 1 => "Lose"
            case 2 => "Win"
        }
    }
}

スクリプトにベタ書きしていた前回は「グー -> 0」のMapを一々呼び出して処理をしていましたが、クラス化することによって、人間向きの文字パラメータとコンピュータ向けの数値パラメータのうち必要なものだけを取り出すことができるようになりました!

Scalaでは、インスタンス毎に指定されないと困る要素は引数のような形で指定が可能です。
また、Scalaは引数が1つの関数の場合、「オブジェクト 関数名 引数」 という数学的な書き方ができます。
今回のケースでは「myHand vs yourHand」といった書き方が可能です!

Jankenオブジェクト

classは動的な要素(メンバー)のみを定義することが、オブジェクト指向ではよいとされています。
動的なメンバーとは、

  • インスタンス毎に変わる変数(フィールド)
  • フィールドによって結果の変わる処理(メソッド)

静的なメンバーを定義するためには、それ専用のオブジェクト(シングルトンオブジェクト)を作る必要があります。
object文で定義しましょう。

//じゃんけんのシングルトンオブジェクト
object Janken {
    val handMapJp: Map[String, Int] = Map("グー" -> 0, "チョキ" -> 1, "パー" -> 2)

    def createHandFromJapanese(): Janken = {
        print("手を出してね:")
        val readHand: String = readLine()

        if (handMapJp.contains(readHand)) {
            new Janken(readHand, handMapJp(readHand))
        }
        else createHandFromJapanese()
    }

    def createRandomHand(): Janken = {
        val hand: String = Random.shuffle(List("グー", "チョキ", "パー")).apply(0)
        new Janken(hand, handMapJp(hand))
    }

    def resultToJapanese(mine: Janken, yours: Janken): String = {
        mine vs yours match {
            case "Win" => "わたし: " + mine.handName + "、あなた: " + yours.handName + " => 私の勝ち!"
            case "Lose" => "わたし: " + mine.handName + "、あなた: " + yours.handName + " => 私の負け..."
            case "Draw" => "わたし: " + mine.handName + "、あなた: " + yours.handName + " => あいこ。"
        }
    }
}

シングルトンオブジェクトは、紐づくクラスと同じ名前にするのがポイントです。

また、Jankenインスタンスは"人が手を決めるパターン"と、"ランダムに手を決める"2パターンがあります。
一々newで書くのも面倒なので、インスタンスを作る処理はメソッド化します。
再帰処理でthisが要らないのは、都度新しいオブジェクトが生成されるから問題ない、ということ?

出力処理については、前回の処理と比較すると相当スッキリ書けています。
面倒なListやTupleを作ったり呼び出したりせず、インスタンスから手の文字情報を呼び出しているだけですね!

##メイン処理

val myHand: Janken = Janken.createRandomHand() //自分(CPU)はランダム
val yourHand: Janken = Janken.createHandFromJapanese() //相手(人間)はユーザ自身が決める
println(Janken.resultToJapanese(myHand, yourHand))

myHand、yourHandはclassで定義したJankenクラスのインスタンスです。
一方、呼び出されているメソッドはobjectで定義しているものです。
使う側はJankenクラスに関わるものを呼び出す、という意識でどちらも一致していますね。
「インスタンスorオブジェクト.メンバー」で呼び出しが可能です。

頭の整理:クラスとオブジェクト

静的メンバーと動的メンバー

classは動的な要素(メンバー)のみを定義することが、オブジェクト指向ではよいとされています。

静的なメンバーを定義するためには、それ専用のオブジェクト(シングルトンオブジェクト)を作る必要があります。

と書きましたが、篩い分けた観点をまとめます。

handName、handval

インスタンスの要素そのものを作っている(初期化)ため、もちろん動的です。

vs

thisを使っている通り、インスタンス自身の中身を見ています。
インスタンスの値によってmatchで並列分岐をしています。よって動的です。
※といいつ

handMapJp

グー/チョキ/パーと数値の紐付けは常に変わらず、Jankenクラスで固定です。よって静的です。

createHandFromJapanese、createRandomHand

ここで行いたい処理は、「インスタンス作成に必要な情報の収集/生成」です。
インスタンス毎にインスタンスの作り方が変わっては大変です。クラスで固定なので、静的です。
こういうインスタンスを作るメソッドをファクトリーメソッドと言います。

resultToJapanese

vsメソッドで行われた処理をわかりやすく整形するメソッドです。
インスタンスによって引数の値は変わりますが、値は処理に影響を与えず、クラス固定です。
よって静的です。

課題

まず一つ作ってみました。
まだまだオブジェクト関連のキーワードが多いので、1つ1つ噛み砕いていこうと思います。

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?