デカい条件分岐のリファクタリング

FacebookTwitterCDN上にあるアイコン、Paperclipでローカルに置いたユーザーアイコンのimgタグをビューから呼び出して生成するために、Railsのヘルパーメソッドで以下のように書いた。

require 'uri'

module ApplicationHelper

  ICON_SIZE = {small:"24x24", normal:"73x73", big:"240x240"}

  :
  :
  
  def photo_tag(user, options = {:size => :normal})
    icon_path = lambda do |path, suffix|
      u     = URI.parse(path)
      ext   = File.extname(u.path)
      path.chomp(ext) << suffix + ext
    end

    icon_size = ICON_SIZE[options[:size]]
    raise "Invalid size option for photo_tag" if icon_size.nil?
 
    photo_url = 
      if user.remote_photo_url && user.provider == "twitter"
        case options[:size]
        when :small
          icon_path.call(user.remote_photo_url, "_normal")
        when :normal
          icon_path.call(user.remote_photo_url, "_bigger")
        when :big
          user.remote_photo_url
        else
          raise "Invalid size option for photo_tag"
        end
      elsif user.remote_photo_url && user.provider == "facebook"
        case options[:size]
        when :small
          icon_path.call(user.remote_photo_url, "_q")
        when :normal
          icon_path.call(user.remote_photo_url, "_q")
        when :big
          icon_path.call(user.remote_photo_url, "_b")
        else
          raise "Invalid size option for photo_tag"
        end
      else
        case options[:size]
        when :small
          user.photo.url(:thumb)
        when :normal
          user.photo.url(:thumb)
        when :big
          user.photo.url(:medium)
        else
          raise "Invalid size option for photo_url"
        end
      end
    image_tag photo_url, :size => icon_size
  end
  :
  :

順番に並べてるだけだし、これでもいっかという気もするけど、なんだかごちゃっとしている。結局、FacebookTwitterかPaperclipかという分岐とアイコンサイズによる分岐とで、2次元のマトリックスになっているのが嫌な感じ。

そういえばマーチン・ファウラーの本に、条件分岐はオブジェクト指向ではポリモーフィズムで書き換えられるんだと書いてあったなと思って、よく意味が分からないけど、以下のように書き換えてみた。

require 'photo_url.rb'

module ApplicationHelper
  :
  :  
  def photo_tag(user, options = {:size => :normal})
    case user.provider
    when "twitter"
      photo_url = TwitterPhotoUrl.new(user, options)
    when "facebook"
      photo_url = FacebookPhotoUrl.new(user, options)
    else
      photo_url = LocalPhotoUrl.new(user, options)
    end
    icon_size = photo_url.icon_size
    raise "Invalid size option for photo_tag" if icon_size.nil?
    image_tag photo_url.path, :size => photo_url.icon_size
  end

(application_helper.rb)

------------------------------
require 'uri'

class PhotoUrl

  ICON_SIZE = {small:"24x24", normal:"73x73", big:"240x240"}

  def initialize(user, options)
    @user, @options = user, options
  end

  def icon_size
    ICON_SIZE[@options[:size]]
  end

  def add_suffix_to_path(path, suffix)
    u     = URI.parse(path)
    ext   = File.extname(u.path)
    path.chomp(ext) << suffix + ext
  end
end

class TwitterPhotoUrl < PhotoUrl
  def path
    case @options[:size]
    when :small
      add_suffix_to_path(@user.remote_photo_url, "_normal")
    when :normal
      add_suffix_to_path(@user.remote_photo_url, "_bigger")
    when :big
      @user.remote_photo_url
    else
      raise "Invalid size option for photo_tag"
    end
  end
end
  
class FacebookPhotoUrl < PhotoUrl
  def path
    case @options[:size]
    when :small
      add_suffix_to_path(@user.remote_photo_url, "_q")
    when :normal
      add_suffix_to_path(@user.remote_photo_url, "_q")
    when :big
      add_suffix_to_path(@user.remote_photo_url, "_b")
    else
      raise "Invalid size option for photo_tag"
    end
  end
end

class LocalPhotoUrl < PhotoUrl
  def path
    case @options[:size]
    when :small
      @user.photo.url(:thumb)
    when :normal
      @user.photo.url(:thumb)
    when :big
      @user.photo.url(:medium)
    else
      raise "Invalid size option for photo_url"
    end
  end
end

(photo_url.rb)

あんまりスッキリした感じがない。