オブジェクト指向プログラミングを意識と例外処理を使った電卓機能を作ってみました！：コード説明あり

# オブジェクト指向を学習して理解を深めたい部分を共有します
今回は、題名にもある通りオブジェクト指向プログラミングを意識した電卓機能アプリを作ってみました！

とても小さなアプリですが、オブジェクト指向とデメテルの法則を意識して作ったのでアウトプットしていきます！

## 最初にこの記事をご覧になるとどんなことがわかるのかを紹介します
1. 単一責任のクラス
1. デメテルの法則
1. 柔軟なインターフェイス
1. 他クラスからのメソッドを使用する際に依存関係を最小限に抑えることができる
1. カスタムエラーの作り方と例外処理のやり方

**これらを意識してコードを書きました！** 
## 参考にした本を紹介

https://www.amazon.co.jp/dp/B01L8SEVYI/

## デメテルの法則について軽く説明
**オブジェクトを疎結合にするためのコーディング規則の集まり。**
オブジェクト指向設計において、
オブジェクトが他のオブジェクトとどのようにコミュニケーションするかに関連する原則。
「直接の隣人にのみ話しかけよう」なんて言い方もされます。

:::note
**オブジェクトが他のオブジェクトの内部構造に深く依存しないようにすることが目的である**
:::

:::note alert
デメテルの法則に反している例とすると
**バケツリレーみたいにどんどんと情報を流れるのは良くない**
ドットが2個以上になることですね。
:::

## どのぐらいファイル分割してクラス分けしたのか
全部で６ファイルあります。電卓ができる機能だけなのに６ファイル！

1. main.rb(メインの実行ファイル)
1. calculator.rb(計算機で計算方法をそれぞれのメソッド定義したファイル)
1. calculation.rb(計算方法を決めるファイル)
1. input_column.rb(実際にユーザー入力方法を記述したファイル)
1. computation_run.rb(計算を実行するファイル)
1. custom_error.rb(カスタムエラー専用のファイル)

だいぶ小分けしましたですがその分依存関係も浅く再利用性のあるコードが書けたと思います！

# 今からファイル別にコード説明と何故そうしたかを書きます

### main.rb
```ruby:main.rb
require './calculation'
require './custom_error'
require './input_column'
require './computation_run'

ComputationRun.execute

```
外部ファイルを呼び込んでます。
小分けする前は１２０行近くあったと思います。
今は6行です。
`ComputationRun.execute`この1行に全てがつまってます！

### calculator.rb

```ruby:calculator.rb
# 計算機クラスで計算方法をそれぞれのメソッドに入れてます
class Calculator
  def initialize(num1:, num2:)
    @num1 = num1
    @num2 = num2
  end

  def add
    @num1 + @num2
  end

  def subtract
    @num1 - @num2
  end

  def multiply
    @num1 * @num2
  end

  def divide
    raise ZeroDivisionError, 'ゼロによる割り算は許可されていません' if @num2.zero?

    @num1 / @num2
  end
end

```

ここは四則演算にて計算方法を作ってます。

いたってシンプルですが、割り算の部分だけ特定の入力をしたらエラーが出る様にしてます。
後々説明しますが、ここで例外処理の一部ガード節を使って、簡潔に書いています。

後半の数値が0だったら、Infinityとして割り切れないのでエラーを出しましょうということですね。

:::note
**ガード節とは？**
処理の対象外とする条件を、関数やループの先頭に集めて return や continue/break で抜ける方法です
3行かかるところが1行でかけます。
:::

これらを踏まえて説明すると、もしnum2が０であればコードを中断させ
例外の`ZeroDivisionError`を発生しエラーメッセージの**ゼロによる割り算は許可されていません。** と処理されます。

### calculation.rb

```ruby:calculation.rb
require './calculator'

# 計算方法を決めるクラスです
class Calculation
  def initialize(num1:, num2:, operator:)
    @calculator = Calculator.new(num1: num1, num2: num2)
    @operator = operator
  end

  
  def selection
    case @operator
    when '+'
      @calculator.add
    when '-'
      @calculator.subtract
    when '*'
      @calculator.multiply
    when '/'
      @calculator.divide
    end
  end
end
```

`require './calculator'`ここでcalculator.rbファイルを読み込んでます

```ruby
def initialize(num1:, num2:, operator:)
    @calculator = Calculator.new(num1: num1, num2: num2)
    @operator = operator
end
```
次から
Calculator => 計算機クラス
Calculation => 計算方法を決めるクラス
とします。

`initializeメソッド`は、計算方法を決めるクラスのインスタンスが**new**された時に実行され、３つのキーワード引数(num1:, num2:, operator:)を受け取ります。

