Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

RubyでStrategyパターン

概要

GoFデザインパターンのStrategyパターンについて。
委譲によってアルゴリズムを交換可能にする。

登場人物

Context = コンテキストクラス
Strategy = 抽象戦略
ConcreteStrategy = 具象戦略

UML


実装サンプル

サンプル概要

ある言語のソースコードを実行するプログラムを想定します。
ある言語を利用するLanguageUserが自分が使用する言語実行のインスタンス(LanguageExecutor)をメンバーに持ちます。
言語実行は継承により他言語対応が可能です。例えばRubyExecutorやJavaExecutorなど。
LanguageUserはLanguageExecutorのexecuteメソッドを実行すれば任意のソースコードを任意の言語で実行できます。

例えば、hoge.rbというRubyソースコードを実行したければメンバーにはRubyExecutorを設定し
RubyExecutor.executeを実行すればrubyコマンドでソースコードを実行します。

次に、hoge.javaというJavaソースコードを実行したければメンバーにはJavaExecutorを設定し
JavaExecutor.executeを実行すればjavacコマンドでコンパイル後、javaコマンドでクラスファイルを実行します。

登場人物

Context = LanguageUser : 言語の利用者
Strategy = LanguageExecutor : 言語実行
ConcreteStrategy = RubyExecutor : Ruby言語実行
ConcreteStrategy = JavaExecutor : Java言語実行

UML


サンプルコード

LanguageUser

# encoding: Shift_JIS
require_relative './language_executor'

=begin rdoc
= LanguageUserクラス
=end
class LanguageUser
  attr_accessor :language_executor,:language,:file_path
  NOT_OVERRRIDE = 'not override error'
  
  def initialize(language)
    @language = language
    @language_executor = LanguageExecutor.get_executor(language)
  end
  
  def execute(file_path)
    puts language_executor.execute file_path
    puts "#{file_path}#{language}で実行しました"
  end
end

LanguageExecutor

# encoding: Shift_JIS
require_relative './ruby_executor'
require_relative './java_executor'

=begin rdoc
= LanguageExecutorクラス
=end
class LanguageExecutor
  attr_accessor :language,:file_path
  EXECUTOR = 'Executor'
  NOT_OVERRRIDE = 'not override error'
  NOT_EXIST_CLASS = 'not exist class'
  
  def self.get_executor(language)
    executor = Object.const_get("#{language}#{LanguageExecutor::EXECUTOR}").new
    return executor
    rescue NameError
      raise LanguageFactory::NOT_EXIST_CLASS
  end
  
  def execute(file_path)
    raise LanguageExecutor::NOT_OVERRRIDE
  end
end

RubyExecutor

# encoding: Shift_JIS
require_relative './language_executor'

=begin rdoc
= RubyExecutorクラス
=end
class RubyExecutor
  def execute(file_path)
    return `ruby -Ku #{file_path}`
  end
end

JavaExecutor

# encoding: Shift_JIS
require_relative './language_executor'
require_relative './string_util'
=begin rdoc
= JavaExecutorクラス
=end
class JavaExecutor
  def execute(file_path)
    `javac #{file_path}`
    folder_path = StringUtil::get_folder_path file_path
    file_name = StringUtil::get_file_name file_path
    file_name_without_extension = StringUtil::get_file_name_without_extension file_name
    `cd #{folder_path}`
    return `java #{file_name_without_extension}`
  end
end

LanguageEnum

# encoding: Shift_JIS

=begin rdoc
= LanguageEnum(もどき)クラス
言語の列挙定数を保持
=end
class LanguageEnum
  LANGUAGE = {:java => "Java",:ruby => "Ruby"}
end

main

# encoding: Shift_JIS
require_relative './language_user'
require_relative './language_enum'

java_user = LanguageUser.new(LanguageEnum::LANGUAGE[:java])
ruby_user = LanguageUser.new(LanguageEnum::LANGUAGE[:ruby])

java_user.execute "HelloJava.java"
ruby_user.execute "hello_ruby.rb"

StringUtil

# encoding: Shift_JIS

class StringUtil
  def self.to_snake_case(str)
    ret = str.gsub(/([A-Z].)/,'_\1').downcase
    ret = ret.slice(1,ret.size)
    return ret
  end
  
  # パスからファイル名を除外した文字列を取得
  def self.get_folder_path(file_path)
    folders = file_path.split('/')
    folders.pop
    
    folder_path = ""
    folders.each {|folder|
      folder_path << folder
      folder_path << "/"
    }
    return folder_path
  end
  
  # ファイル名のみを取得
  def self.get_file_name(file_path)
    folders = file_path.split('/')
    return folders[folders.size-1]
  end
  
  # 拡張子なしファイル名の取得
  def self.get_file_name_without_extension(file_name)
    file_names = file_name.split('.')
    return file_names[0]
  end
end
出力結果
Hello!Java
HelloJava.javaをJavaで実行しました
hello ruby!
hello_ruby.rbをRubyで実行しました