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.
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.
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.
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.
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.
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.
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 →