`initializeメソッド`内で計算機クラスのインスタンス生成して、それをインスタンス変数に格納しています
これにより、計算機クラスのnum1,num2を計算方法を決めるクラスに使用できる様になります。

`@operator = operator`これはCalculationクラスの引数の一つで演算子を入力する引数です。
それをインスタンス変数に格納してます。
これにより、計算方法を決めるクラスのインスタンス内で演算子(+、-、*、/)を参照できます。


```ruby:計算方法を決めるクラス
def selection
    case @operator
    when '+'
      @calculator.add
    when '-'
      @calculator.subtract
    when '*'
      @calculator.multiply
    when '/'
      @calculator.divide
    end
end
```

selectionメソッドを作り、`@operator`に入った四則演算を割り当てそれぞれの計算方法を特定しています
例えば：　`@operator`の中に'+'が入れば計算機クラスのaddメソッドが使われます。
```ruby:計算機クラス
def add
    @num1 + @num2
end
```
このメソッド↑ですね！
これにより足し算ができます！

### custom_error.rb

```ruby:custom_error.rb
class NoNumberError < StandardError
  def initialize
    super('num1とnum2は数値を入力してください、空欄も許可されていません')
  end
end


class NoMatchOperatorError < StandardError
  def initialize
    super('演算子には  +、-、*、/ のいずれかを使用してください')
  end
end

```

カスタムエラーを作る際は`StandardError`を継承しなければなりません。
**`initialize`メソッドのsuperエラーメッセージを渡せます。**


自分でメソッド(例えば：message)作ってオーバーライドしputsで出力も可能
あまり意味ないですが。。。

```ruby
def message
    puts 'それは違うよ'
end
```
:::note
なんで`ZeroDivisionError`はないの？？？
`ZeroDivisionError`はRubyが元々準備してくれてる
メソッドなので書く必要が無いです！
:::

### input_column.rb

```ruby:input_column.rb
# ユーザー入力欄クラスです。
class InputColumn
  def self.get_input(description)
    print description
    gets.chomp
  end

  def self.get_number(description)
    loop do
      input = get_input(description)
      return input.to_f if input.match(/\A-?\d+(\.\d+)?\z/) 

      raise NoNumberError
    rescue NoNumberError => e
      puts e
    end
  end

  def self.get_operator(description)
    loop do
      input = get_input(description)
      return input if ['+', '-', '*', '/'].include?(input)

      raise NoMatchOperatorError
    rescue NoMatchOperatorError => e
      puts e
    end
  end
end
```

こちらのファイルはインスタンスを作成せずに使用するためのクラスです。
メソッドの先頭に`self`とつける事でクラスメソッドになります。

:::note
**クラスメソッドとインスタンスメソッドの違い**
インスタンスメソッドはインスタンスを作成しなければ使用できませんが

クラスメソッドというのは直接クラスを
参照しインスタンスを作成しなくてもクラス内のメソッドが使えます。
:::

クラスメソッドは、**特定のインスタンスに依存せず、汎用的な処理を行う場合**に使用されます。

```ruby
def self.get_input(description)
    print description
    gets.chomp
end
```
get_inputクラスメソッドは引数に`description`を受け取ります。

例えば、`**get_input(説明が入るよ)**`とこの様にメソッドを読んであげると

`print 説明が入るよ`
gets.chomp
とこの様な感じで出力されます。

:::note
なぜputsじゃなくてprintなの？？？
`puts`だと自動に改行されてしまい、`print`だと改行されない状態で出力されます。
自分で確かめてみてください！！！

ユーザーにとって見やすいインターフェイスを心掛けましょう！
:::
私の場合、下に入力を促すより横に入力を促した方が見やすいと思いました。

```ruby
def self.get_number(description)
    loop do
      input = get_input(description)
      return input.to_f if input.match(/\A-?\d+(\.\d+)?\z/) # ガード節のあとは空行を
    
      raise NoNumberError
    rescue NoNumberError => e
      puts e
    end
end
```

get_numberクラスメソッドは引数にdescriptionを受け取ります。

`loop do`により条件が合わなければ振り出しに戻る、ループさせる様にしました

`input = get_input(description)`
これは先程のget_inputメソッドの戻り値(ユーザーが入力した値)をinput変数に格納しました。

`return input.to_f if input.match(/\A-?\d+(\.\d+)?\z/)`

ガード節を使用し、`input.to_f`には実際入力されるもの`gets.chomp.to_f`が入り

`input.match(/\A-?\d+(\.\d+)?\z/)`
**入力された物のバリデーションを行います。**
`/\A-?\d+(\.\d+)?\z/`は入力が整数または小数がマッチするかどうかをチェックする正規表現になります
要するにそれ以外の入力でマッチしないのは`NoNumberError`が発生します。

