【ラーメン注文システムを例に】初学者がつまづくオブジェクト指向

## はじめに
初めまして。プログラミング学習を始めてまだ2週間の新米エンジニアです!
Rubyの基本的な文法から、オブジェクト指向を用いた簡単な設計まで、書籍やProgateなどを使用して学習してきました。

学んだことをアウトプットするため、また同じく学習を進める仲間たちと共有するために、この記事を書くことにしました。

今回の記事では、初学者がつまづきやすいオブジェクト指向に焦点を当て、実際にラーメン注文システムのコード例を見ながらオブジェクト指向の概念をわかりやすく解説していきます。まだ学習途中のため、オブジェクト指向の基本部分のみに絞って説明していきます。この記事を読んでいただき、ざっくりとオブジェクト指向はこんなことをやっているんだとイメージしてもらえれば幸いです。



## オブジェクト指向の用語解説
### オブジェクト指向
オブジェクト指向プログラミング（OOP：ObjectOrientedProgramminglanguage）は、ソフトウェア開発において、現実世界の物体や概念をモデル化する一つのアプローチ方法です。この方法により、データとそのデータを操作する手続きを一つのまとまりとして扱い、より直感的で再利用可能なコードを作成することができます。
### 1. クラスとインスタンスの理解
クラスはオブジェクトの設計図であり、オブジェクトの属性（データ）と振る舞い（メソッド）を定義します。一方、インスタンスはその設計図を基に作られた実体で、クラスの属性と振る舞いを具体的なデータとして持ちます。

### 2. initialize メソッドとその役割
initializeメソッドはクラスのインスタンスが生成される際に自動的に呼ばれるコンストラクタメソッドです。このメソッドは、インスタンスの初期設定を行い、必要なデータを初期化します。

### 3. インスタンスメソッドとクラスメソッドの違い
インスタンスメソッドは、あるクラスのインスタンスに属するメソッドです。対してクラスメソッドは、クラス自身に属し、インスタンスを必要とせずに呼び出せるメソッドです。

### 4. アクセッサメソッド：attr_accessor、attr_reader、attr_writer
これらはインスタンス変数へのアクセスを提供するメソッドで、attr_accessorは読み取りと書き込みの両方を、attr_readerは読み取りのみを、attr_writerは書き込みのみをそれぞれ提供します。

### 5. 変数の種類：インスタンス変数、ローカル変数
インスタンス変数はインスタンスごとに保持されるデータ、ローカル変数はメソッド内でのみ有効な変数です。

### 6. 継承
継承はあるクラス(親クラス)が別のクラス(子クラス)の属性と振る舞いを引き継ぐことです。これにより、共通の特徴を持つ異なるクラスを効率的に作成することが可能になります。

### 7. メソッドのオーバーライドとsuperの適切な使い方
オーバーライドは子クラスが親クラスのメソッドを新しい定義で置き換えることです。superを使うと、オーバーライドしたメソッド内で親クラスの元のメソッドを呼び出せます。


## 実際に作ってみよう
### 【ラーメン注文システム】

ここでは、ラーメン注文システムを作成する過程を通じて、前述のオブジェクト指向の概念を実際に見ていきます。スープとトッピングという属性を持つラーメンオブジェクトのクラスを定義し、さまざまなラーメンの味とトッピングを表現するために継承を活用します。
```ruby:ramen.rb
# ベースとなるラーメンクラス。このクラスには、全てのラーメンが持つ共通の属性が定義されています。
class BaseRamen
    attr_reader :soup, :toppings

    # initializeメソッドでラーメンのスープと基本トッピングを設定します。
    def initialize(soup)
        @soup = soup
        @toppings = ['味玉'] # すべてのラーメンには味玉が入ります。
    end

    # トッピングを追加するメソッドです。
    def add_topping(topping)
        @toppings << topping
    end

    # 注文内容を表示するメソッドです。
    def display_order
        puts "ラーメンのスープ: #{@soup}"
        puts "トッピング: #{@toppings.join(', ')}"
    end
end

# 醤油ラーメンクラス。BaseRamenクラスを継承。
class SoySauceRamen < BaseRamen
    # 醤油ラーメン独自のトッピングを追加します。
    def initialize
        super('醤油') # BaseRamenのinitializeメソッドを呼び出し、スープを設定します。
        add_topping('のり')
        add_topping('メンマ')
    end
end

# 味噌ラーメンクラス。BaseRamenクラスを継承。
class MisoRamen < BaseRamen
    # 味噌ラーメン独自のトッピングを追加します。
    def initialize
        super('味噌') # BaseRamenのinitializeメソッドを呼び出し、スープを設定します。
        add_topping('コーン')
        add_topping('バター')
    end
end

# 豚骨ラーメンクラス。BaseRamenクラスを継承。
class TonkotsuRamen < BaseRamen
    # 豚骨ラーメン独自のトッピングを追加します。
    def initialize
        super('豚骨') # BaseRamenのinitializeメソッドを呼び出し、味を設定します。
        add_topping('キクラゲ')
        add_topping('紅生姜')
    end
end

# 注文プロセスを試すことができます。
puts "ラーメンのスープを選んでください：1.醤油 2.味噌 3.豚骨"
choice = gets.chomp.to_i

# ユーザーの選択に応じて、対応するラーメンオブジェクトを作成します。
case choice
when 1
    ramen = SoySauceRamen.new
when 2
    ramen = MisoRamen.new
when 3
    ramen = TonkotsuRamen.new
else
    puts "選択肢が正しくありません。"
    exit
end

# 注文内容を表示します。
ramen.display_order

```

