Tbpgr Blog

Ruby プログラマ tbpgr(てぃーびー) のブログ

Ruby | Builder Pattern 〜 プロジェクトチームを組み立てる

概要

Builder Pattern 〜 プロジェクトチームを組み立てる

詳細

Builder Patternは、一定の設定手順のあるオブジェクトを
一か所で管理して組み立てるためのパターンです。

これにより、オブジェクトの利用各所で何度も複雑な手順を呼び出す手間や、
呼出し手順を誤ることによるバグから解放されます。

サンプル1 仕様

プロジェクトチームを組み立てます。
プロジェクトには、各役割が必要です。
各役割は
・プロジェクトマネージャー
・チームリーダー
・開発者
・DB管理者
・ネットワークエンジニア
・テスター
があります。

大規模案件の場合は各役割が必要になります。

小規模ネットワーク対応の非DB案件の場合は
プロジェクトマネージャー/開発者/ネットワークエンジニア
が必要になります。

中規模スタンドアロンのDB案件の場合は
プロジェクトマネージャー/開発者/DB管理者/テスター/が必要になります。

Builderパターン上の役割は以下になります。

TeamBuilder => Builder
Team => Product

サンプル1 コード

require 'active_support/core_ext'
require 'tbpgr_utils'
require 'attributes_initializable'
require 'pp'

[:ProjectManager, :TeamLeader, :Developer, :DbAdministrator, :Tester, :NetworkEngineer].each do |klass|
  self.class.const_set klass, Class.new { |k|
    attr_reader :name
    def initialize(name)
      @name = name
    end
  }
end

class TeamBuilder
  attr_reader :team
  def add_project_manager(name)
    @team.project_manager = ProjectManager.new(name)
  end

  def add_developer(name)
    @team.developer = Developer.new(name)
  end
end

class ProjectTeam
  include AttributesInitializable
  attr_accessor_init :name, :project_manager, :developer
end

class BigProjectTeam < ProjectTeam
  include AttributesInitializable
  attr_accessor_init :team_leader, :db_administrator, :tester, :network_engineer
end

class BigProjectTeamBuilder < TeamBuilder
  def initialize(name)
    @team = BigProjectTeam.new({name: name})
  end

  [ProjectManager, TeamLeader, Developer, DbAdministrator, Tester, NetworkEngineer].each do |klass|
    method = klass.to_s.underscore
    define_method :"add_#{method}" do |name|
      @team.send "#{method}=", klass.new(name)
    end
  end
end

bptb = BigProjectTeamBuilder.new('big project')
['project_manager', 'team_leader', 'developer', 'db_administrator', 'tester', 'network_engineer'].each do |role|
  bptb.send :"add_#{role}", role
end
pp bptb.team

class SamllNetworkProjectTeam < ProjectTeam
  include AttributesInitializable
  attr_accessor_init :network_engineer
end

class SamllNetworkProjectTeamBuilder < TeamBuilder
  def initialize(name)
    @team = SamllNetworkProjectTeam.new({name: name})
  end

  [ProjectManager, Developer, NetworkEngineer].each do |klass|
    method = klass.to_s.underscore
    define_method :"add_#{method}" do |name|
      @team.send "#{method}=", klass.new(name)
    end
  end
end

snptb = SamllNetworkProjectTeamBuilder.new('small network project')
['project_manager', 'developer', 'network_engineer'].each do |role|
  snptb.send :"add_#{role}", role
end
pp snptb.team

class MiddleDbProjectTeam < ProjectTeam
  include AttributesInitializable
  attr_accessor_init :db_administrator, :tester
end

class MiddleDbProjectTeamBuilder < TeamBuilder
  def initialize(name)
    @team = MiddleDbProjectTeam.new({name: name})
  end

  [ProjectManager, Developer, DbAdministrator, Tester].each do |klass|
    method = klass.to_s.underscore
    define_method :"add_#{method}" do |name|
      @team.send "#{method}=", klass.new(name)
    end
  end
end