```ruby
raise NoNumberError
rescue NoNumberError => e
puts e
```
ここで出てきました例外処理です
今回の場合、自分でカスタムしたエラーを読んできています。カスタムエラーについては後ほど説明します。

**get_operatorクラスメソッド**

```ruby
def self.get_operator(description)
    loop do
      input = get_input(description)
      return input if ['+', '-', '*', '/'].include?(input)

      raise NoMatchOperatorError
    rescue NoMatchOperatorError => e
      puts e
    end
  end
end
```

これはほぼ先程のget_numberとほぼ一緒です
格納された`input`の `gets.chomp.to_f`が `gets.chomp`に変更(今回は文字列が入るから！)

**バリデーションチェックの内容も変わっており**

inputで渡ってきた値が'+', '-', '*', '/'いずれかであれば
trueを返すので正常であり中断する為ループから抜け出します

それ以外はループから抜け出せず、NoMatchOperatorErrorが出る

### computation_run.rb

```ruby:computation_run.rb
require './calculation'

# 計算を実行するクラスです
class ComputationRun
  class << self
    def result(num1, num2, operator)
      calculation = Calculation.new(num1: num1, num2: num2, operator: operator)

      result = calculation.selection
      puts ''
      puts "計算結果: #{num1}#{operator}#{num2} = #{result}"
    end

    def execute
      loop do
        num1, operator, num2 = user_input
        begin
          result(num1, num2, operator)
          exit_prompt = prompt('続けますか？(Y/N)')
          break if exit_prompt.upcase == 'N'
        rescue ZeroDivisionError => e
          puts e
        end
      end
    end

    private

    def prompt(message)
      print message
      gets.chomp
    end

    def user_input
      num1 = InputColumn.get_number('1番目の数値を入力してください: ')
      operator = InputColumn.get_operator('演算子(+, -, *, /)を入力してください: ')
      num2 = InputColumn.get_number('2番目の数値を入力してください: ')
      [num1, operator, num2]
    end
  end
end
```
`class << self`と囲ってあげる事によりメソッドの先頭に`self`と付けなくてもクラスメソッドとなります。
全てクラスメソッドならこっちの方がいいかもしれません。

`result`クラスメソッドについて
こちらのメソッドは計算結果を責務としています
ここでようやく、Calculationクラスのインスタンスを作成します。

名前が一緒でややこしくて申し訳ないのですがメソッド内の変数resultをcalculationクラスのselectionメソッドを
使用して計算結果を引き出しています。

`execute`クラスメソッド
こちらのメソッドは、**全てを果たします。**
メイン実行部分です。
このメソッドは、ユーザーが入力する数値や演算子を受け取り、計算結果を表示し、
続行するかどうかを尋ねるループを実行します。
**今回の電卓アプリの中枢となる部分です。**

**わかりやすく言いますと全てのメソッドが色々な経路で集結しています！**

コード説明なのですが、見た感じそのまんまですね！
後ほど説明しますが、user_inputクラスメソッドの配列を受け取ってます。

```ruby
begin
    result(num1, num2, operator)
    exit_prompt = prompt('続けますか？(Y/N)')
    break if exit_prompt.upcase == 'N'
rescue ZeroDivisionError => e
    puts e
end
```
先程のresultメソッドを受け取ってます。

例外処理について説明ます
`begin`から始まり
`rescue`でエラーを選択しています。
途中途中あった`raise`はここで処理を実行しますという意味があります。
なぜ`ZeroDivisionError`しかないと言いますと

例えば
```ruby
def divide
    raise ZeroDivisionError, 'ゼロによる割り算は許可されていません' if @num2.zero? # ガード節
    
    @num1 / @num2
  rescue ZeroDivisionError => e
    puts e
  end
```
この様に書いてしまうと、エラーメッセージが出てからloopが始まりますのでちょっと変な感じになってしまいます

![１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124781/06bc1b71-ed71-e9c8-1660-685af8c246e3.png)





:::note
ちなみに例外処理とは？？？
予期しないエラーが起きた際に、適切な対処を行う仕組みです。
今回のNoNumberErrorの場合、num1num2のどちらかに数値以外が入力されたとします
例外処理がないと、**ユーザーにとって意味がわからないままプログラムが止まってしまいます。**
この様な状態ですと、ユーザーには何が起こったのか理解できません。
なぜ止まったのかを教えるためにエラーメッセージを渡します。
このままでもいいのですが、今回私の場合失敗したらもう一回入力できる様にしました
:::

```ruby
exit_prompt = prompt('続けますか？(Y/N)')
break if exit_prompt.upcase == 'N'
```