### 説明
先ほどの概念をもとに、コードの中身を説明していきます。

### 1.クラスとインスタンス　　　6.継承
BaseRamenはラーメンの一般的な特徴を定義する親クラスです。この親クラスを継承して、特定のスープを持つラーメンのクラスを作ります。例えば、ShoyuRamenやMisoRamenはBaseRamenの子クラスとなり、それぞれがBaseRamenクラスの属性とメソッドを受け継ぎつつ、独自の特徴を追加して新しいインスタンスを形成します。
```
#親クラス。
class BaseRamen
        - - 省略　-　-
end

#子クラス。親クラスを継承。
class ShoyuRamen < BaseRamen
        - - 省略　-　-
end

#子クラス。親クラスを継承。
class MisoRamen < BaseRamen
        - - 省略　-　-
end
```


### 2.initialize メソッド　　　7.メソッドのオーバーライドとsuper
・BaseRamenクラスにinitializeメソッドを定義しており、新しいBaseRamenオブジェクトが作られるとき（例えばBaseRamen.new('醤油')を実行したとき）、自動的にこのメソッドが呼び出されます。
・子クラス(ShoyuRamen、MisoRamen、TonkotsuRamen)では、initializeメソッドをオーバーライドしていますが、superキーワードを使って親クラスのメソッドを呼び出し、その上で追加のトッピングを設定しています。
```
#親クラス。
class BaseRamen
        - - 省略　-　-
    # initializeメソッドでラーメンのスープと基本トッピングを設定します。
    def initialize(soup)
        @soup = soup
        @toppings = ['味玉'] # すべてのラーメンには味玉が入ります。
    end
        - - 省略　-　-
end

#
class SoySauceRamen < BaseRamen
    # 醤油ラーメン独自のトッピングを追加します。
    def initialize
        super('醤油') # BaseRamenのinitializeメソッドを呼び出し、スープを設定します。
        add_topping('のり')
        add_topping('メンマ')
    end
end
```
### 3.インスタンスメソッドとクラスメソッド
・インスタンスメソッド
↓display_orderはBaseRamenのインスタンスが呼び出すことができるメソッドです。
```
class BaseRamen

        - - 省略　-　-
        
  # display_orderメソッドはインスタンスメソッドで、特定のラーメンインスタンスの注文を表示します。
  def display_order
    puts "ラーメンの種類: #{@flavor}"
    puts "トッピング: #{@toppings.join(', ')}"
  end
end
  
# インスタンスメソッドの使用例
ramen = BaseRamen.new('醤油')
ramen.display_order  # "ラーメンの種類: 醤油" と "トッピング: 味玉" を表示
```


・クラスメソッド(例：新しくスープを追加するメソッド)
　　＊上記コードにはありません
↓インスタンスを介さずに直接クラスによって呼び出され、全てのBaseRamenオブジェクトに共通の情報を提供します。
```
class BaseRamen

       - - 省略　-　-
       
  # announceメソッドはクラスメソッドで、ラーメンの新しいスープを追加する際に使われます。
  def self.announce(soup)
    puts "新しいラーメンが登場しました！フレーバー: #{soup}"
  end
end


# クラスメソッドの使用例
BaseRamen.announce('塩')  # "新しいラーメンが登場しました！スープ: 塩" を表示
```

### 4.アクセサメソッド：attr_accessor、attr_reader、attr_writer
・今回はattr_readerのみ使用しています。
・attr_readerは読み取り専用アクセサを作成するためのメソッドで、クラスの外部からインスタンス変数の値を取得することができるようにしますが、その値を変更することはできません。この機能により、インスタンスの内部状態は保護され、外部から安全にアクセスできるようになります。
```
class BaseRamen
    attr_reader :soup, :toppings

       - - 省略　-　-
       
end
```
### 5.インスタンス変数、ローカル変数
・インスタンス変数は、変数名の前に@記号をつけることで定義され、例えば@soupや@toppingsのように使われます。ラーメンの味や選ばれたトッピングを保持するのに使われています。

・ローカル変数は特に接頭辞なしで定義され、例えばsoupやtoppingのように単純な名前で使われます。一時的なデータの保存や、メソッド内の計算に使われることが一般的です。




## まとめ
オブジェクト指向の基本的な概念をラーメン注文システムを用いて簡単に紹介しました。オブジェクト指向はとても奥が深く、今回の内容はほんの入口に過ぎません。最初は理解するのに苦労すると思いますが、ただ用語を理解するだけで終わらせず、実際に手を動かしながらコードを書き進めていくうちに理解が少しずつ深まってくると思います。初学者の皆さんも、これらの概念を学んだ上で、何か一つ自分の作りたいものをオブジェクト指向を意識して作ってみてください。