Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Bob the Builder - Gr8Conf EU 2017

162 views

Published on

Slides from my Bob the Builder -talk on Gr8Conf EU 2017 about Groovy Builder Pattern

Published in: Software
  • The #1 Woodworking Resource With Over 16,000 Plans, Download 50 FREE Plans...  http://tinyurl.com/yy9yh8fu
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Be the first to like this

Bob the Builder - Gr8Conf EU 2017

  1. 1. Bob the Builder_ Alexander (Sascha) Klein <alexander.klein@codecentric.de>   1
  2. 2. Bob the Builder   2
  3. 3. About me Alexander Klein Branchmanager codecentric AG Curiestr. 2 70563 Stuttgart, Germany +49 (0) 172 529 40 20 alexander.klein@codecentric.de www.codecentric.de blog.codecentric.de @saschaklein   3
  4. 4. Bobs project   4
  5. 5. Domain House Material @Canonical class House { int number Material material Roof roof List<Level> level = [] } enum Material { Bricks, Wood, Concrete }   5
  6. 6. Domain Roof @Canonical class Roof { static String MATERIAL_TILES = 'Tiles' static String MATERIAL_REED = 'Reed' static String MATERIAL_SHINGLES = 'Shingles' String material String color }   6
  7. 7. Domain Level Room @Canonical class Level { String floor List<Room> rooms = [] } @Canonical class Room { String name List<Room> rooms = [] }   7
  8. 8. Builder Pattern   8
  9. 9. Java Builder Pattern   9
  10. 10. Java Builder Pattern static main(def args) { prettyPrint House.builder() .number(1) .material(Material.Bricks) .roof(Roof.builder().material(Roof.MATERIAL_TILES).color('Red').build()) .level([ Level.builder().floor('1nd floor').rooms([ Room.builder().name('Bedroom').build(), Room.builder().name('Childrens room').build() ]).build(), Level.builder().floor('Ground floor').rooms([ Room.builder().name('Living room').build(), Room.builder().name('Kitchen').build() ]).build(), Level.builder().floor('Basement').rooms([ Room.builder().name('Laundry').build(), Room.builder().name('Cellar').rooms([ Room.builder().name('Food storage').build(), Room.builder().name('Store room').build() ]).build() ]).build() ]).build() }   10
  11. 11. Java Builder Pattern { <House> material: Bricks, number: 1, roof: { <Roof> material: Tiles, color: Red } level : [ { <Level> floor: 1nd floor, rooms: [ { <Room> rooms: null, name: Bedroom }, { <Room> rooms: null, name: Childrens room } ] }, { <Level> floor: Ground floor, rooms: [ { <Room> rooms: null, name: Living room}, { <Room> rooms: null name: Kitchen} ] }, { <Level> floor: Basement, rooms: [ { <Room> rooms: null, name: Laundry }, { <Room> name: Cellar, rooms: [ { <Room> rooms: null, name: Food storage }, { <Room> rooms: null, name: Store room } ] } ] } ] }   11
  12. 12. @Builder House same with the other classes @Builder @Canonical class House { int number Material material Roof roof List<Level> level = [] }   12
  13. 13. Groovy Builder Pattern   13
  14. 14. Groovy Builder Pattern static main(def args) { House house = this.newInstance().house(number: 1) { material('Bricks') roof('tiles', color: 'Red') level('1nd floor') { room(name: 'Bedroom') room('Childrens room') } level('Ground floor') { room('Living room') room('Kitchen') } level('Basement') { room('Laundry') room('Cellar') { room('Food storage') room('Store room') } } } prettyPrint(house) }   14
  15. 15. Groovy Builder Pattern { <House> material: Bricks, number: 1, roof: { <Roof> material: Tiles, color: Red } level : [ { <Level> floor: 1nd floor, rooms: [ { <Room> rooms: null, name: Bedroom }, { <Room> rooms: null, name: Childrens room } ] }, { <Level> floor: Ground floor, rooms: [ { <Room> rooms: null, name: Living room}, { <Room> rooms: null name: Kitchen} ] }, { <Level> floor: Basement, rooms: [ { <Room> rooms: null, name: Laundry }, { <Room> name: Cellar, rooms: [ { <Room> rooms: null, name: Food storage }, { <Room> rooms: null, name: Store room } ] } ] } ] }   15
  16. 16. Groovy Builder Pattern General contract node to create → method-name 0..1 values often used as value for an important attribute 0..n attributes 0..1 Closure for subnodes node(value?, attributes*) { subnode(value?, attributes*) { leaf(value?, attributes*) } }   16
  17. 17. Handcraft   17
  18. 18. Handcraft Scaffolding Result class Handcraft1 { House house(Closure blueprint) { House house = new House() Closure plan = blueprint.clone() plan.delegate = this plan.resolveStrategy = Closure.DELEGATE_ONLY // OR DELEGATE_FIRST plan() return house } static main(def args) { House house = this.newInstance().house {} prettyPrint(house) } } { <House> material: null, roof: null, number: 0, level: [] }   18
  19. 19. Handcraft Adding details import static bob.v1.Material.Bricks class Handcraft2 { House house(Map attributes = [:], Closure blueprint = {}) { House house = new House(attributes) Closure plan = blueprint.clone() plan.delegate = this plan.resolveStrategy = Closure.DELEGATE_ONLY plan() return house } static main(def args) { House house = this.newInstance().house(number: 1, material: Bricks) prettyPrint(house) } }   19
  20. 20. Handcraft Result { <House> material: Bricks, roof: null, number: 1, level: [] }   20
  21. 21. Handcraft Roofing …​ House house House house(Map attributes = [:], Closure blueprint = {}) { house = new House(attributes) void roof(Map attributes = [:], String material) { Roof roof = new Roof(attributes) roof.material = material house.roof = roof } static main(def args) { House house = this.newInstance().house(number: 1, material: Bricks) { roof('tiles', color: 'Red') } prettyPrint(house) }   21
  22. 22. Handcraft Result { <House> material: Bricks, number: 1, roof : { <Roof> material: tiles, color: Red }, level : [] }   22
  23. 23. Handcraft Refactoring House house House house(Map attributes = [:], Closure blueprint = {}) { house = new House(attributes) runClosure(blueprint) return house } private runClosure(Closure cls) { if (cls) { Closure clone = cls.clone() clone.delegate = this clone.resolveStrategy = Closure.DELEGATE_ONLY clone() } } // ...   23
  24. 24. Handcraft Storing the context List stack = [] def parent def current private runClosure(Closure cls, def instance) { parent = stack ? stack?.last() : null stack.push(instance) current = instance if (cls) { Closure clone = cls.clone() clone.delegate = this clone.resolveStrategy = Closure.DELEGATE_ONLY clone() } current = stack ? stack.last() : null stack.pop() parent = stack ? stack.last() : null }   24
  25. 25. Handcraft Creating the stories void level(String floor, Closure blueprint = {}) { Level level = new Level(floor: floor) runClosure(blueprint, level) parent.level << level } static main(def args) { House house = this.newInstance().house(number: 1, material: Bricks) { roof('tiles', color: 'Red') level('1nd floor') level('Ground floor') level('Basement') } prettyPrint(house) }   25
  26. 26. Handcraft Result { <House> material : Bricks, number : 1 roof : { <Roof> material: tiles, color: Red }, level : [ { <Level> floor: 1nd floor, rooms: [] }, { <Level> floor: Ground floor, rooms: [] }, { <Level> floor: Basement, rooms: [] } ] }   26
  27. 27. Handcraft Material as a node void material(def material) { if (material instanceof String) material = Material.valueOf(material) current.material = material } static main(def args) { House house = this.newInstance().house(number: 1) { material('Bricks') roof('tiles', color: 'Red') level('1nd floor') level('Ground floor') level('Basement') } prettyPrint(house) }   27
  28. 28. Handcraft Result { <House> material : Bricks, number : 1 roof : { <Roof> material: tiles, color: Red }, level : [ { <Level> floor: 1nd floor, rooms: [] }, { <Level> floor: Ground floor, rooms: [] }, { <Level> floor: Basement, rooms: [] } ] }   28
  29. 29. Handcraft Building the rooms static main(def args) { House house = this.newInstance().house(number: 1) { material('Bricks') roof('tiles', color: 'Red') level('1nd floor') { room(name: 'Bedroom') room('Childrens room') } level('Ground floor') { room('Living room') room('Kitchen') } level('Basement') { room('Laundry') room('Cellar') { room('Food storage') room('Store room') } } } prettyPrint(house) }   29
  30. 30. Handcraft Building the rooms void room(String name, Closure blueprint = null) { room([:], name, blueprint) } void room(Map attributes, String name = null, Closure blueprint = null) { Room room = new Room(attributes) if (name != null) room.name = name runClosure(blueprint, room) parent.rooms << room }   30
  31. 31. Handcraft { <House> material: Bricks, number: 1, roof: { <Roof> material: Tiles, color: Red } level : [ { <Level> floor: 1nd floor, rooms: [ { <Room> rooms: null, name: Bedroom }, { <Room> rooms: null, name: Childrens room } ] }, { <Level> floor: Ground floor, rooms: [ { <Room> rooms: null, name: Living room}, { <Room> rooms: null name: Kitchen} ] }, { <Level> floor: Basement, rooms: [ { <Room> rooms: null, name: Laundry }, { <Room> name: Cellar, rooms: [ { <Room> rooms: null, name: Food storage }, { <Room> rooms: null, name: Store room } ] } ] } ] }   31
  32. 32. Using a plant   32
  33. 33. Using a plant groovy.util.BuilderSupport   33
  34. 34. Using a plant Scaffolding class Plant1 extends BuilderSupport { @Override protected Object createNode(Object name, Map attributes = [:]) { return createNode(name, attributes, null) } @Override protected Object createNode(Object name, Map attributes = [:], Object value) { switch (name) { case 'house': return new House(attributes) default: return null } } @Override protected void setParent(Object parent, Object child) {} static main(def args) { House house = this.newInstance().house(number: 1) {} prettyPrint(house) } }   34
  35. 35. Using a plant Adding details @Override protected Object createNode(Object name, Map attributes = [:], Object value){ switch (name) { case 'house': return new House(attributes) case 'roof': return new Roof(attributes + [material: value]) default: return null } } @Override protected void setParent(Object parent, Object child) { switch (child) { case Roof: parent.roof = child; break } } static main(def args) { House house = this.newInstance().house(number: 1) { roof('tiles', color: 'Red') } prettyPrint(house) }   35
  36. 36. Using a plant Roofs and storages @Override protected Object createNode(Object name, Map attributes = [:], Object value){ switch (name) { case 'house': return new House(attributes) case 'roof': return new Roof(attributes + [material: value]) case 'level': return new Level(floor: value) default: return null } } @Override protected void setParent(Object parent, Object child) { switch (child) { case Roof: parent.roof = child; break case Level: parent.level << child; break } }   36
  37. 37. Using a plant Roofs and storages static main(def args) { House house = this.newInstance().house(number: 1) { roof('tiles', color: 'Red') level('1nd floor') {} level('Ground floor') {} level('Basement') {} } prettyPrint(house) }   37
  38. 38. Using a plant Material as a node @Override protected Object createNode(Object name, Map attributes = [:], Object value) { switch (name) { case 'house': return new House(attributes) case 'roof': return new Roof(attributes + [material: value]) case 'level': return new Level(floor: value) case 'material': return value instanceof Material ? value : Material.valueOf(value.toString()) default: return null } } @Override protected void setParent(Object parent, Object child) { switch (child) { case Roof: parent.roof = child; break case Level: parent.level << child; break case Material: parent.material = child; break } }   38
  39. 39. Using a plant Material as a node static main(def args) { House house = this.newInstance().house(number: 1) { material('Bricks') roof('tiles', color: 'Red') level('1nd floor') {} level('Ground floor') {} level('Basement') {} } prettyPrint(house) }   39
  40. 40. Using a plant Building the rooms @Override protected Object createNode(Object name, Map attributes = [:], Object value) { switch (name) { case 'house': return new House(attributes) case 'roof': return new Roof(attributes + [material: value]) case 'level': return new Level(floor: value) case 'material': return value instanceof Material ? value : Material.valueOf(value.toString()) case 'room': String roomName = value ?: attributes.remove('name') return new Room(attributes + [name: roomName]) default: return null } } @Override protected void setParent(Object parent, Object child) { switch (child) { case Roof: parent.roof = child; break case Level: parent.level << child; break case Material: parent.material = child; break case Room: parent.rooms << child; break } }   40
  41. 41. Using a plant Building the rooms static main(def args) { House house = this.newInstance().house(number: 1) { material('Bricks') roof('tiles', color: 'Red') level('1nd floor') { room(name: 'Bedroom') room('Childrens room') } level('Ground floor') { room('Living room') room('Kitchen') } level('Basement') { room('Laundry') room('Cellar') { room('Food storage') room('Store room') } } } prettyPrint(house) }   41
  42. 42. Factory scale   42
  43. 43. Factory scale groovy.util.FactoryBuilderSupport   43
  44. 44. Factory scale Scaffolding All methods starting with 'register' will be executed Part after 'register' is used as group name Group name is internally stored to be queried in your builder class Factory extends FactoryBuilderSupport { public Factory(boolean init = true) { super(init) } def registerNodes() { registerBeanFactory('house', House) } static main(def args) { prettyPrint this.newInstance(true).house(number: 1) } }   44
  45. 45. Factory scale Roofs def registerNodes() { registerBeanFactory('house', House) registerFactory('roof', new RoofFactory()) } static main(def args) { prettyPrint this.newInstance(true).house(number: 1) { roof('tiles', color: 'Red') } }   45
  46. 46. Factory scale RoofFactory class RoofFactory extends AbstractFactory { @Override Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) throws InstantiationException, IllegalAccessException { return new Roof(material: attributes.remove('material') ?: value) } @Override boolean isLeaf() { true } @Override void setParent(FactoryBuilderSupport builder, Object parent, Object child) { if (parent instanceof House) parent.roof = child } }   46
  47. 47. Factory scale Storages def registerNodes() { registerBeanFactory('house', House) registerFactory('roof', new RoofFactory()) registerFactory('level', new LevelFactory()) } static main(def args) { prettyPrint this.newInstance(true).house(number: 1) { roof('tiles', color: 'Red') level('1nd floor') {} level('Ground floor') {} level('Basement') {} } }   47
  48. 48. Factory scale LevelFactory class LevelFactory extends AbstractFactory { @Override Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) throws InstantiationException, IllegalAccessException { return new Level(floor: attributes.remove('floor') ?: value) } @Override void setParent(FactoryBuilderSupport builder, Object parent, Object child) { if (parent instanceof House) parent.level << child } }   48
  49. 49. Factory scale Material def registerNodes() { registerBeanFactory('house', House) registerFactory('roof', new RoofFactory()) registerFactory('level', new LevelFactory()) registerFactory('material', new MaterialFactory()) } static main(def args) { prettyPrint this.newInstance(true).house(number: 1) { material('Bricks') roof('tiles', color: 'Red') level('1nd floor') {} level('Ground floor') {} level('Basement') {} } }   49
  50. 50. Factory scale MaterialFactory class MaterialFactory extends AbstractFactory { @Override Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) throws InstantiationException, IllegalAccessException { return value instanceof Material ? value : Material.valueOf(value) } @Override void setParent(FactoryBuilderSupport builder, Object parent, Object child) { if (parent instanceof House) parent.material = child } }   50
  51. 51. Factory scale Building the rooms def registerNodes() { registerBeanFactory('house', House) registerFactory('roof', new RoofFactory()) registerFactory('level', new LevelFactory()) registerFactory('material', new MaterialFactory()) registerFactory('room', new RoomFactory()) }   51
  52. 52. Factory scale Building the rooms static main(def args) { prettyPrint this.newInstance(true).house(number: 1) { material('Bricks') roof('tiles', color: 'Red') level('1nd floor') { room(name: 'Bedroom') room('Childrens room') } level('Ground floor') { room('Living room') room('Kitchen') } level('Basement') { room('Laundry') room('Cellar') { room('Food storage') room('Store room') } } } }   52
  53. 53. Factory scale RoomFactory class RoomFactory extends AbstractFactory { @Override Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) throws InstantiationException, IllegalAccessException { return new Room((attributes.remove('name') ?: value)?.toString()) } @Override void setParent(FactoryBuilderSupport builder, Object parent, Object child) { if (parent instanceof Level) parent.rooms << child // else if (parent instanceof Room) // parent.rooms << child } @Override void setChild(FactoryBuilderSupport builder, Object parent, Object child) { if (child instanceof Room) parent.rooms << child } }   53
  54. 54. Miscellaneous   54
  55. 55. Temperature Collecting data externally static main(def args) { def builder = this.newInstance(true) prettyPrint builder.house(number: 1, temperature: 60) { material('Bricks') roof('tiles', color: 'Red') level('1nd floor') { room(name: 'Bedroom') { cooler(-5) } room('Childrens room') { heating(5) } } level('Basement', temperature: 55) { room('Laundry') room('Cellar', temperature: 50) { room('Food storage', temperature: 40) room('Store room') } } } println "Room Temperatures: $builder.roomTemperature" }   55
  56. 56. Temperature Attribute Delegates public FactoryTemperature(boolean init = true) { super(init) setVariable('roomTemperature', [:]) } def registerNodes() { registerBeanFactory('house', House) registerFactory('roof', new RoofFactory()) registerFactory('level', new LevelFactory()) registerFactory('material', new MaterialFactory()) registerFactory('room', new RoomFactory()) addAttributeDelegate { FactoryBuilderSupport builder, Object node, Map attributes -> def temperature = attributes.remove 'temperature' if (temperature instanceof Number) builder.context.temperature = temperature else { temperature = builder.parentContext.temperature if (temperature) builder.context.temperature = temperature } } }   56
  57. 57. Temperature Using transparent nodes TemperatureFactory registerFactory('heating', new TemperatureFactory()) registerFactory('cooler', new TemperatureFactory()) class TemperatureFactory extends AbstractFactory { @Override Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) throws InstantiationException, IllegalAccessException { return [modifier: value] } @Override void setParent(FactoryBuilderSupport builder, Object parent, Object child) { Number temperature = builder.parentContext.temperature + child.modifier builder.roomTemperature[parent.name] = temperature } }   57
  58. 58. Temperature Result Room Temperatures: [ Bedroom:55, Childrens room:65, Laundry:55, Cellar:50, Food storage:40, Store room:50 ]   58
  59. 59. Ringing the bell Taking control over the Closure registerFactory('bell', new BellFactory()) static main(def args) { House house = this.newInstance(true).house(number: 1) { bell { println "The door rings" } material('Bricks') roof('tiles', color: 'Red') level('1nd floor') { room(name: 'Bedroom') room('Childrens room') } } prettyPrint house house.ring() }   59
  60. 60. Ringing the bell BellFactory class BellFactory extends AbstractFactory { @Override Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) throws InstantiationException, IllegalAccessException { return [:] } @Override boolean isLeaf() { return false } @Override boolean isHandlesNodeChildren() { true } @Override boolean onNodeChildren(FactoryBuilderSupport builder, Object node, Closure childContent) { builder.current.bell = childContent return false } }   60
  61. 61. Linking Doors Door instance @Canonical class Door { Room one Room two }   61
  62. 62. Linking Doors static main(def args) { prettyPrint this.newInstance(true).house(number: 1) { level('1nd floor') { room(name: 'Bedroom') room('Childrens room') { door('Bedroom') } } level('Ground floor') { room('Living room') { door('Kitchen') } room('Kitchen') } level('Basement') { room('Laundry') { door('Food storage') } room('Cellar') { room('Food storage') { door('Laundry') } room('Store room') { door('Laundry') } } } } }   62
  63. 63. Linking Doors class RoomFactory extends AbstractFactory { @Override Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) throws InstantiationException, IllegalAccessException { Room room = new Room((attributes.remove('name') ?: value)?.toString()) getLevelContext(builder).roomMap."$room.name" = room return room } @Override void setParent(FactoryBuilderSupport builder, Object parent, Object child) { if (parent instanceof Level) parent.rooms << child } @Override void setChild(FactoryBuilderSupport builder, Object parent, Object child) { if (child instanceof Room) parent.rooms << child } Map getLevelContext(FactoryBuilderSupport builder) { Map levelContext = builder.parentContext while (levelContext."$FactoryBuilderSupport.CURRENT_NAME" != 'level') levelContext = levelContext."$FactoryBuilderSupport.PARENT_CONTEXT" return levelContext } }   63
  64. 64. Linking Doors DoorFactory registerFactory('door', new DoorFactory()) class DoorFactory extends AbstractFactory { @Override Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) throws InstantiationException, IllegalAccessException { return [from: value] } @Override void setParent(FactoryBuilderSupport builder, Object parent, Object child) { Map levelContext = builder.parentFactory.getLevelContext(builder) Room other = levelContext.roomMap."$child.from" if (other) { Door door = new Door(other, parent) other.doors << door parent.doors << door } else { levelContext.freeDoors."$child.from" = parent } } }   64
  65. 65. Linking Doors LevelFactory class LevelFactory extends AbstractFactory { @Override Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attributes) throws InstantiationException, IllegalAccessException { builder.context.roomMap = [:] builder.context.freeDoors = [:] return new Level(floor: attributes.remove('floor') ?: value) } @Override void setParent(FactoryBuilderSupport builder, Object parent, Object child) { if (parent instanceof House) parent.level << child } @Override void onNodeCompleted(FactoryBuilderSupport builder, Object parent, Object node) { builder.context.freeDoors.each { String name, Room room -> def other = builder.context.roomMap."$name" if (other) { Door door = new Door(room, other) other.doors << door room.doors << door } } } }   65
  66. 66. Linking Doors Result { <House> material: null, roof: null, number: 1 level : [ { <Level> floor: 1nd floor, rooms: [ { <Room> name: Bedroom, rooms: [], doors: [ <Door> room: Bedroom, room: Childrens room ] }, { <Room> name: Childrens room, rooms: [], doors: [ <Door> room: Bedroom, room: Childrens room ] } ] }, { <Level> floor : Ground floor, rooms: [ { <Room> name: Living room, rooms: [], doors: [ <Door> room: Living room, room: Kitchen ] }, { <Room> name: Kitchen, rooms: [], doors: [ <Door> room: Living room, room: Kitchen ] } ] },   66
  67. 67. Linking Doors Result { <Level> floor: Basement, rooms: [ { <Room> name: Laundry, rooms: [], doors: [ <Door> room: Laundry, room: Food storage, <Door> room: Laundry, room: Store room ] }, { <Room> name: Cellar, rooms: [ { <Room> name: Food storage, rooms: [], doors: [ <Door> room: Laundry, room: Food storage ] }, { <Room> name: Store room, rooms: [], doors: [ <Door> room: Laundry, room: Store room ] } ], doors: [] } ] } ] }   67
  68. 68. Untouched groovy.util.Factory groovy.util.FactoryBuilderSupport void onFactoryRegistration(FactoryBuilderSupport builder, String name, String groupName) void registerExplicitProperty(String name, Closure getter, Closure setter) void registerExplicitMethod(String name, Closure closure) Closure addPreInstantiateDelegate(Closure delegate) Closure addPostInstantiateDelegate(Closure delegate) Closure addPostNodeCompletionDelegate(Closure delegate)   68
  69. 69. Questions? Alexander Klein Branchmanager codecentric AG Curiestr. 2 70563 Stuttgart, Germany +49 (0) 172 529 40 20 alexander.klein@codecentric.de www.codecentric.de blog.codecentric.de @saschaklein   69

×