Solution: Complete Refactor to Composition

Continuing from the classical inheritance example, we now take the Bicycle system refactor to its final state using composition as described in Practical Object-Oriented Design in Ruby (POODR) by Sandi Metz. This final implementation demonstrates a fully modular and flexible design, leveraging composition effectively.

Final Refactored Implementation

In this final state, the system is fully refactored to encapsulate parts into modular, reusable objects, and define part-specific configurations dynamically. This approach eliminates the rigidity of inheritance hierarchies.

Step 1: `Parts` and `Part` Classes

class Part
  attr_reader :name, :required

  def initialize(name:, required:)
    @name = name
    @required = required
  end

  def required?
    @required
  end
end

class Parts
  attr_reader :parts

  def initialize(parts)
    @parts = parts
  end

  def spares
    @parts.select(&:required?)
  end

  def size
    @parts.size
  end

  include Enumerable

  def each(&block)
    @parts.each(&block)
  end
end
        

The `Part` class represents individual components, while the `Parts` collection manages these components, providing behaviors like iteration and spare parts filtering.

Step 2: Configuring Specific Parts

road_bike_parts_config = [
  Part.new(name: "chain", required: true),
  Part.new(name: "tire_size", required: true),
  Part.new(name: "tape_color", required: true)
]

mountain_bike_parts_config = [
  Part.new(name: "chain", required: true),
  Part.new(name: "tire_size", required: true),
  Part.new(name: "front_shock", required: true),
  Part.new(name: "rear_shock", required: false)
]

road_bike_parts = Parts.new(road_bike_parts_config)
mountain_bike_parts = Parts.new(mountain_bike_parts_config)
        

These configurations encapsulate all part-specific details, making the system extensible without altering existing classes.

Step 3: Simplified `Bicycle` Class

class Bicycle
  attr_reader :size, :parts

  def initialize(size:, parts:)
    @size = size
    @parts = parts
  end

  def spares
    parts.spares
  end
end
        

The `Bicycle` class is now a simple container, delegating all parts-related behaviors to the `Parts` object.

Final Usage

road_bike = Bicycle.new(size: "M", parts: road_bike_parts)
mountain_bike = Bicycle.new(size: "L", parts: mountain_bike_parts)

puts road_bike.spares
puts mountain_bike.spares
        

This design minimizes duplication, enhances flexibility, and makes the system easier to understand and extend. All behaviors are encapsulated in the appropriate objects, adhering to SOLID principles.

Conclusion

By fully embracing composition, we’ve transformed the Bicycle system into a modular and extensible design. This refactor showcases how shifting from inheritance to composition can align systems with modern software design principles, ensuring scalability and maintainability for the long term.

Continue to Code Examples →