Tbpgr Blog

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

RubyでBridgeパターン

概要

GoFデザインパターンのBridgeパターンについて。
クラス〜サブクラスの機能の階層。
抽象クラス〜具象クラスの実装の階層。
それぞれを独立させることで保守性を高める。
新たな機能、新たな実装の追加が容易となる、

登場人物

Abstraction = 抽象クラス
RefinedAbstraction = 改善した抽象化
Implementor = 実装者
ConcreteImplementor = 具体的な実装者

UML


実装サンプル

サンプル概要

ある言語のソースコードを生成するプログラムを想定します。
CodeGeneratorは機能の階層です。
指定したCodeGenerateImplでコードの標準出力を行うクラス。
CodeFileGeneratorはCodeGeneratorを継承したクラスでコードのファイル出力機能を
追加してあります。
CodeGenerateImplは実装の階層です。
RubyCodeGeneratorはソースコードの出力を行います。
RubyTestCodeGeneratorはテストコードの出力を行います。

登場人物

Abstraction = CodeGenerator : 言語生成の抽象クラス
RefinedAbstraction = CodeFileGenerator : 言語生成のファイル出力稲生が追加された具象クラス
Implementor = CodeGenerateImpl : 言語生成の実装用抽象クラス
ConcreteImplementor = RubyTestCodeGenerator : 言語生成実装抽象クラス
ConcreteImplementor = RubyCodeGenerator : 言語生成実装抽象クラス

その他
GenerateUtil = コード生成用汎用クラス(キャメルケースからスネークケースへの変換)

UML


サンプルコード

CodeGenerator

# encoding: Shift_JIS
require_relative './code_generate_impl'
require_relative './ruby_code_generator'
require_relative './ruby_test_code_generator'

=begin rdoc
= CodeGeneratorクラス
=end
class CodeGenerator
  attr_accessor :ruby_test_codegenerator,:ruby_codegenerator
  GENERATE = {:code => 1,:test_code => 2,:both => 3}
  INCOLLECT_GENERATE_TYPE = 'incorrect generate_type'
  
  def initialize()
    @ruby_codegenerator = RubyCodeGenerator.new
    @ruby_test_codegenerator = RubyTestCodeGenerator.new
  end

  def generate_code(class_name, generate_type)
    ret = ""
    case generate_type
    when GENERATE[:code]
      ret = @ruby_codegenerator.generate_code class_name
    when GENERATE[:test_code]
      ret = @ruby_test_codegenerator.generate_code class_name
    when GENERATE[:both]
      ret = @ruby_codegenerator.generate_code class_name
      ret += @ruby_test_codegenerator.generate_code class_name
    else
      raise CodeGenerator::INCOLLECT_GENERATE_TYPE
    end
    return ret
  end
end

CodeFileGenerator

# encoding: Shift_JIS
require_relative './code_generator'

=begin rdoc
= CodeFileGeneratorクラス
=end
class CodeFileGenerator < CodeGenerator
  def generate_code_to_file(class_name, generate_type)
    # 基底クラスのgenerate_codeから文字列を取得
    code = generate_code(class_name, generate_type)

    file_name = GenerateUtil::to_snake_case(class_name)
    file_name = "test_" + class_name if generate_type == GENERATE[:test_code]

    # ファイルを出力
    File.open("./" + file_name + ".rb",'w'){|output_file|
      output_file.write code
    }

  end
end

CodeGenerateImpl

# encoding: Shift_JIS

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

  def generate_code(class_name)
    raise CodeGenerateImpl::NOT_OVERRRIDE
  end
end

RubyCodeGenerator

# encoding: Shift_JIS
require_relative './code_generate_impl'

=begin rdoc
= RubyCodeGeneratorクラス
=end
class RubyCodeGenerator < CodeGenerateImpl
  def generate_code(class_name)
    code =<<"EOS"
# encoding: Shift_JIS
class #{class_name}
# 以下に実装コードを追加

end
EOS
    return code
  end
end

RubyTestCodeGenerator

# encoding: Shift_JIS
require_relative './code_generate_impl'
require_relative './generate_util'

=begin rdoc
= RubyTestCodeGeneratorクラス
=end
class RubyTestCodeGenerator < CodeGenerateImpl
  def generate_code(class_name)
     # クラス名をスネークケースに変換
    snake_case_class_name = GenerateUtil::to_snake_case(class_name)

  code =<<"EOS"
# encoding: Shift_JIS
require 'test/unit'
require_relative './#{snake_case_class_name}'
class Test#{class_name} < Test::Unit::TestCase
# 以下にテストコードを追加

end
EOS
    return code
  end
end

main

# encoding: Shift_JIS
require_relative './code_generator'
require_relative './code_file_generator'

code_generator = CodeGenerator.new
result = code_generator.generate_code "SampleRubyClass", CodeGenerator::GENERATE[:both]
puts result

code_generator = CodeFileGenerator.new
code_generator.generate_code_to_file "SampleRubyClass", CodeGenerator::GENERATE[:test_code]
code_generator.generate_code_to_file "SampleRubyClass", CodeGenerator::GENERATE[:code]

generate_util.rb

# encoding: Shift_JIS

class GenerateUtil
  def self.to_snake_case(str)
    ret = str.gsub(/([A-Z].)/,'_\1').downcase
    ret = ret.slice(1,ret.size)
    return ret
  end
end
標準出力
# encoding: Shift_JIS
class SampleRubyClass
# 以下に実装コードを追加

end
# encoding: Shift_JIS
require 'test/unit'
require_relative './sample_ruby_class'
class TestSampleRubyClass < Test::Unit::TestCase
# 以下にテストコードを追加

end
ファイル出力確認
Feb 20 23:09 sample_ruby_class.rb
Feb 20 23:09 test_SampleRubyClass.rb

※中身は上記の標準出力内容のまま