【SOLID原則】じゃんけんアプリをオブジェクト指向で作り変えてみた

# はじめに

この記事は「🎄[RUNTEQ Advent Calendar 2023](https://qiita.com/advent-calendar/2023/runteq)🎅」の18日目に参加用の記事です。

テーマは【**初めて学んだ技術**】ということで、改めてデザインパターンである【**オブジェクト指向とSOLID原則**】について書いていきます。

以前作ったじゃんけんアプリをもとに**オブジェクト指向**について考えながら、作り変えていきたいと思います。


# 古のジャンケンアプリ
学習初期に**Ruby**の学習に合わせて**ジャンケンアプリ**を作っていました。
**勝負回数を選択でき、CPUと対戦できる簡単なアプリです。**

[![Image from Gyazo](https://i.gyazo.com/d29b14003757f441de466d365802822d.gif)](https://gyazo.com/d29b14003757f441de466d365802822d)

コードはこちらです。
パッと見でも???と思うようなコードが散見されています。
もはや愛くるしささえ感じます。

```ruby
HAND_GU = "g"
HAND_CHOKI = "c"
HAND_PA = "p"
JANKENHANDS = [HAND_GU,HAND_CHOKI,HAND_PA]
NUMBEROFMATCHES = [1,3,5]

def create_number_of_matches
  puts "何本勝負？(press #{NUMBEROFMATCHES.join(' or ')})"
  number = gets.chomp.to_i
  number_exists(number)
end

def do_rock_paper_scissor(number_of_matches)
  victory = 0
  defeat = 0
  num = 0
  while num < number_of_matches do
    puts "#{num + 1}本目"
    puts "じゃんけん…(press #{JANKENHANDS.join(' or ')})"
    @cpu_hands = JANKENHANDS[rand(0..2)]
    @my_hands = gets.chomp.to_s
    hands_exists(@my_hands)

    if @my_hands == @cpu_hands 
      aiko
    else
      show_hand
    end

    #結果の判定
    if @my_hands == HAND_GU && @cpu_hands == HAND_CHOKI || @my_hands == HAND_CHOKI && @cpu_hands == HAND_PA || @my_hands == HAND_PA && @cpu_hands == HAND_GU
      victory += 1
      puts "勝ち！"
    end
    if @my_hands == HAND_GU && @cpu_hands == HAND_PA || @my_hands == HAND_CHOKI && @cpu_hands == HAND_GU || @my_hands == HAND_PA && @cpu_hands == HAND_CHOKI
      defeat += 1
      puts "負け！"
    end
      puts "#{victory}勝#{defeat}敗"
      num += 1
  end
    if victory > defeat
      puts "結果\n#{victory}勝#{defeat}敗であなたの勝ち"
    else
      puts "結果\n#{victory}勝#{defeat}敗であなたの負け"
    end
end

def aiko 
  show_hand 
  puts "あいこで...(press #{JANKENHANDS.join(' or ')})"
  @cpu_hands = JANKENHANDS[rand(0..2)]
  @my_hands = gets.chomp.to_s
  hands_exists(@my_hands)
end

def show_hand
  if @cpu_hands == HAND_GU
    puts 'CPU…グー'
  elsif @cpu_hands == HAND_CHOKI
    puts 'CPU…チョキ'
  elsif  @cpu_hands == HAND_PA
    puts 'CPU…パー'
  end
  if @my_hands == HAND_GU
    puts 'あなた…グー'
  elsif @my_hands == HAND_CHOKI
    puts 'あなた…チョキ'
  elsif  @my_hands == HAND_PA
    puts 'あなた…パー'
  end
end  

def number_exists(number)
  if NUMBEROFMATCHES.include?(number)
    puts "#{number}本勝負を選びました"
    number
  else
    puts "#{NUMBEROFMATCHES.join(',')}のいずれかの値を入力してください"
    create_number_of_matches
  end
end

def hands_exists(hands)
  if JANKENHANDS.include?(hands)
    true
  else
    puts "#{JANKENHANDS.join(',')}のいずれかの値を入力してください"
    hands = gets.chomp.to_s
    hands_exists(hands)
  end
end

number_of_matches = create_number_of_matches
do_rock_paper_scissor(number_of_matches)
```

# オブジェクト指向とは？
> オブジェクト指向（object oriented）とは、コンピュータプログラムの設計や実装についての考え方の一つで、互いに密接に関連するデータと手続き（処理手順）を「オブジェクト」（object）と呼ばれる一つのまとまりとして定義し、様々なオブジェクトを組み合わせてプログラム全体を構築していく手法。

少し難しいですね。

もう少し調べると、こんな説明が↓↓↓

> システム全体を、現実世界の物理的なモノ（object）に見立てた「オブジェクト」と呼ばれる小さな構成単位の組み合わせとして捉える。

なるほどなるほど。

現実世界の物理的なモノに見立ててプログラムを組むということでしょうか？


一方で、**モノ**として考えてコードを書いていけば良いことはわかったものの、具体的な書き方がわかりません。。。。。


https://e-words.jp/w/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91.html



# そして神となる


とりあえず神になります。
余計なことは考えず、神となってじゃんけんアプリを作ってみましょう。

### 神「神じゃ。」

![internet_god.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/d108cdcc-085b-061f-b7cd-677c9166296f.png)

# 神はこの様に世界を作られる
神はいくらでも自由に世界をお作りになられることができます。

一方で、神が世界を作られる上で**前提のルール**があります。

それが**クラス**と**インスタンス**です。

例えば、神がある`世界(プログラム)`に`人間(オブジェクト/モノ)`を誕生させたい時には、いきなり作ることは出来ません。

`人間(オブジェクト/モノ)`を誕生させたい場合は、人間って何？という必ず設計図を書く必要があります。

この設計図を**クラス**と呼びます。

![んvjdんjf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/b07a5590-65b3-3493-e0c9-b2f0bd6867b3.png)

```ruby
#クラス(設計図)を作る
class Human
end
```
そしてこの**クラス(設計図)をもとに実際に誕生した人間=オブジェクト/モノ**を**インスタンス**と呼びます。

![人間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/e249a039-731e-ca25-7032-a3929d3bf8d9.png)


```ruby
#1.クラス(設計図)を作る
class Human
end

#2.クラス(設計図)をもとに人間(インスタンス)を誕生させる
Human.new
```

# 神は全てを操れる
神は設計図である**Humanクラス**を作り、`Human.new`でこの世に**オブジェクト**を誕生させました。

一方で、**クラス**には現状何の情報も無いため、誕生させても人間の形をした空っぽのオブジェクトになってしまいます。

![figure_standcd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/cba2686d-55d9-e530-a0f1-b859d36dce89.png)



### 神「つまらん。」


そこで神は、この空っぽ人間を誕生させる時に、**名前**と**性別**という情報を与えることにしました。


```ruby
class Human
  #Human.newの際に実行される
  def initialize(name, gender)
    @name = name
    @gender = gender
  end
end

takahashi = Human.new("高橋", "男")
satoh = Human.new("佐藤", "女")
```

https://pikawaka.com/curriculums/programming-introduction/ruby-class#initialize%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89

こうして無事に、**名前と性別という情報を持ったHumanクラスのインスタンス**を誕生させることが出来ました。

Rubyでは、この情報のことを`インスタンス変数`と呼んでいます。

![count_boy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/a9cda443-0272-032e-f7cf-081bc6a2b884.png)
![count_girl.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/619d1863-67a7-12f9-54f7-9535fd0ff2fb.png)


さっそく神は作った人間を操作することにしました。

### 神「高橋、佐藤と喋ってくれ〜」

### 高橋「......」

![count_boy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/a9cda443-0272-032e-f7cf-081bc6a2b884.png)

### 神「佐藤、何か食べてくれ〜」

### 佐藤「......」

![count_girl.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/619d1863-67a7-12f9-54f7-9535fd0ff2fb.png)

### 神「ミスった。」
そう、神はお間違いになられました。

今の人間インスタンスは情報を持っただけで、食べたり、会話したりする**反応をすることが出来ません。**

取り急ぎ、神は設計図を書き直すことにしました。

```ruby
class Human
  def initialize(name, gender)
    @name = name
    @gender = gender
  end

  def talk_with(name)
    puts "今から#{name}と話すよー"
  end

  def eat(food)
    puts "今から#{food}を食べるよー"
  end
end

takahashi = Human.new("高橋", "男")
satoh = Human.new("佐藤", "女")

takahashi.talk_with("佐藤")
satoh.eat("焼肉")
```
https://pikawaka.com/curriculums/programming-introduction/ruby-class#%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E6%AF%8E%E3%81%AB%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86


### 神「高橋、佐藤と喋ってくれ〜 `takahashi.talk_with("佐藤")`」

### 高橋「今から佐藤と話すよー」

![count_boy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/a9cda443-0272-032e-f7cf-081bc6a2b884.png)

### 神「佐藤、焼肉を食べてくれ〜 `satoh.eat("焼肉")`」

### 佐藤「今から焼肉を食べるよー」

![count_girl.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/619d1863-67a7-12f9-54f7-9535fd0ff2fb.png)

無事に神は人間たちを思い通りに操ることが出来ました。

この様に誕生させたインスタンスに反応してもらうためには、**メソッドを定義する**必要があります。

```ruby
def メソッド名
  #反応してもらう内容
end
```

このように、プログラム上で`クラス(設計図)`を作り、その`クラス(設計図)`を基に`インスタンス(オブジェクト)`を誕生させ、`インスタンス変数(情報)`や`メソッド(反応)`を追加し操作することでプログラム`(世界)`を完成させていきます。

このような流れでプログラムを行うことを**オブジェクト指向プログラミング**と呼びます。




### 神「オブジェクト指向で色んな世界を作るんじゃ」

![internet_god.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/cbb272b7-cebe-8fce-decb-4106894c7f6f.png)



# これまでのまとめ
1. **世界=プログラム**を作るには、**設計図=クラス**と設計図から作られた**モノ=インスタンス**を準備する必要がある

2. 設計図から作られた**モノ=インスタンス**には、情報と、反応してもらうために**メソッド**を定義することができる
2. **オブジェクト指向プログラミング**とは、プログラム上で`クラス(設計図)`を作り、その`クラス(設計図)`を基に`インスタンス(オブジェクト)`を誕生させ、`インスタンス変数(情報)`や`メソッド(反応)`を追加し操作することでプログラム`(世界)`を完成させていくこと



# 神は暇なのでジャンケンができる世界を作ってみた

### 神「人間がジャンケンできる世界を作るんじゃ！」

神は早速作ることにしました。

ここで先ほどのルールから、ジャンケンに**必要な設計図は何か**を考えることにしました。

![janken_boys.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/e9c19ea4-9e64-4491-6d31-067acb727103.png)

### 神「じゃんけんというゲームとジャンケンができる人間を用意すれば出来そうじゃ」


そして、神はこのジャンケンを楽しくするためにいくつかの**ルール**を考えました。

### ルール

- じゃんけんの勝負回数を1,3,5回と最初に選択できること
- 1人の人間を操作することができ、もう一方の人間はランダムで手を出してくる
- あいこの場合は、勝負回数に含まれずに仕切り直しにされること
- 最終的な結果発表をすること

### 神「早速設計図から作るのじゃ」

```ruby
class Janken

end

class Human

end

class Cpu

end
```

そして、雛形を作ってジャンケンができそうか一旦確認してみることにしました。

### 神「設計図から人間とじゃんけんゲームを誕生させるのじゃ」


```ruby
class Janken
  def initialize
    @game_rounds = 1        # 1回で仮置き
    @human = Human.new      # 人間プレイヤーのインスタンスを作成
    @cpu = Cpu.new          # CPUプレイヤーのインスタンスを作成
  end

  def start
    puts "今回の勝負回数は#{1}回だよ、ジャンケン始めるよー"
  end
end

class Human
  def initialize
    puts " 人間だよーじゃんけん始める準備はできたよ"
  end
end

class Cpu
  def initialize
    puts " CPUだよーじゃんけん始める準備はできたよ"
  end
end

janken = Janken.new
janken.start

# =>  人間だよーじゃんけん始める準備はできたよ
# =>  CPUだよーじゃんけん始める準備はできたよ
# =>  今回の勝負回数は1回だよ、ジャンケン始めるよー
```

### 神「早速ルールを作るのじゃ」

## ルール1.ジャンケンの勝負回数を1,3,5回と最初に選択できること

次に勝負回数を選択できる様にしていきます。

先ほど設定した`start`の中に書いていきましょう。


```ruby
class Janken
  GAME_COUNT = [1,3,5]
  def initialize
    @game_rounds = 0       
    @human = Human.new      # 人間プレイヤーのインスタンスを作成
    @cpu = Cpu.new          # CPUプレイヤーのインスタンスを作成
  end

  def start
    # 勝負回数を選択できる
    puts "何本勝負？(press #{GAME_COUNT.join(' or ')})"
    game_rounds = gets.to_i
    if GAME_COUNT.include?(game_count)
      @game_rounds = game_rounds
      puts "#{@game_rounds}本勝負を選びました。"
    else
      puts "#{GAME_COUNT.join(' か ')}で入力してください"
      start
    end
  end
end

# => 人間だよーじゃんけん始める準備はできたよ
# => CPUだよーじゃんけん始める準備はできたよ
# => 何本勝負？(press 1 or 3 or 5)
# => 1
# => 1本勝負を選びました。
```

### 神「いい感じじゃ。」


# じゃんけんゲームを作る

次に実際にジャンケンの中身を作っていきましょう。

```ruby
  def start
    # 勝負回数を選択できる
    puts "何本勝負？(press #{GAME_COUNT.join(' or ')})"
    game_rounds = gets.to_i
    if GAME_COUNT.include?(game_count)
      @game_rounds = game_rounds
      puts "#{@game_rounds}本勝負を選びました。"
      #ジャンケンの中身
    else
      puts "#{GAME_COUNT.join(' か ')}で入力してください"
      start
    end
  end
```

## ルール2.人間を操作することができ、もう一方の人間はランダムで手を出してくる
`game_count = gets.to_i`で入力された値を`@game_rounds.times do |round| ~ end`で回数分処理を行う様にしています。

```ruby
    def start
      puts "何本勝負？(press #{GAME_COUNT.join(' or ')})"
      game_count = gets.to_i
      if GAME_COUNT.include?(game_count)
        @game_rounds = game_count
        puts "#{@game_rounds}本勝負を選びました。"
        @game_rounds.times do |round|
          # 回数分じゃんけんが対戦できる
        end
      else
        puts "#{GAME_COUNT.join(' か ')}で入力してください"
        start
      end
    end
```

次に実際に対戦できる様に作っていきます。
まずは、`人間`と`Cpu`がそれぞれ手を出せる様にします。

```ruby
class Janken
  GAME_COUNT = [1,3,5]
  HANDS = { 'g' => 0, 'c' => 1, 'p' => 2 }
  
  def initialize
    @game_rounds = 0       
      @human = Human.new      
      @cpu = Cpu.new          
    end
  
    def start
      puts "何本勝負？(press #{GAME_COUNT.join(' or ')})"
      game_count = gets.to_i
      if GAME_COUNT.include?(game_count)
        @game_rounds = game_count
        puts "#{@game_rounds}本勝負を選びました。"
        @game_rounds.times do |round|
          # 回数分じゃんけんが対戦できる
          human_hand = @human.choose_hand
          cpu_hand = @cpu.choose_hand
          puts "#{round + 1}回戦: あなたの手: #{human_hand}, CPUの手: #{HANDS.key(cpu_hand)}"
        end
      else
        puts "#{GAME_COUNT.join(' か ')}で入力してください"
        start
      end
    end
  end
  
  class Human
    def initialize
      puts "人間だよーじゃんけん始める準備はできたよ"
    end
  
    def choose_hand
      puts "g、c、pから選んでください:"
      challenge_hand = gets.chomp
      if Janken::HANDS.key?(challenge_hand)
        challenge_hand
      else
        puts "入力が不正です。もう一度選んでください。"
        choose_hand
      end
    end
  end
  
  class Cpu
    def initialize
      puts "CPUだよーじゃんけん始める準備はできたよ"
    end
  
    def choose_hand
      Janken::HANDS.keys.sample
    end
  end
```

人間側には`@human.choose_hand`でジャンケンする手を選択できる様にしています。
CPU側には`@cpu.choose_hand` 内で、`Janken::HANDS.values.sample`とすることで、ランダムに出す手を出力する様にしています。


### 神「人間よ、ジャンケンの手を選んでやるのじゃ！`@human.choose_hand`」

### 神「チョキじゃ！(ターミナルで`c`と入力)`chosen_hand = gets.chomp`」

### 人間 「チョキ」
![otoko.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/156e2f9d-2b61-25d7-3ca5-2f012fcdc702.png)


## ルール3.あいこの場合は、勝負回数に含まれずに仕切り直しにされること

結果を表示させる前に、あいこのケースを実装していきます。

あいこの場合は、勝負回数に含まれず勝敗が着くまで、対戦できるようにします。


```ruby
class Janken
  GAME_COUNT = [1,3,5]
  HANDS = { 'g' => 0, 'c' => 1, 'p' => 2 }
  
  def initialize
    @game_rounds = 0       
    @human = Human.new      
    @cpu = Cpu.new
  end
  
  def start
    puts "何本勝負？(press #{GAME_COUNT.join(' or ')})"
    game_count = gets.to_i
    if GAME_COUNT.include?(game_count)
      @game_rounds = game_count
      puts "#{@game_rounds}本勝負を選びました。"
      actual_round = 1
      while actual_round <= @game_rounds
        puts "#{actual_round}回戦"
        human_hand = @human.choose_hand
        cpu_hand = @cpu.choose_hand
        puts "あなたの手: #{human_hand}, CPUの手: #{cpu_hand}"
        result = (HANDS[human_hand] - HANDS[cpu_hand]) % 3
        # 余りが0の場合は引き算の結果が3の倍数、つまり両者の手が同じ（あいこ）。
        # 余りが1の場合はCPUの勝ち。
        # 余りが2の場合はHumanの勝ち。
        if result == 1
          puts "あなたの負けです。"
        elsif result == 2
          puts "あなたの勝ちです！"
        else
          puts "あいこでした。もう一度じゃんけんします。"
          next  # あいこの場合は次のラウンドへは進まず、同じラウンドを再実行
        end
          actual_round += 1
      end
    else
      puts "#{GAME_COUNT.join(' か ')}で入力してください"
      self.start
    end
  end
end
  
class Human
  def initialize
    puts "人間だよーじゃんけん始める準備はできたよ"
  end
  
  def choose_hand
    puts "g、c、pから選んでください:"
    challenge_hand = gets.chomp
    if Janken::HANDS.key?(challenge_hand)
      challenge_hand
    else
      puts "入力が不正です。もう一度選んでください。"
      choose_hand
    end
  end
end
  
class Cpu
  def initialize
    puts "CPUだよーじゃんけん始める準備はできたよ"
  end
  
  def choose_hand
    Janken::HANDS.keys.sample
  end
end
```

## ルール4.最終的な結果発表をすること

次に最終的な結果を表示させます。


```ruby
class Janken
  GAME_COUNT = [1,3,5]
  HANDS = { 'g' => 0, 'c' => 1, 'p' => 2 }
  
  def initialize
    @game_rounds = 0       
    @human = Human.new      
    @cpu = Cpu.new 
    @human_score = 0 #追加
    @cpu_score = 0 #追加
  end
  
  def start
    puts "何本勝負？(press #{GAME_COUNT.join(' or ')})"
    game_count = gets.to_i
    if GAME_COUNT.include?(game_count)
      @game_rounds = game_count
      puts "#{@game_rounds}本勝負を選びました。"
      actual_round = 1
      while actual_round <= @game_rounds
        puts "#{actual_round}回戦"
        human_hand = @human.choose_hand
        cpu_hand = @cpu.choose_hand
        puts "あなたの手: #{human_hand}, CPUの手: #{cpu_hand}"
        result = (HANDS[human_hand] - HANDS[cpu_hand]) % 3
        # 余りが0の場合は引き算の結果が3の倍数、つまり両者の手が同じ（あいこ）。
        # 余りが1の場合はCPUの勝ち。
        # 余りが2の場合はHumanの勝ち。
        if result == 1
          puts "あなたの負けです。"
            @cpu_score += 1
        elsif result == 2
          puts "あなたの勝ちです！"
            @human_score += 1
        else
          puts "あいこでした。もう一度じゃんけんします。"
          next  # あいこの場合は次のラウンドへは進まず、同じラウンドを再実行
        end
          actual_round += 1
      end
      # 最終的な結果を表示
      puts '結果'
      puts "#{@human_score}勝#{@cpu_score}敗であなたの#{ @human_score > @cpu_score ? '勝ち' : '負け' }です。"
    else
      puts "#{GAME_COUNT.join(' か ')}で入力してください"
      self.start
    end
  end
end
```

### 神「完成じゃ!!!!!」


[![Image from Gyazo](https://i.gyazo.com/98679ef5717d6d87fe523a9366af6b20.gif)](https://gyazo.com/98679ef5717d6d87fe523a9366af6b20)


![internet_god.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/d4e81a18-bcf1-435f-c68c-7f03d8c50e49.png)


# 神気付く
喜びも束の間、神は何かの違和感を感じました。

![internet_god_kamieshi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/e8d9d6d6-a498-d88d-1acc-6f6a126daa5d.png)



```ruby
def start
  puts "何本勝負？(press #{GAME_COUNT.join(' or ')})"
  game_count = gets.to_i
  if GAME_COUNT.include?(game_count)
    @game_rounds = game_count
    puts "#{@game_rounds}本勝負を選びました。"
    actual_round = 1
    while actual_round <= @game_rounds
      puts "#{actual_round}回戦"
      human_hand = @human.choose_hand
      cpu_hand = @cpu.choose_hand
      puts "あなたの手: #{human_hand}, CPUの手: #{cpu_hand}"
      result = (HANDS[human_hand] - HANDS[cpu_hand]) % 3
      if result == 1
        puts "あなたの負けです。"
        @cpu_score += 1
      elsif result == 2
        puts "あなたの勝ちです！"
        @human_score += 1
      else
        puts "あいこでした。もう一度じゃんけんします。"
        next  # あいこの場合は次のラウンドへは進まず、同じラウンドを再実行
      end
        actual_round += 1
    end
      puts '結果'
      puts "#{@human_score}勝#{@cpu_score}敗であなたの#{ @human_score > @cpu_score ? '勝ち' : '負け' }です。"
  else
    puts "#{GAME_COUNT.join(' か ')}で入力してください"
    self.start
  end
end
```

### 神「なんか処理長くね、、？？」

そうです、今の`start`はゲームカウントを入力させ、実際に対戦を行い、あいこの場合は再度対戦させ、最終的な結果を表示させるというたくさん役割のあるメソッドになっています。

### 神「他の神に見られたら、Janken.startだけで何してるかわからないな...」

`Janken.start`とは書かれているものの、ロジックを最後まで読まないと何をしているかがわからない！という非常にストレスがかかる書き方になってしまっています。

ここに来てようやく、神は**ある原則**のオブジェクト指向に基づいて世界を作るということを忘れていました。

# SOLID原則


### 神「SOLID原則じゃ、、！！」

![slide_3.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/055dc26c-0da4-94e8-0871-3951901e8efe.jpeg)
[引用先: 「SOLIDの原則って何ですか？」って質問に答えたかった / What's SOLID principle](https://speakerdeck.com/nazonohito51/whats-solid-principle)

**SOLID原則**とはオブジェクト指向を設計する上で、**柔軟で保守可能なコードを実現するための5つの原則の頭文字から取った総称**です。

神はこの原則に基づいてコードを修正していくことにしました。

### 神「修正じゃ」

## S (Single Responsibility)　単一責任の原則
クラスや関数は**責任は一つにしておくべき**という原則です。

今回の例では、`start`メソッドに

**1.対戦回数を選択する役割**
**2.じゃんけんを行う役割**
**3.最終的な結果を表示させる役割**

とメソッド1つに対して、多くの役割を持たせてしまっております。
多くの役割を持つことは、影響範囲が大きく知らないうちに他の責任に影響を与える可能性が高くなります。

```ruby
class Janken
    GAME_COUNT = [1,3,5]
    HANDS = { 'g' => 0, 'c' => 1, 'p' => 2 }
  
    def initialize
      @game_rounds = 0       
      @human = Human.new      
      @cpu = Cpu.new 
      @cpu_score = 0 
      @human_score = 0         
    end
  
    def start
      choose_game_count
      play_rounds
      display_result
    end
  
    private
  
    def choose_game_count
      puts "何本勝負？(press #{GAME_COUNT.join(' or ')})"
      game_count = gets.to_i
      if GAME_COUNT.include?(game_count)
        @game_rounds = game_count
        puts "#{@game_rounds}本勝負を選びました。"
      else
        puts "#{GAME_COUNT.join(' か ')}で入力してください"
        choose_game_count
      end
    end
  
    def play_rounds
      actual_round = 1
      while actual_round <= @game_rounds
        puts "#{actual_round}回戦"
        play_single_round
        actual_round += 1
      end
    end
  
    def play_single_round
      human_hand = @human.choose_hand
      cpu_hand = @cpu.choose_hand
      puts "あなたの手: #{human_hand}, CPUの手: #{cpu_hand}"
      determine_winner(human_hand, cpu_hand)
    end
  
    def determine_winner(human_hand, cpu_hand)
      result = (HANDS[human_hand] - HANDS[cpu_hand]) % 3
      if result == 1
        puts "あなたの負けです。"
        @cpu_score += 1
      elsif result == 2
        puts "あなたの勝ちです！"
        @human_score += 1
      else
        puts "あいこでした。もう一度じゃんけんします。"
        play_single_round
      end
    end
  
    def display_result
      puts '結果'
      puts "#{@human_score}勝#{@cpu_score}敗であなたの#{ @human_score > @cpu_score ? '勝ち' : '負け' }です。"
    end
  end
```

### 神「少し読みやすくなったぞ」


# O (Open-Closed)　オープン・クローズドの原則
`Human`クラスと`Cpu`クラスは処理は違いますが、`choose_hand`という同じメソッド名を使用しています。

今後グーだけを出すプレイヤーを追加したい場合も考えると、元となる`Player`クラスを用意することで、元となるクラスのメソッドをオーバーライドすることで保守性がより高まります。

```ruby
class Janken
    GAME_COUNT = [1,3,5]
    HANDS = { 'g' => 0, 'c' => 1, 'p' => 2 }
  
    def initialize(human, cpu)
      @game_rounds = 0       
      @human = human  #変更    
      @cpu = cpu  #変更    
      @human_score = 0 
      @cpu_score = 0         
    end
  
    def start
      choose_game_count
      play_rounds
      display_result
    end
  
    private
  
    def choose_game_count
      #省略
    end
  
    def play_rounds
      #省略
    end
  
    def play_single_round
      #省略
    end
  
    def determine_winner(human_hand, cpu_hand)
      #省略
    end
  
    def display_result
      #省略
    end
  end

  class Player
    def initialize
    end

    def choose_hand
      "g"
    end
  end
  
  class Human < Player
    def initialize
      puts "人間だよーじゃんけん始める準備はできたよ"
    end
  
    def choose_hand
      puts "g、c、pから選んでください:"
      challenge_hand = gets.chomp
      if Janken::HANDS.key?(challenge_hand)
        challenge_hand
      else
        puts "入力が不正です。もう一度選んでください。"
        choose_hand
      end
    end
  end
  
  class Cpu < Player
    def initialize
      puts "CPUだよーじゃんけん始める準備はできたよ"
    end
  
    def choose_hand
      Janken::HANDS.keys.sample  
    end
  end
  
  human = Human.new
  cpu = Cpu.new
  janken = Janken.new(human, cpu)
  janken.start
```

# L (Liskov Substitution)　リスコフの置換原則

オブジェクト指向プログラミングにおいて、子クラスのオブジェクトは親クラスのオブジェクトの仕様に従わなければならない、という原則です。

現状はあえて親クラスの`Player`のchoose_handメソッドに`g`を返すように指定しています。

しかし、それらの子クラスである`Human`、`Cpu`では同じメソッドでも中身の処理(振舞い)が異なります。

その為、`リスコフの置換原則`では、**あるクラス(Player)を継承するとき、継承元(Human、CPU)と継承先のクラス(Player)の振る舞いを同じにしよう**ということです。

**親クラスで処理ができることは、子クラスでも同じ処理ができなければならない**という原則です。

今回は`Player`クラスのメソッドを抽象メソッドにすることでこの置換原則に則っています。


```ruby
  class Player
    def initialize
    end
  
    def choose_hand
      raise NotImplementedError, 'Subclasses must implement abstract_method'
    end
  end
```

https://zenn.dev/chiki0320/articles/ec3343baae446f


# I (Interface Segregation)　インターフェイス分離の原則

**継承先で使わないメソッドがないようにきちんとメソッドは分けよう**という原則です。

例として`Player`クラスに`greet`メソッドを追加で定義しています。

`Human`クラスでは`greet`メソッドが実装されていません。

一方、`Cpu`クラスでは`greet`メソッドが実装されています。
これは、Humanクラスが使わないgreetメソッドの実装も継承できてしまう状況でありインターフェース分離の原則に違反しています。

したがって、`greet`メソッドは`Cpu`クラスにのみ定義することでこの問題を解決することができます。

例として説明しましたが、今回は`リスコフの置換原則`に基づいて修正した段階で、こちらの原則は満たされていた為、特に変更はありません。


```ruby
  class Player
    def initialize
    end
  
    def choose_hand
        raise NotImplementedError, 'Subclasses must implement abstract_method'
    end

    def greet
      raise NotImplementedError, 'Subclasses must implement abstract_method'
    end
  end
  
  class Human < Player
    def initialize
      puts "人間だよーじゃんけん始める準備はできたよ"
    end
  
    def choose_hand
      puts "g、c、pから選んでください:"
      challenge_hand = gets.chomp
      if Janken::HANDS.key?(challenge_hand)
        challenge_hand
      else
        puts "入力が不正です。もう一度選んでください。"
        choose_hand
      end
    end
  end
  
  class Cpu < Player
    def initialize
      puts "CPUだよーじゃんけん始める準備はできたよ"
    end
  
    def choose_hand
      Janken::HANDS.keys.sample  
    end

    def greet
      puts "CPUです"
    end
  end
```

# D (Dependency Inversion)　依存性逆転の原則


今回のじゃんけんは、プレイヤーに手を選択してもらうために`choose_hand`メソッドを呼び出しますが、`Playerクラス`が`choose_hand`のインターフェースを持っていて、それを各サブクラスが継承して実装しています。

通常の依存関係だと、じゃんけんのロジックは具体的な実装（`Human`や`Cpu`）に依存してしまう。しかし「**依存関係逆転**」の原則によって、それが逆転していて、ロジックは`Player`クラスという抽象的なインターフェースに依存し、具体的なサブクラスの実装の詳細は気にせずに実装を行えます。

今回も`リスコフの置換原則`に基づいて修正した段階で、こちらの原則は満たされていた為、特に変更はありません。





### 神「この辺りはさらに勉強が必要じゃ」


# じゃんけんの完成

### 神「ようやく完成じゃ！！！」

```ruby
class Janken
    GAME_COUNT = [1,3,5]
    HANDS = { 'g' => 0, 'c' => 1, 'p' => 2 }
  
    def initialize(human, cpu)
      @game_rounds = 0       
      @human = human  #変更    
      @cpu = cpu  #変更    
      @human_score = 0 
      @cpu_score = 0         
    end
  
    def start
      choose_game_count
      play_rounds
      display_result
    end
  
    private
  
    def choose_game_count
      puts "何本勝負？(press #{GAME_COUNT.join(' or ')})"
      game_count = gets.to_i
      if GAME_COUNT.include?(game_count)
        @game_rounds = game_count
        puts "#{@game_rounds}本勝負を選びました。"
      else
        puts "#{GAME_COUNT.join(' か ')}で入力してください"
        choose_game_count
      end
    end
  
    def play_rounds
      actual_round = 1
      while actual_round <= @game_rounds
        puts "#{actual_round}回戦"
        play_single_round
        actual_round += 1
      end
    end
  
    def play_single_round
      human_hand = @human.choose_hand
      cpu_hand = @cpu.choose_hand
      puts "あなたの手: #{human_hand}, CPUの手: #{cpu_hand}"
      determine_winner(human_hand, cpu_hand)
    end
  
    def determine_winner(human_hand, cpu_hand)
      result = (HANDS[human_hand] - HANDS[cpu_hand]) % 3
      if result == 1
        puts "あなたの負けです。"
        @cpu_score += 1
      elsif result == 2
        puts "あなたの勝ちです！"
        @human_score += 1
      else
        puts "あいこでした。もう一度じゃんけんします。"
        play_single_round
      end
    end
  
    def display_result
      puts '結果'
      puts "#{@human_score}勝#{@cpu_score}敗であなたの#{ @human_score > @cpu_score ? '勝ち' : '負け' }です。"
    end
  end

  class Player
    def initialize
    end
  
    def choose_hand
      raise NotImplementedError, 'Subclasses must implement abstract_method'
    end
  end
  
  class Human < Player
    def initialize
      puts "人間だよーじゃんけん始める準備はできたよ"
    end
  
    def choose_hand
      puts "g、c、pから選んでください:"
      challenge_hand = gets.chomp
      if Janken::HANDS.key?(challenge_hand)
        challenge_hand
      else
        puts "入力が不正です。もう一度選んでください。"
        choose_hand
      end
    end
  end
  
  class Cpu < Player
    def initialize
      puts "CPUだよーじゃんけん始める準備はできたよ"
    end
  
    def choose_hand
      Janken::HANDS.keys.sample  
    end
  end
  
  human = Human.new
  cpu = Cpu.new
  janken = Janken.new(human, cpu)
  janken.start
```

# 最後に
オブジェクト指向を意識して今後も柔軟なコードをかけるかけるようにしていきたいです。

最後まで読んでいただきありがとうございました！


# 参考

https://qiita.com/baby-degu/items/d058a62f145235a0f007

https://qiita.com/hirodragon/items/090467284d19a4cbafa0

https://qiita.com/engineer_ryoma/items/c47b387dd93a0dfc8ec1

https://speakerdeck.com/nazonohito51/whats-solid-principle