Combining Objects with
Composition
POODR CH8
Goal
▸ Techniques of OO composition with example
▸ Discussion of the relative strengths and weakness of
composition and in...
Code reuse Approaches?
▸ Copy and Paste
▸ Inheritance
▸ Mixin
▸ Composition
Definition from Wiki
▸ A way to combine simple objects or data types into more
complex ones.
▸ https://en.wikipedia.org/wik...
1 class Bicycle
2 attr_reader :size, :chain, :tire_size
3 def initialize(args = {})
4 @size = args[:size]
5 @chain = args[...
A Bicycle has-a Parts
Bicycle Parts
1
MountainBikeParts
RoadBikeParts
1 class Bicycle
2 attr_reader :size, :chain, :tire_size
3 def initialize(args = {})
4 @size = args[:size]
5 @chain = args[...
1 class Bicycle
2 attr_reader :size, :parts
3 def initialize(args = {})
4 @size = args[:size]
5 @parts = args[:parts]
6 en...
1 class RoadBike < Bicycle
2 attr_reader :tape_color
3 def post_initialize(args)
4 @tape_color = args[:tape_color]
5 end
6...
1 road_bike = RoadBike.new(
2 size: 'L',
3 tape_color: 'red'
4 )
1 road_bike = Bicycle.new(
2 size: 'L',
3 parts: RoadBike...
A Bicycle has-a Parts which with many Part
Bicycle Parts
1
Part
1..*
1 class Parts
2 attr_reader :parts
3 def initialize(parts)
4 @parts = parts
5 end
6
7 def spares
8 parts.select(&:needs_sp...
1 class RoadBikeParts < Parts
2 attr_reader :tape_color
3 def post_initialize(args)
4 @tape_color = args[:tape_color]
5 en...
1 road_bike = Bicycle.new(
2 size: 'L',
3 tape_color: 'red'
4 )
1 road_bike = Bicycle.new(
2 size: 'L',
3 parts: RoadBikeP...
Making the Parts more like an Array
▸ Add size method in to Parts class manually
▸ How about each, sort and more…
▸ Let Pa...
A middle ground between complexity and usability
1 require 'forwardable'
2 class Parts
3 extend Forwardable
4 def_delegato...
Knowledge leakage
1 chain = Part.new(name: 'chain', description:
2 '10-speed')
3 road_tire = Part.new(name: 'tire_size',
4...
Manufacturing parts
▸ Everything would be easier if you could describe the
different bikes and then use your descriptions ...
1 road_config =
2 [['chain', '10-speed'],
3 %w(tire_size 23),
4 %w(tape_color red)]
5 mountain_config =
6 [['chain', '10-s...
1 module PartsFactory
2 def self.build(config,
3 part_class = Part, parts_class = Parts)
4 parts_class.new(
5 config.colle...
Aggregation
▸ Aggregation is exactly like composition except that the
contained object has an independent life.
Inheritance vs. Composition
Inheritance Composition
Delegation of Message Free Cost
Hierarchy of Class Cost Free
Inheritance: What will happen when I’m wrong?
▸ That is, Incorrectly modeled hierarchy
▸ Making small changes near the top...
Using Inheritance when satisfied …
▸ Reasonable
▸ Big changes in behavior can be achieved via small
changes in code.
▸ Usab...
Drawbacks of Mixin
▸ Too powerful, so its easy to abuse.
▸ Refactoring anti-pattern: God object
▸ You still have one objec...
How about composition
▸ Small objects, SRP, transparent.
▸ Simple and pluggable objects that are easy to extend and
have a...
Concepts
▸ Inheritance
▸ Subclass Is-A specialization of Superclass.
▸ Mixin
▸ Class A take A Role Of Module B (Module B i...
Conclusion
▸ Can't figure out what to do? Use composition.
▸ The key to improving your design skills is to attempt these 
t...
Reference
▸ Re-use in OO: Inheritance, Composition and Mixins.
▸ Composition over Mixins
▸ Factory pattern - Design patter...
of 29

Poodr ch8-composition

Study group topic
Published on: Mar 4, 2016
Published in: Software      
Source: www.slideshare.net


Transcripts - Poodr ch8-composition

  • 1. Combining Objects with Composition POODR CH8
  • 2. Goal ▸ Techniques of OO composition with example ▸ Discussion of the relative strengths and weakness of composition and inheritance (and mixins)
  • 3. Code reuse Approaches? ▸ Copy and Paste ▸ Inheritance ▸ Mixin ▸ Composition
  • 4. Definition from Wiki ▸ A way to combine simple objects or data types into more complex ones. ▸ https://en.wikipedia.org/wiki/Object_composition
  • 5. 1 class Bicycle 2 attr_reader :size, :chain, :tire_size 3 def initialize(args = {}) 4 @size = args[:size] 5 @chain = args[:chain] || default_chain 6 @tire_size = args[:tire_size] || 7 default_tire_size 8 post_initialize(args) 9 end 10 11 def spares 12 { tire_size: tire_size, 13 chain: chain }.merge(local_spares) 14 end 15 16 def default_tire_size 17 fail NotImplementedError 18 end 19 20 def post_initialize(_args) 21 nil 22 end 23 24 def local_spares 25 {} 26 end 27 28 def default_chain 29 '10-speed' 30 end 31 end 1 class RoadBike < Bicycle 2 attr_reader :tape_color 3 def post_initialize(args) 4 @tape_color = args[:tape_color] 5 end 6 7 def local_spares 8 { tape_color: tape_color } 9 end 10 11 def default_tire_size 12 '23' 13 end 14 end 1 class MountainBike < Bicycle 2 attr_reader :front_shock, :rear_shock 3 def post_initialize(args) 4 @front_shock = args[:front_shock] 5 @rear_shock = args[:rear_shock] 6 end 7 8 def local_spares 9 { rear_shock: rear_shock } 10 end 11 12 def default_tire_size 13 '2.1' 14 end 15 end
  • 6. A Bicycle has-a Parts Bicycle Parts 1 MountainBikeParts RoadBikeParts
  • 7. 1 class Bicycle 2 attr_reader :size, :chain, :tire_size 3 def initialize(args = {}) 4 @size = args[:size] 5 @chain = args[:chain] || default_chain 6 @tire_size = args[:tire_size] || 7 default_tire_size 8 post_initialize(args) 9 end 10 11 def spares 12 { tire_size: tire_size, 13 chain: chain }.merge(local_spares) 14 end 15 16 def default_tire_size 17 fail NotImplementedError 18 end 19 20 def post_initialize(_args) 21 nil 22 end 23 24 def local_spares 25 {} 26 end 27 28 def default_chain 29 '10-speed' 30 end 31 end 1 class Bicycle 2 attr_reader :size, :parts 3 def initialize(args = {}) 4 @size = args[:size] 5 @parts = args[:parts] 6 end 7 8 def spares 9 parts.spares 10 end 11 end
  • 8. 1 class Bicycle 2 attr_reader :size, :parts 3 def initialize(args = {}) 4 @size = args[:size] 5 @parts = args[:parts] 6 end 7 8 def spares 9 parts.spares 10 end 11 end 1 class Parts 2 attr_reader :chain, :tire_size 3 def initialize(args={}) 4 @chain = args[:chain] || default_chain 5 @tire_size = args[:tire_size] || 6 default_tire_size 7 post_initialize(args) 8 end 9 10 def spares 11 { tire_size: tire_size, 12 chain: chain }.merge(local_spares) 13 end 14 15 def default_tire_size 16 fail NotImplementedError 17 end 18 19 def post_initialize(_args) 20 nil 21 end 22 23 def local_spares 24 {} 25 end 26 27 def default_chain 28 '10-speed' 29 end 30 end
  • 9. 1 class RoadBike < Bicycle 2 attr_reader :tape_color 3 def post_initialize(args) 4 @tape_color = args[:tape_color] 5 end 6 7 def local_spares 8 { tape_color: tape_color } 9 end 10 11 def default_tire_size 12 '23' 13 end 14 end 1 class MountainBike < Bicycle 2 attr_reader :front_shock, :rear_shock 3 def post_initialize(args) 4 @front_shock = args[:front_shock] 5 @rear_shock = args[:rear_shock] 6 end 7 8 def local_spares 9 { rear_shock: rear_shock } 10 end 11 12 def default_tire_size 13 '2.1' 14 end 15 end 1 class RoadBikeParts < Parts 2 attr_reader :tape_color 3 def post_initialize(args) 4 @tape_color = args[:tape_color] 5 end 6 7 def local_spares 8 { tape_color: tape_color } 9 end 10 11 def default_tire_size 12 '23' 13 end 14 end 1 class MountainBikeParts < Parts 2 attr_reader :front_shock, :rear_shock 3 def post_initialize(args) 4 @front_shock = args[:front_shock] 5 @rear_shock = args[:rear_shock] 6 end 7 8 def local_spares 9 { rear_shock: rear_shock } 10 end 11 12 def default_tire_size 13 '2.1' 14 end 15 end
  • 10. 1 road_bike = RoadBike.new( 2 size: 'L', 3 tape_color: 'red' 4 ) 1 road_bike = Bicycle.new( 2 size: 'L', 3 parts: RoadBikeParts.new(tape_color: 'red') 4 ) Instantiate
  • 11. A Bicycle has-a Parts which with many Part Bicycle Parts 1 Part 1..*
  • 12. 1 class Parts 2 attr_reader :parts 3 def initialize(parts) 4 @parts = parts 5 end 6 7 def spares 8 parts.select(&:needs_spare) 9 end 10 end 1 class Parts 2 attr_reader :chain, :tire_size 3 def initialize(args={}) 4 @chain = args[:chain] || default_chain 5 @tire_size = args[:tire_size] || 6 default_tire_size 7 post_initialize(args) 8 end 9 10 def spares 11 { tire_size: tire_size, 12 chain: chain }.merge(local_spares) 13 end 14 15 def default_tire_size 16 fail NotImplementedError 17 end 18 19 def post_initialize(_args) 20 nil 21 end 22 23 def local_spares 24 {} 25 end 26 27 def default_chain 28 '10-speed' 29 end 30 end 1 class Part 2 attr_reader :name, :description, :needs_spare 3 def initialize(args) 4 @name = args[:name] 5 @description = args[:description] 6 @needs_spare = args.fetch(:needs_spare, 7 true) 8 end 9 end
  • 13. 1 class RoadBikeParts < Parts 2 attr_reader :tape_color 3 def post_initialize(args) 4 @tape_color = args[:tape_color] 5 end 6 7 def local_spares 8 { tape_color: tape_color } 9 end 10 11 def default_tire_size 12 '23' 13 end 14 end 1 class MountainBikeParts < Parts 2 attr_reader :front_shock, :rear_shock 3 def post_initialize(args) 4 @front_shock = args[:front_shock] 5 @rear_shock = args[:rear_shock] 6 end 7 8 def local_spares 9 { rear_shock: rear_shock } 10 end 11 12 def default_tire_size 13 '2.1' 14 end 15 end
  • 14. 1 road_bike = Bicycle.new( 2 size: 'L', 3 tape_color: 'red' 4 ) 1 road_bike = Bicycle.new( 2 size: 'L', 3 parts: RoadBikeParts.new(tape_color: 'red') 4 ) Instantiate 1 chain = Part.new(name: 'chain', description: 2 '10-speed') 3 road_tire = Part.new(name: 'tire_size', 4 description: '23') 5 tape = Part.new(name: 'tape_color', description: 6 'red') 7 8 road_bike = Bicycle.new( 9 size: 'L', 10 parts: Parts.new([chain, 11 road_tire, 12 tape]) 13 )
  • 15. Making the Parts more like an Array ▸ Add size method in to Parts class manually ▸ How about each, sort and more… ▸ Let Parts inherit Array ▸ Some behaviors may not be expected !! ▸ There is no perfect solution
  • 16. A middle ground between complexity and usability 1 require 'forwardable' 2 class Parts 3 extend Forwardable 4 def_delegators :@parts, :size, :each 5 include Enumerable 6 7 def initialize(parts) 8 @parts = parts 9 end 10 11 def spares 12 parts.select(&:needs_spare) 13 end 14 end
  • 17. Knowledge leakage 1 chain = Part.new(name: 'chain', description: 2 '10-speed') 3 road_tire = Part.new(name: 'tire_size', 4 description: '23') 5 tape = Part.new(name: 'tape_color', description: 6 'red') 7 8 road_bike = Bicycle.new( 9 size: 'L', 10 parts: Parts.new([chain, 11 road_tire, 12 tape]) 13 ) Need knowledge of how to create part Need to know road bike’s all parts
  • 18. Manufacturing parts ▸ Everything would be easier if you could describe the different bikes and then use your descriptions to magically manufacture the correct Parts object for any bike. ▸ 2-dimensional array ▸ Unlike a hash, this simple 2-dimensional array provides no structural information.
  • 19. 1 road_config = 2 [['chain', '10-speed'], 3 %w(tire_size 23), 4 %w(tape_color red)] 5 mountain_config = 6 [['chain', '10-speed'], 7 ['tire_size', '2.1'], 8 ['front_shock', 'Manitou', false], 9 %w(rear_shock Fox)] 1 module PartsFactory 2 def self.build(config, 3 part_class = Part, parts_class = Parts) 4 parts_class.new( 5 config.collect do|part_config| 6 part_class.new( 7 name: part_config[0], 8 description: part_config[1], 9 needs_spare: part_config.fetch(2, 10 true)) 11 end 12 ) 13 end 14 end 1 road_bike = Bicycle.new( 2 size: 'L', 3 parts: PartsFactory.build(road_config) 4 )
  • 20. 1 module PartsFactory 2 def self.build(config, 3 part_class = Part, parts_class = Parts) 4 parts_class.new( 5 config.collect do|part_config| 6 part_class.new( 7 name: part_config[0], 8 description: part_config[1], 9 needs_spare: part_config.fetch(2, 10 true)) 11 end 12 ) 13 end 14 end 1 class Part 2 attr_reader :name, :description, :needs_spare 3 def initialize(args) 4 @name = args[:name] 5 @description = args[:description] 6 @needs_spare = args.fetch(:needs_spare, 7 true) 8 end 9 end 1 require 'ostruct' 2 module PartsFactory 3 def self.build(config, parts_class = Parts) 4 parts_class.new( 5 config.collect { |part_config| 6 create_part(part_config) 7 } 8 ) 9 end 10 11 def self.create_part(part_config) 12 OpenStruct.new( 13 name: part_config[0], 14 description: part_config[1], 15 needs_spare: part_config.fetch(2, true) 16 ) 17 end 18 end
  • 21. Aggregation ▸ Aggregation is exactly like composition except that the contained object has an independent life.
  • 22. Inheritance vs. Composition Inheritance Composition Delegation of Message Free Cost Hierarchy of Class Cost Free
  • 23. Inheritance: What will happen when I’m wrong? ▸ That is, Incorrectly modeled hierarchy ▸ Making small changes near the top of hierarchy break everything. ▸ You will be forced to duplicate or restructure code. ▸ Impossibility of adding behavior when new subclasses represent a mixture of types. ▸ Might be used by others for purposes you did not anticipate
  • 24. Using Inheritance when satisfied … ▸ Reasonable ▸ Big changes in behavior can be achieved via small changes in code. ▸ Usable ▸ Easily create new subclasses. ▸ Exemplary
  • 25. Drawbacks of Mixin ▸ Too powerful, so its easy to abuse. ▸ Refactoring anti-pattern: God object ▸ You still have one object with the same number of public methods. ▸ The rules of coupling and cohesion start to come into play
  • 26. How about composition ▸ Small objects, SRP, transparent. ▸ Simple and pluggable objects that are easy to extend and have a high tolerance for change. ▸ Cost of management small parts collection, but relatively easy to control.
  • 27. Concepts ▸ Inheritance ▸ Subclass Is-A specialization of Superclass. ▸ Mixin ▸ Class A take A Role Of Module B (Module B is Adjective) ▸ Composition ▸ Class A Has-A class B
  • 28. Conclusion ▸ Can't figure out what to do? Use composition. ▸ The key to improving your design skills is to attempt these  techniques, accept your errors cheerfully, remain detached  from past design decisions, and refactor mercilessly.
  • 29. Reference ▸ Re-use in OO: Inheritance, Composition and Mixins. ▸ Composition over Mixins ▸ Factory pattern - Design patterns in ruby

Related Documents