概要
Represent special cases as Object
前提
Confident Rubyではメソッド内の処理を次のように分類しています。
・Collecting Inputs(引数チェック、変換など)
・Performing Work(主処理)
・Delivering Output(戻り値に関わる処理)
・Handling Failure(例外処理)
当記事は上記のうち、Collecting Inputsに関する話です。
詳細
状況
プログラムの多くの箇所で考慮すべき特別なケースがある。
例えばWeb Applicationならカレントユーザーがログインしているかどうかで
振る舞いが変わる必要があるかもしれない。
概要
一意な型のオブジェクトとして特別なケースを表す。
特別なケースがどこで見つかってもポリモルフィズムによって取り扱う。
理由
特殊なケースの取り扱いにポリモルフィズムを利用することによって、数十もの繰り返しの条件分岐を
なくすことができるため。
サンプルコード仕様
プログラマ丸投げクラス(ProgrammerProvider)は指定した名前のプログラマのスキルリストを返却します。
スキルリストを取得後、スキルリストに関する様々な処理があちこちから呼ばれます。
ここでは例としてスキルリストの出力。
一番初めのスキルの出力。
を行います。
この際に、まったくスキルを持たないプログラマは同一ケースで処理できないため
「Represent special cases as Object」の適用前は毎回ifの分岐を行い、
「Represent special cases as Object」の適用後はSkillfulProgrammerクラスとNoSkillProgrammerを作成することで
分岐を局所化し、あちこちで同じような分岐を記述しないで済むようにします。
サンプルコード(適用前)
["tanaka", "sato", "miso"].each ・・内のロジックでskillの分岐が複数回登場しています。
(実際にはもっと大量に呼び出しがあることを想定)
class ProgrammerProvider PROGRAMMERS = [ { name: 'tanaka', skills: ['Java', 'Oracle', 'HTML'], }, { name: 'sato', skills: ['Ruby', 'MySQL', 'CoffeeScript'], }, { name: 'miso', skills: nil, }, ] def get_programmer_skills(name) PROGRAMMERS.select { |v|v[:name] == name}.first[:skills] end end pp = ProgrammerProvider.new ["tanaka", "sato", "miso"].each do |name| skills = pp.get_programmer_skills(name) if skill puts "#{name} has '#{skill.join(' ,')}' skills" else puts "#{name} has no skill" end # some logic if skill puts "#{name}'s first skill is '#{skill.first}' skills" else puts "#{name}'s first skill is nothing" end end
出力
tanaka has 'Java ,Oracle ,HTML' skills tanaka's first skill is 'Java' skills sato has 'Ruby ,MySQL ,CoffeeScript' skills sato's first skill is 'Ruby' skills miso has no skill miso's first skill is nothing
サンプルコード(適用後)
["tanaka", "sato", "miso"].each ・・内のロジックでskillの分岐がなくなりました。)
class SkillfulProgrammer attr_reader :name, :skills def initialize(name, skills) @name, @skills = name, skills end def print_skills puts "#{name} has '#{@skills.join(' ,')}' skills" end def print_first_skill puts "#{name}'s first skill is '#{skills.first}' skills" end end class NoSkillProgrammer attr_reader :name def initialize(name) @name = name end def print_skills puts "#{name} has no skill" end def print_first_skill puts "#{name}'s first skill is nothing" end end class ProgrammerProvider PROGRAMMERS = [ SkillfulProgrammer.new("tanaka", ['Java', 'Oracle', 'HTML']), SkillfulProgrammer.new("sato", ['Ruby', 'MySQL', 'CoffeeScript']), NoSkillProgrammer.new("miso") ] def get_programmer(name) PROGRAMMERS.select { |v|v.name == name }.first end end pp = ProgrammerProvider.new ["tanaka", "sato", "miso"].each do |name| programmer = pp.get_programmer(name) programmer.print_skills programmer.print_first_skill end
出力
tanaka has 'Java ,Oracle ,HTML' skills tanaka's first skill is 'Java' skills sato has 'Ruby ,MySQL ,CoffeeScript' skills sato's first skill is 'Ruby' skills miso has no skill miso's first skill is nothing