概要
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">>