これから紹介するメソッドに`prompt`メソッドがあります
`exit_prompt`変数に`prompt`メソッドの引数にメッセージを格納し続けるか辞めるかの選択肢を促しています。

`break if exit_prompt.upcase == 'N'`
もしNを入力したら`break`する、ループから抜け出すことができます。
upcaseメソッドを使うことで小文字のnでも大文字になる様にしました。

因みにY押すと次に進む様になっていますが、Y以外でも次に進めてしまいます。（どうしたものか）

~~それかメッセージを'Nを押すと辞めれて他のボタンを押すと続けてできます'にすればいいのか！！~~

### プライベートなインターフェイス

```ruby:computation_run.rb
private

    def prompt(message)
      print message
      gets.chomp
    end

    def user_input
      num1 = InputColumn.get_number('1番目の数値を入力してください: ')
      operator = InputColumn.get_operator('演算子(+, -, *, /)を入力してください: ')
      num2 = InputColumn.get_number('2番目の数値を入力してください: ')
      [num1, operator, num2]
    end
```

**何故こちらをプライベートなクラスメソッドにしたのか自分なりに解釈し説明します。**

共通点として、この２つのクラスメソッドは、ComputationRunクラス内でしか使われておりません。

`prompt`メソッド

試しにプライベートなクラスメソッドにしました。

`user_input`メソッド
万が一、今後２乗も計算できる機能を作るとして他のクラスからアクセスできないので
変更による副作用かなり少ないと思います。

**それに再利用する可能性も低いですから、プライベートなクラスメソッドにしました。**

その他利点
1. この様にプライベートと可視化することで将来、別の開発者一眼でわかる様になる
1. これから変更する場合、プライベートによりカプセル化されてるため、他のコードに影響を与えない
**その結果、コードの保守性と可読性が向上します。**

:::note alert
注意点！
メソッドに直接`self`をかくとプライベートがうまく活用できません。
:::

:::note
解決策は、class << selfで囲ってあげましょう！
そうすることで、上記でも説明したように`self`と書かなくてすみます
そして、privateの下にメソッドを書いてあげることで
プライベートクラスメソッドとして定義できます！
:::

### さてさてひとつ疑問が出てきました
みなさん覚えてますでしょうか？

**InputColumnクラスのget_inputクラスメソッドはInputColumnクラスでしか使われておりません！**

なぜプライベートにしないのか？
**今後再利用できる可能性が大いにあるからです。**
get_inputクラスメソッドはただ単に入力を受けるだけのメソッドです。
これから計算機だけでなく他の機能で使えそうです
他のクラスでも再利用する可能性があるため、プライベートにしない方がいいと私は思いました。
これがもしInputColumnクラス内だけに特化したメソッドで再利用することが考えにくい場合は
privateメソッドにした方が圧倒的良いでしょう。

### 本題に戻ります

`prompt`メソッドはInputColumnクラスのget_inputメソッドにほぼ一緒なので省略します

`user_input`メソッド

`num1, operator, num2`これらを配列にして**executeメソッド**で1行にしてかけるようにしました

`InputColumn.get_number('1番目の数値を入力してください: ')`

InputColumnクラスのget_numberクラスメソッドを使用していますここで
入力されたものを、バリデーションとエラーをチェックしています。

**クラスメソッドなのでインスタンスを作成せずにクラスからメソッド直接呼び出すことができます。**

```ruby
    loop do

      print '1番目の数値を入力してください: '
      input = gets.chomp
      return input.to_f if input.match(/\A-?\d+(\.\d+)?\z/) # ガード節のあとは空行を

      raise NoNumberError
    rescue NoNumberError => e
      puts e
    end
  end
```
この様な処理が`InputColumn.get_number('1番目の数値を入力してください: ')`
この1行で行われてます！

残り2行も同じ様な感じです。

## どんなのができたのか？
こんなのができました！
![スクリーンショット 2023-04-20 17.15.14（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124781/ddf21888-f0cb-4438-a621-7acd93a22e5b.png)

![スクリーンショット 2023-04-20 17.16.45（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124781/d1fda3ed-14ca-ea52-2759-9913590b560e.png)




# 最後に
「**クラス外でインスタンス変数は使うな！！！**」 と書籍に書いてありましたのでその様にしたら
こんなに分割できてしまいまいした。

個人的には１ファイル１ファイルのコード量は少ない方が好きなのでオブジェクト指向プログラミングは向いてるのかもしれません！

オブジェクト指向設計勉強初めて６日目ですがこれからも意識してコード書いていきます！

初日に比べてスラスラかける様になってきたので控えめに言って楽しいです。

**長くなってしまいましたがここまで見てくださった方有難うございました！**