はじめに
これまでの続きです。
その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つ噛み砕いていこうと思います。