mdptb = MiddleDbProjectTeamBuilder.new('middle db project')
['project_manager', 'developer', 'db_administrator', 'tester'].each do |role|
  mdptb.send :"add_#{role}", role
end
pp mdptb.team

出力

#<BigProjectTeam:0x2e3fda0
 @db_administrator=#<DbAdministrator:0x2e3f848 @name="db_administrator">,
 @developer=#<Developer:0x2e3f8c0 @name="developer">,
 @network_engineer=#<NetworkEngineer:0x2e3f740 @name="network_engineer">,
 @project_manager=#<ProjectManager:0x2e3fa10 @name="project_manager">,
 @team_leader=#<TeamLeader:0x2e3f950 @name="team_leader">,
 @tester=#<Tester:0x2e3f7e8 @name="tester">>
#<SamllNetworkProjectTeam:0x2e45c68
 @developer=#<Developer:0x2e45b18 @name="developer">,
 @network_engineer=#<NetworkEngineer:0x2e45ab8 @name="network_engineer">,
 @project_manager=#<ProjectManager:0x2e45b78 @name="project_manager">>
#<MiddleDbProjectTeam:0x2e4df78
 @db_administrator=#<DbAdministrator:0x2e4dd08 @name="db_administrator">,
 @developer=#<Developer:0x2e4dd68 @name="developer">,
 @project_manager=#<ProjectManager:0x2e4ddb0 @name="project_manager">,
 @tester=#<Tester:0x2e4dcc0 @name="tester">>

サンプル2 仕様

基本はサンプル1と同じです。

サンプル2ではマジックメソッドを利用します。
マジックメソッドは method_missing で任意のルールのメソッド名を処理することで簡潔に設定群を記述できます。
書籍ではアンダースコアをメソッド区切りとして処理していましたが、それだとアンダースコアも含むメソッドに対応できないので
「__」を区切り文字として処理するようにしました。

説明に不要なケースを除外し、小規模ネットワーク対応の非DB案件の例だけを利用します。

サンプル2 コード

require 'active_support/core_ext'
require 'tbpgr_utils'
require 'attributes_initializable'
require 'pp'

[:ProjectManager, :TeamLeader, :Developer, :DbAdministrator, :Tester, :NetworkEngineer].each do |klass|
  self.class.const_set klass, Class.new { |k|
    attr_reader :name
    def initialize(name)
      @name = name
    end
  }
end

class TeamBuilder
  attr_reader :team
  def add_project_manager(name)
    @team.project_manager = ProjectManager.new(name)
  end

  def add_developer(name)
    @team.developer = Developer.new(name)
  end
end

class ProjectTeam
  include AttributesInitializable
  attr_accessor_init :name, :project_manager, :developer
end

class SamllNetworkProjectTeam < ProjectTeam
  include AttributesInitializable
  attr_accessor_init :network_engineer
end

class SamllNetworkProjectTeamBuilder < TeamBuilder
  def initialize(name)
    @team = SamllNetworkProjectTeam.new({name: name})
  end

  [ProjectManager, Developer, NetworkEngineer].each do |klass|
    method = klass.to_s.underscore
    define_method :"add_#{method}" do |name|
      @team.send "#{method}=", klass.new(name)
    end
  end

  def method_missing(method, *args)
    each_members = method.to_s.split('__')
    super(method, *args) unless each_members.shift == 'add'
    each_members.each_with_index do |member, i|
      send :"add_#{member}", args[i]
    end
  end
end

snptb = SamllNetworkProjectTeamBuilder.new('small network project')
snptb.add__project_manager__developer__network_engineer 'project_manager', 'developer', 'network_engineer'
pp snptb.team

出力

#<SamllNetworkProjectTeam:0x2f89d58
 @developer=#<Developer:0x2f89b18 @name="developer">,
 @network_engineer=#<NetworkEngineer:0x2f89aa0 @name="network_engineer">,
 @project_manager=#<ProjectManager:0x2f89b60 @name="project_manager">>