Tbpgr Blog

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

RubyでFacadeパターン

概要

GoFデザインパターンのFacadeパターンについて。
複雑な手順の処理の窓口=Facadeを用意することで
システムをシンプルにする。

登場人物

Facade = 窓口
ModuleA = 処理A
ModuleB = 処理B
Client = 利用者

UML


実装サンプル

サンプル概要

親子構造を持つクラスを生成するジェネレーターを実装します。
ジェネレーターの利用者は親子クラスジェネレーターに親クラス名、子クラス名、
テストクラスの生成有無を渡すして、コード生成メソッドを呼ぶことで
ファイルの生成を行うことが出来ます。
親子クラスジェネレーターがFacadeにあたります。
利用者は親子クラスジェネレーターのコード生成メソッドのインターフェースのみを知っていれば問題ありません。

親子クラスジェネレーターのコード生成メソッドでは、引数で受けとったクラス名で
親クラスジェネレーターと子クラスジェネレーターを順に呼び出してクラスを生成します。
さらに、テストクラス生成有無が真ならテストクラスジェネレーターを呼び出して
テストクラスを作成します。

各クラスの責務は以下の登場人物に記述した内容になります。

登場人物

Facade = FamilyClassGenerator:継承構造を持つクラスを生成する窓口
ModuleA = ClassGenerator:クラス生成の基底クラス
ModuleB = ParentClassGenerator:親クラスを生成
ModuleC = ChildClassGenerator:子クラスを生成
ModuleD = TestClassGenerator:テストクラスを生成
Client = User:親子クラスジェネレーターの利用者

UML


サンプルコード

FamilyClassGenerator

# encoding: Shift_JIS
require_relative './parent_class_generator'
require_relative './child_class_generator'
require_relative './test_class_generator'

=begin rdoc
= FamilyClassGeneratorクラス
=end
class FamilyClassGenerator
  def generate_code(parent_name,child_names,has_test_class)
    parent_class_generator = ParentClassGenerator.new
    child_class_generator = ChildClassGenerator.new
    test_class_generator = TestClassGenerator.new
    
    parent_class_generator.generate_class(parent_name,nil,false)
    test_class_generator.generate_class("Test#{parent_name}",nil,false) if has_test_class
    child_names.each{|child|
      child_class_generator.generate_class(child,parent_name,true)
      test_class_generator.generate_class("Test#{child}",nil,false) if has_test_class
    }
  end
end

class_generator

# encoding: Shift_JIS
require_relative './string_util'

=begin rdoc
= ClassGeneratorクラス
=end
class ClassGenerator
  NOT_OVERRRIDE = 'not override error'

  def generate_class(class_name,parent_name,has_parent)
    if has_parent
      code = generate_code(class_name,parent_name)
    else
      code = generate_code(class_name)
    end
    
    output_class_file(class_name,code)
  end
  
  private
  def output_class_file(class_name,code)
    snake_file_name = StringUtil.to_snake_case class_name
    file_path = "./output/#{snake_file_name}.rb"
    folder_path=File::dirname file_path
    Dir::mkdir folder_path unless FileTest.exist?(folder_path)
    File::delete(file_path) if FileTest.exist?(file_path)
    file = open(file_path,"w")
    file.puts code
    file.close
  end

  protected
  def generate_code(class_name,parent_name)
    raise NOT_OVERRRIDE
  end
  
  protected
  def generate_code(class_name)
    raise NOT_OVERRRIDE
  end

end

parent_class_generator

# encoding: Shift_JIS
require_relative './class_generator'

=begin rdoc
= ParentClassGeneratorクラス
=end
class ParentClassGenerator < ClassGenerator
  def generate_code(class_name)
    code =<<"EOS"
class #{class_name}

end
EOS
    return code
  end
end

child_class_generator

# encoding: Shift_JIS
require_relative './class_generator'

=begin rdoc
= ChildClassGeneratorクラス
=end
class ChildClassGenerator < ClassGenerator
  def generate_code(class_name,parent_name)
    snake_file_name = StringUtil.to_snake_case parent_name
    code =<<"EOS"
require_relative './#{snake_file_name}'
class #{class_name} < #{parent_name}

end
EOS
    return code
  end
end

test_class_generator

# encoding: Shift_JIS
require_relative './class_generator'

=begin rdoc
= TestClassGeneratorクラス
=end
class TestClassGenerator < ClassGenerator
  def generate_code(class_name)
    snake_file_name = StringUtil.to_snake_case(class_name.sub('Test',''))
    code =<<"EOS"
require 'test/unit'
require_relative './#{snake_file_name}'
class #{class_name} < Test::Unit::TestCase

end
EOS
    return code
  end
end

user

# encoding: Shift_JIS
require_relative './family_class_generator'

=begin rdoc
= Userクラス
=end
class User
  def call_generate_family_class(parent_name,child_names,has_test_class)
    class_generator = FamilyClassGenerator.new
    class_generator.generate_code(parent_name,child_names,has_test_class)
  end
end

string_util

# 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

main

# encoding: Shift_JIS
require_relative './user'

user = User.new
child_classes = ["ChildFirst","ChildSecond","ChildThird"]
user.call_generate_family_class("Parent",child_classes,true)
other_child_classes = ["OtherChildFirst","OtherChildSecond","OtherChildThird"]
user.call_generate_family_class("OtherParent",other_child_classes,false)

puts `ls -ltr ./output | sort`
puts "--------------------------------"
puts `cat ./output/parent.rb`
puts "--------------------------------"
puts `cat ./output/test_parent.rb`
puts "--------------------------------"
puts `cat ./output/child_first.rb`
puts "--------------------------------"
puts `cat ./output/test_child_first.rb`

出力

-rwxr-xr-x+ 1 ***** Domain Users  21 Feb 27 21:36 parent.rb
-rwxr-xr-x+ 1 ***** Domain Users  26 Feb 27 21:36 other_parent.rb
-rwxr-xr-x+ 1 ***** Domain Users  63 Feb 27 21:36 child_first.rb
-rwxr-xr-x+ 1 ***** Domain Users  63 Feb 27 21:36 child_third.rb
-rwxr-xr-x+ 1 ***** Domain Users  64 Feb 27 21:36 child_second.rb
-rwxr-xr-x+ 1 ***** Domain Users  79 Feb 27 21:36 other_child_first.rb
-rwxr-xr-x+ 1 ***** Domain Users  79 Feb 27 21:36 other_child_third.rb
-rwxr-xr-x+ 1 ***** Domain Users  80 Feb 27 21:36 other_child_second.rb
-rwxr-xr-x+ 1 ***** Domain Users  98 Feb 27 21:36 test_parent.rb
-rwxr-xr-x+ 1 ***** Domain Users 107 Feb 27 21:36 test_child_first.rb
-rwxr-xr-x+ 1 ***** Domain Users 107 Feb 27 21:36 test_child_third.rb
-rwxr-xr-x+ 1 ***** Domain Users 109 Feb 27 21:36 test_child_second.rb
total 12
--------------------------------
class Parent

end
--------------------------------
require 'test/unit'
require_relative './parent'
class TestParent < Test::Unit::TestCase

end
--------------------------------
require_relative './parent'
class ChildFirst < Parent

end
--------------------------------
require 'test/unit'
require_relative './child_first'
class TestChildFirst < Test::Unit::TestCase

end