はじめに
この記事は、「クラスをnewする際の引数が多くて困ってる」人向けのものです。
『オブジェクト指向設計実践ガイド 3.2 疎結合なコードを書く-引数の順番への依存を取り除く』を参考にしてこの記事を作成しました。
ラーメンの例を用いて、クラスをnewする際の引数が多いときの問題点とその解決方法を紹介しているのでぜひご覧ください。
まずは結論から
引数をハッシュで渡そう!
ラーメンを用いての例
あぁ、ラーメンが食べたい。そうだラーメンクラスを使ってラーメンを作ろう!
ラーメンに必要な具材は、これとあれとそれだから...っと
よし、これをRamenクラスに渡してあげればきっとラーメンが作れるはずだ〜
(私は煮玉子いらない派なのでnilにしてます。)
class Ramen
def initialize(noodles, soup, roast_pork, bamboo_shoots, marinated_boiled_egg)
@noodles = noodles # 麺
@soup = soup # スープ
@roast_pork = roast_pork # チャーシュー
@bamboo_shoots = bamboo_shoots # メンマ
@marinated_boiled_egg = marinated_boiled_egg # 煮玉子
end
end
Ramen.new('硬めの麺', '醤油', '厚めチャーシュー', '薄めメンマ', nil)
お気づきでしょうか? initializeの引数(ラーメンの具材) が多すぎることに。。。
このように引数が多すぎる場合に起こり得る問題として以下が挙げられます。
問題点
- 引数の順番に変更があった際に、Ramenクラスをnewしている箇所全てを修正する必要があるため、変更の手間がかかる。(引数の順番に対する依存)
- Ramenクラスをnewするために必要な引数(ラーメンの具材)が増えるたびに、認知負荷が高まる。
解決策
上記2つの問題点を簡単に解決する方法があります。
それは 引数にハッシュを使う というものです。
さっそくこの方法を実践してみましょう。
以下が引数にハッシュを使った例です。
class Ramen
def initialize(args)
@noodles = args[:noodles] # 麺
@soup = args[:soup] # スープ
@roast_pork = args[:roast_pork] # チャーシュー
@bamboo_shoots = args[:bamboo_shoots] # メンマ
@marinated_boiled_egg = args[:marinated_boiled_egg] # 煮玉子
end
end
Ramen.new(:noodles => '硬めの麺',
:soup => '醤油',
:roast_pork => '厚めチャーシュー',
:bamboo_shoots => '薄めメンマ',
:marinated_boiled_egg => nil)
引数をハッシュで受け取るように変更したことで以下のメリットがあります。
・引数の順番に対する依存が解消された(問題点 1 の解決)
・引数をハッシュで持っているため、Ramenクラスをnewするために必要なものが増えたとしても引数が増えない(問題点 2 の解決)
余談
問題点の一つとして「引数の順番に対する依存」が挙げられていましたが、これに対する解決策はもう一つあります。
それは キーワード引数 です。
キーワード引数ってなに?
キーワード引数は、その名前の通り、引数をそのキーワード(名前)で指定できる機能です。
以下のように、引数の順番に依存することなく処理を実行できます。
また、特定の引数が何を指しているのかが明確になるため、コードの可読性が向上します。
def order(ramen:, count:)
puts "#{ramen}を#{count}杯注文お願いします"
end
order(ramen: '塩ラーメン', count: 2)
-> "塩ラーメンを2杯注文お願いします"
order(count: 2, ramen: '塩ラーメン')
-> "塩ラーメンを2杯注文お願いします"
ラーメンを用いての例でキーワード引数を使用しなかった理由
まずご覧いただきたいのは以下の表です。
これは、引数をハッシュで渡す方法とキーワード引数で渡す方法のメリット・デメリットをまとめたものです。
方法 | メリット | デメリット |
---|---|---|
キーワード引数 | ・ 順番に依存しない ・引数に過不足があるときにエラーが発生する |
引数が多いときは可読性が低下する |
ハッシュで渡す | ・順番に依存しない ・引数の受け取りが楽になる |
引数に過不足があるときにエラーが発生しない |
キーワード引数を選択した際のデメリットを考慮した結果、引数をハッシュで渡す方が良いと判断したので、キーワード引数を使用しなかったのです。
実際に、Ramenクラスをnewする際の引数をキーワード引数にしてみると納得がいくと思います。
class Ramen
def initialize(noodles:, soup:, roast_pork:, bamboo_shoots:, marinated_boiled_egg:)
@noodles = noodles # 麺
@soup = soup # スープ
@roast_pork = roast_pork # チャーシュー
@bamboo_shoots = bamboo_shoots # メンマ
@marinated_boiled_egg = marinated_boiled_egg # 煮玉子
end
end
Ramen.new(noodles: '硬めの麺',
soup: '醤油',
roast_pork: '厚めチャーシュー',
bamboo_shoots: '薄めメンマ',
marinated_boiled_egg: nil)
キーワード引数にすることによって、引数の順番に対する依存は解決されました。
しかし、initializeの部分を見ていただければ分かる通り、もう一つの問題点である
「Ramenクラスをnewするために必要な引数(ラーメンの具材)が増えるたびに、認知負荷が高まる」が全く解決されていません。
やはり、キーワード引数は引数が多い時に使用するには向いていなさそうですね。