CrackedRuby CrackedRuby

Composition vs Inheritance

Overview

Composition and inheritance represent two fundamental approaches to code reuse and relationship modeling in object-oriented programming. Inheritance creates "is-a" relationships where a subclass inherits behavior from a parent class, while composition creates "has-a" relationships where an object contains other objects to delegate behavior.

The composition versus inheritance debate addresses a core design question: how should objects acquire and share behavior? Inheritance establishes a class hierarchy where child classes automatically receive parent class methods and can override them. Composition builds objects from smaller, independent components that work together through delegation.

# Inheritance: Employee is-a Person
class Person
  def initialize(name)
    @name = name
  end
  
  def introduce
    "I'm #{@name}"
  end
end

class Employee < Person
  def initialize(name, employee_id)
    super(name)
    @employee_id = employee_id
  end
end

# Composition: Employee has-a Person
class Employee
  def initialize(person, employee_id)
    @person = person
    @employee_id = employee_id
  end
  
  def introduce
    @person.introduce
  end
end

The Gang of Four design patterns book popularized the principle "favor composition over inheritance," but this guideline requires context. Each approach offers distinct advantages and constraints that affect maintainability, flexibility, and code clarity.

Key Principles

Inheritance Hierarchy: Inheritance creates a taxonomic relationship where subclasses form specializations of parent classes. The subclass inherits all public and protected methods from the parent, gaining access to the parent's interface and implementation. Changes to parent classes propagate automatically to all descendants, creating tight coupling across the hierarchy.

Composition Structure: Composition assembles objects from component parts, each responsible for specific behavior. The containing object delegates work to its components through explicit method calls. Components remain independent and can be replaced or modified without affecting the containing object's interface.

Coupling Differences: Inheritance couples subclasses to parent implementation details. A child class depends on parent method signatures, return types, and even internal state in some cases. Composition creates loose coupling through interfaces—the containing object only depends on component method signatures, not their internal implementation.

Substitutability: Inheritance supports polymorphism through the Liskov Substitution Principle—any code accepting a parent class instance should work correctly with subclass instances. This enables runtime polymorphism where different subclasses respond differently to the same method calls.

class Animal
  def speak
    raise NotImplementedError
  end
end

class Dog < Animal
  def speak
    "Woof"
  end
end

class Cat < Animal
  def speak
    "Meow"
  end
end

def make_animal_speak(animal)
  puts animal.speak
end

make_animal_speak(Dog.new)  # => Woof
make_animal_speak(Cat.new)  # => Meow

Interface Contracts: Composition establishes contracts through interfaces or duck typing. Components must implement expected methods, but their internal structure remains hidden. Ruby's duck typing eliminates the need for explicit interface declarations—any object responding to the required methods satisfies the contract.

class Printer
  def initialize(formatter)
    @formatter = formatter
  end
  
  def print(data)
    puts @formatter.format(data)
  end
end

class JsonFormatter
  def format(data)
    data.to_json
  end
end

class CsvFormatter
  def format(data)
    data.join(',')
  end
end

# Both formatters work because they implement format()
printer = Printer.new(JsonFormatter.new)
printer.print({name: "Alice"})

Single vs Multiple Behavior Sources: Inheritance in Ruby supports single inheritance only—each class inherits from exactly one parent. Composition allows objects to delegate to multiple components, each providing different capabilities. This makes composition more flexible when combining behavior from multiple sources.

Reusability Models: Inheritance reuses code through class hierarchies where common behavior moves to parent classes. Composition reuses code through independent, composable components that can be mixed and matched across different contexts.

Design Considerations

When Inheritance Applies: Inheritance works best when modeling true taxonomic relationships where subclasses represent specialized versions of a general concept. If the relationship satisfies "is-a" semantics naturally and subclasses need to override or extend parent behavior in predictable ways, inheritance provides clear modeling and polymorphic behavior.

The Liskov Substitution Principle tests inheritance appropriateness: subclass instances must be usable anywhere the parent class is expected without breaking functionality. Violations indicate that inheritance forces an unnatural relationship.

# Good inheritance: Square is-a Shape
class Shape
  def area
    raise NotImplementedError
  end
end

class Square < Shape
  def initialize(side)
    @side = side
  end
  
  def area
    @side * @side
  end
end

# Problematic inheritance: Stack is-not-a Array
class Stack < Array
  # Inherits push, pop, but also [], []=, each, etc.
  # These break stack semantics
end

When Composition Applies: Composition excels when assembling objects from independent capabilities that don't form natural hierarchies. If different object types need to share some behavior but not others, composition allows selective inclusion of components without forcing unrelated classes into artificial inheritance relationships.

Composition handles behavior changes at runtime. Objects can swap components dynamically, changing their behavior without modifying class definitions. Inheritance locks behavior at compile time through the class hierarchy.

class TextEditor
  def initialize
    @spell_checker = nil
    @auto_saver = nil
  end
  
  def enable_spell_check(language)
    @spell_checker = SpellChecker.new(language)
  end
  
  def enable_auto_save(interval)
    @auto_saver = AutoSaver.new(interval)
  end
  
  def check_spelling(text)
    @spell_checker.check(text) if @spell_checker
  end
end

Multiple Inheritance Alternative: Ruby's single inheritance limitation often forces composition for combining multiple behaviors. Rather than attempting complex mixin chains, composition explicitly models each behavior source as a component. This creates clearer dependencies and avoids method resolution order confusion.

Deep Hierarchies Problem: Inheritance hierarchies deeper than 2-3 levels become difficult to understand and maintain. Each level adds coupling and makes it harder to predict method behavior. Composition flattens these structures by delegating to specialized components rather than chaining through parent classes.

Fragile Base Class Problem: Changes to parent classes risk breaking subclasses in unexpected ways. A parent method modification might affect dozens of subclasses, requiring careful testing of all descendants. Composition isolates changes to individual components—modifying one component affects only objects directly using it.

Testing Implications: Composition simplifies testing by allowing component isolation. Tests can mock or stub individual components without involving the entire object graph. Inheritance requires testing subclasses in the context of their parent hierarchy, increasing test complexity.

Ruby Implementation

Class Inheritance Syntax: Ruby implements inheritance through the < operator. Subclasses inherit all methods from their parent except private methods. The super keyword calls the parent implementation of overridden methods.

class Vehicle
  def initialize(make, model)
    @make = make
    @model = model
  end
  
  def description
    "#{@make} #{@model}"
  end
  
  def fuel_efficiency
    raise "Must be implemented by subclass"
  end
end

class ElectricVehicle < Vehicle
  def initialize(make, model, battery_capacity)
    super(make, model)
    @battery_capacity = battery_capacity
  end
  
  def fuel_efficiency
    "#{@battery_capacity} kWh capacity"
  end
  
  def description
    "#{super} (Electric)"
  end
end

ev = ElectricVehicle.new("Tesla", "Model 3", 75)
puts ev.description  # => Tesla Model 3 (Electric)

Module Mixins: Ruby modules provide a middle ground between inheritance and composition. Including a module copies its methods into the class, similar to inheritance. The method resolution order checks included modules before the parent class but after the class itself.

module Swimmable
  def swim
    "Swimming at #{swim_speed} mph"
  end
  
  def swim_speed
    10
  end
end

module Flyable
  def fly
    "Flying at #{fly_speed} mph"
  end
  
  def fly_speed
    50
  end
end

class Duck
  include Swimmable
  include Flyable
  
  def swim_speed
    5
  end
end

duck = Duck.new
puts duck.swim  # => Swimming at 5 mph
puts duck.fly   # => Flying at 50 mph

Delegation Pattern: Ruby's Forwardable module simplifies composition by automatically generating delegation methods. This reduces boilerplate when forwarding method calls to components.

require 'forwardable'

class Document
  extend Forwardable
  
  def initialize(content, formatter)
    @content = content
    @formatter = formatter
  end
  
  # Delegate format method to @formatter
  def_delegator :@formatter, :format
  
  # Delegate multiple methods to @content
  def_delegators :@content, :length, :empty?, :size
end

class MarkdownFormatter
  def format(text)
    "**#{text}**"
  end
end

doc = Document.new("Hello", MarkdownFormatter.new)
puts doc.format("World")  # => **World**
puts doc.length           # => 5

SimpleDelegator: Ruby's SimpleDelegator class wraps another object and forwards all method calls to it by default. This creates a transparent proxy that can selectively override methods while delegating the rest.

require 'delegate'

class TimestampedArray < SimpleDelegator
  def initialize(array)
    super(array)
    @created_at = Time.now
  end
  
  def push(item)
    puts "[#{Time.now}] Adding #{item}"
    super
  end
  
  def created_at
    @created_at
  end
end

arr = TimestampedArray.new([1, 2, 3])
arr.push(4)      # => [2025-10-06 ...] Adding 4
puts arr[0]      # => 1 (delegated to wrapped array)
puts arr.length  # => 4 (delegated to wrapped array)

Composition with Dependency Injection: Ruby's dynamic typing enables flexible composition through dependency injection. Objects receive components as constructor arguments or through setter methods, allowing component substitution without modifying the containing class.

class OrderProcessor
  def initialize(payment_gateway:, notification_service:, logger:)
    @payment_gateway = payment_gateway
    @notification_service = notification_service
    @logger = logger
  end
  
  def process(order)
    @logger.info("Processing order #{order.id}")
    
    if @payment_gateway.charge(order.amount)
      @notification_service.send_confirmation(order)
      @logger.info("Order #{order.id} completed")
      true
    else
      @logger.error("Payment failed for order #{order.id}")
      false
    end
  end
end

# Different components injected at runtime
processor = OrderProcessor.new(
  payment_gateway: StripeGateway.new,
  notification_service: EmailService.new,
  logger: Rails.logger
)

Prepend for Composition: The prepend method inserts a module before the class in the method resolution order. This allows modules to wrap class methods, creating a composition-like pattern where the module can call the original implementation through super.

module Cacheable
  def fetch(key)
    @cache ||= {}
    @cache[key] ||= super
  end
end

class Database
  def fetch(key)
    puts "Fetching #{key} from database"
    "value_#{key}"
  end
end

Database.prepend(Cacheable)
db = Database.new
puts db.fetch("user_1")  # => Fetching user_1 from database
puts db.fetch("user_1")  # => Returns cached value

Practical Examples

Example 1: Logger with Multiple Outputs: A logging system needs to write messages to different destinations. Inheritance would create a rigid hierarchy (FileLogger, ConsoleLogger, DatabaseLogger), making it impossible to log to multiple destinations simultaneously. Composition allows combining multiple output handlers.

class Logger
  def initialize
    @handlers = []
  end
  
  def add_handler(handler)
    @handlers << handler
  end
  
  def log(level, message)
    timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
    formatted = "[#{timestamp}] #{level.upcase}: #{message}"
    
    @handlers.each { |handler| handler.write(formatted) }
  end
  
  def info(message)
    log("info", message)
  end
  
  def error(message)
    log("error", message)
  end
end

class FileHandler
  def initialize(filename)
    @file = File.open(filename, 'a')
  end
  
  def write(message)
    @file.puts(message)
    @file.flush
  end
end

class ConsoleHandler
  def write(message)
    puts message
  end
end

class EmailHandler
  def initialize(email_address)
    @email = email_address
  end
  
  def write(message)
    # Only send emails for errors
    if message.include?("ERROR")
      # Send email logic here
      puts "Emailing #{@email}: #{message}"
    end
  end
end

logger = Logger.new
logger.add_handler(FileHandler.new("app.log"))
logger.add_handler(ConsoleHandler.new)
logger.add_handler(EmailHandler.new("admin@example.com"))

logger.info("Application started")
logger.error("Database connection failed")

Example 2: Payment Processing Hierarchy: An e-commerce system processes different payment types. Inheritance models the payment type hierarchy naturally, as all payment processors share common validation and recording logic but differ in processing implementation.

class PaymentProcessor
  def initialize(amount, customer)
    @amount = amount
    @customer = customer
    @processed_at = nil
  end
  
  def process
    return false unless validate
    
    result = execute_payment
    if result
      @processed_at = Time.now
      record_transaction
    end
    result
  end
  
  private
  
  def validate
    @amount > 0 && @customer.active?
  end
  
  def execute_payment
    raise "Must be implemented by subclass"
  end
  
  def record_transaction
    Transaction.create(
      customer_id: @customer.id,
      amount: @amount,
      processed_at: @processed_at,
      payment_type: self.class.name
    )
  end
end

class CreditCardProcessor < PaymentProcessor
  def initialize(amount, customer, card_number, cvv)
    super(amount, customer)
    @card_number = card_number
    @cvv = cvv
  end
  
  private
  
  def execute_payment
    # Charge credit card
    CreditCardGateway.charge(@card_number, @cvv, @amount)
  end
end

class PayPalProcessor < PaymentProcessor
  def initialize(amount, customer, paypal_email)
    super(amount, customer)
    @paypal_email = paypal_email
  end
  
  private
  
  def execute_payment
    # Process PayPal payment
    PayPalAPI.transfer(@paypal_email, @amount)
  end
end

class CryptoProcessor < PaymentProcessor
  def initialize(amount, customer, wallet_address, currency)
    super(amount, customer)
    @wallet_address = wallet_address
    @currency = currency
  end
  
  private
  
  def execute_payment
    # Process cryptocurrency payment
    CryptoAPI.send_payment(@wallet_address, @amount, @currency)
  end
end

processor = CreditCardProcessor.new(100, customer, "1234...", "123")
processor.process

Example 3: Game Entity System: A game needs entities with different combinations of abilities. A player might move and shoot, enemies might move and take damage, and decorative objects might only render. Inheritance forces choosing a single primary behavior, while composition allows combining any abilities.

class Position
  attr_accessor :x, :y
  
  def initialize(x, y)
    @x = x
    @y = y
  end
  
  def distance_to(other)
    Math.sqrt((@x - other.x)**2 + (@y - other.y)**2)
  end
end

class Movement
  def initialize(position, speed)
    @position = position
    @speed = speed
  end
  
  def move_to(x, y)
    direction_x = x - @position.x
    direction_y = y - @position.y
    distance = Math.sqrt(direction_x**2 + direction_y**2)
    
    if distance > 0
      @position.x += (direction_x / distance) * @speed
      @position.y += (direction_y / distance) * @speed
    end
  end
end

class Health
  attr_reader :current, :maximum
  
  def initialize(maximum)
    @maximum = maximum
    @current = maximum
  end
  
  def take_damage(amount)
    @current = [@current - amount, 0].max
  end
  
  def heal(amount)
    @current = [@current + amount, @maximum].min
  end
  
  def alive?
    @current > 0
  end
end

class Combat
  def initialize(damage)
    @damage = damage
  end
  
  def attack(target_health)
    target_health.take_damage(@damage)
  end
end

class Entity
  attr_reader :position
  
  def initialize(x, y)
    @position = Position.new(x, y)
    @movement = nil
    @health = nil
    @combat = nil
  end
  
  def add_movement(speed)
    @movement = Movement.new(@position, speed)
    self
  end
  
  def add_health(maximum)
    @health = Health.new(maximum)
    self
  end
  
  def add_combat(damage)
    @combat = Combat.new(damage)
    self
  end
  
  def move_to(x, y)
    @movement.move_to(x, y) if @movement
  end
  
  def take_damage(amount)
    @health.take_damage(amount) if @health
  end
  
  def attack(target)
    @combat.attack(target) if @combat && target.respond_to?(:take_damage)
  end
  
  def alive?
    @health ? @health.alive? : true
  end
end

# Create different entity types by composing abilities
player = Entity.new(0, 0)
  .add_movement(5)
  .add_health(100)
  .add_combat(25)

enemy = Entity.new(10, 10)
  .add_movement(3)
  .add_health(50)
  .add_combat(15)

decoration = Entity.new(5, 5)  # No abilities added

player.move_to(8, 8)
player.attack(enemy.instance_variable_get(:@health))
puts "Enemy health: #{enemy.instance_variable_get(:@health).current}"

Example 4: Report Generation with Strategies: A reporting system generates data in different formats. The report structure and data gathering use inheritance, while output formatting uses composition to allow runtime format selection.

class Report
  def initialize(data_source)
    @data_source = data_source
  end
  
  def generate
    data = gather_data
    calculate_metrics(data)
  end
  
  private
  
  def gather_data
    raise "Must be implemented by subclass"
  end
  
  def calculate_metrics(data)
    {
      total: data.sum,
      average: data.sum / data.length.to_f,
      count: data.length
    }
  end
end

class SalesReport < Report
  private
  
  def gather_data
    @data_source.sales_for_period(start_date, end_date)
  end
  
  def start_date
    Date.today - 30
  end
  
  def end_date
    Date.today
  end
end

class InventoryReport < Report
  private
  
  def gather_data
    @data_source.current_inventory_levels
  end
  
  def calculate_metrics(data)
    super.merge(
      low_stock: data.count { |level| level < 10 },
      out_of_stock: data.count { |level| level == 0 }
    )
  end
end

class ReportFormatter
  def initialize(report, formatter)
    @report = report
    @formatter = formatter
  end
  
  def output
    metrics = @report.generate
    @formatter.format(metrics)
  end
end

class JsonFormatter
  def format(metrics)
    require 'json'
    JSON.pretty_generate(metrics)
  end
end

class HtmlFormatter
  def format(metrics)
    html = "<table>\n"
    metrics.each do |key, value|
      html += "  <tr><td>#{key}</td><td>#{value}</td></tr>\n"
    end
    html += "</table>"
  end
end

class CsvFormatter
  def format(metrics)
    require 'csv'
    CSV.generate do |csv|
      csv << metrics.keys
      csv << metrics.values
    end
  end
end

# Inheritance for report types, composition for output format
sales = SalesReport.new(data_source)
inventory = InventoryReport.new(data_source)

# Generate same report in different formats
puts ReportFormatter.new(sales, JsonFormatter.new).output
puts ReportFormatter.new(sales, HtmlFormatter.new).output
puts ReportFormatter.new(inventory, CsvFormatter.new).output

Common Patterns

Strategy Pattern: The strategy pattern replaces conditional logic with composition by encapsulating algorithms in separate objects. The context object holds a reference to a strategy and delegates algorithm execution to it.

class DataCompressor
  def initialize(strategy)
    @strategy = strategy
  end
  
  def compress(data)
    @strategy.compress(data)
  end
  
  def strategy=(new_strategy)
    @strategy = new_strategy
  end
end

class ZipStrategy
  def compress(data)
    # Zip compression logic
    "zip(#{data})"
  end
end

class GzipStrategy
  def compress(data)
    # Gzip compression logic
    "gzip(#{data})"
  end
end

compressor = DataCompressor.new(ZipStrategy.new)
puts compressor.compress("hello")  # => zip(hello)

compressor.strategy = GzipStrategy.new
puts compressor.compress("hello")  # => gzip(hello)

Decorator Pattern: The decorator pattern adds behavior to objects dynamically through composition. Each decorator wraps an object and adds functionality while maintaining the same interface.

class Coffee
  def cost
    2.00
  end
  
  def description
    "Coffee"
  end
end

class MilkDecorator
  def initialize(coffee)
    @coffee = coffee
  end
  
  def cost
    @coffee.cost + 0.50
  end
  
  def description
    @coffee.description + ", Milk"
  end
end

class SugarDecorator
  def initialize(coffee)
    @coffee = coffee
  end
  
  def cost
    @coffee.cost + 0.25
  end
  
  def description
    @coffee.description + ", Sugar"
  end
end

# Build coffee with decorators
coffee = Coffee.new
coffee_with_milk = MilkDecorator.new(coffee)
coffee_with_milk_and_sugar = SugarDecorator.new(coffee_with_milk)

puts coffee_with_milk_and_sugar.description  # => Coffee, Milk, Sugar
puts coffee_with_milk_and_sugar.cost         # => 2.75

Template Method Pattern: The template method pattern uses inheritance to define an algorithm's structure in a base class while allowing subclasses to override specific steps.

class DataProcessor
  def process(file)
    data = read_file(file)
    parsed = parse_data(data)
    validated = validate_data(parsed)
    transform_data(validated)
  end
  
  private
  
  def read_file(file)
    File.read(file)
  end
  
  def parse_data(data)
    raise "Must be implemented by subclass"
  end
  
  def validate_data(parsed)
    # Default validation
    parsed.compact
  end
  
  def transform_data(validated)
    raise "Must be implemented by subclass"
  end
end

class CsvProcessor < DataProcessor
  private
  
  def parse_data(data)
    require 'csv'
    CSV.parse(data, headers: true).map(&:to_h)
  end
  
  def transform_data(validated)
    validated.map { |row| row.transform_keys(&:upcase) }
  end
end

class JsonProcessor < DataProcessor
  private
  
  def parse_data(data)
    require 'json'
    JSON.parse(data)
  end
  
  def validate_data(parsed)
    super
    parsed.select { |item| item['valid'] == true }
  end
  
  def transform_data(validated)
    validated.map { |item| item.slice('id', 'name', 'value') }
  end
end

Adapter Pattern: The adapter pattern uses composition to make incompatible interfaces work together by wrapping an object and translating method calls.

# Existing legacy system
class LegacyPrinter
  def print_document(doc, copies)
    copies.times { puts "Printing: #{doc}" }
  end
end

# New interface expected by application
class ModernPrinterAdapter
  def initialize(legacy_printer)
    @legacy_printer = legacy_printer
  end
  
  def print(document)
    @legacy_printer.print_document(document, 1)
  end
  
  def print_multiple(document, count)
    @legacy_printer.print_document(document, count)
  end
end

# Application code uses modern interface
def send_to_printer(printer, doc)
  printer.print(doc)
end

legacy = LegacyPrinter.new
adapter = ModernPrinterAdapter.new(legacy)
send_to_printer(adapter, "report.pdf")

Bridge Pattern: The bridge pattern separates abstraction from implementation using composition, allowing them to vary independently.

class RemoteControl
  def initialize(device)
    @device = device
  end
  
  def power_on
    @device.turn_on
  end
  
  def power_off
    @device.turn_off
  end
  
  def set_volume(level)
    @device.volume = level
  end
end

class AdvancedRemote < RemoteControl
  def mute
    @device.volume = 0
  end
  
  def max_volume
    @device.volume = 100
  end
end

class Television
  attr_accessor :volume
  
  def turn_on
    puts "TV is on"
  end
  
  def turn_off
    puts "TV is off"
  end
end

class Radio
  attr_accessor :volume
  
  def turn_on
    puts "Radio is on"
  end
  
  def turn_off
    puts "Radio is off"
  end
end

# Different remotes can control different devices
tv_remote = AdvancedRemote.new(Television.new)
tv_remote.power_on
tv_remote.mute

radio_remote = RemoteControl.new(Radio.new)
radio_remote.power_on
radio_remote.set_volume(50)

Common Pitfalls

Premature Composition: Converting all inheritance to composition creates unnecessary indirection and boilerplate. Simple inheritance hierarchies with clear "is-a" relationships often provide better readability than composition with multiple delegation layers.

# Unnecessary composition
class Dog
  def initialize(animal)
    @animal = animal
  end
  
  def speak
    @animal.speak
  end
end

class Animal
  def speak
    "Generic sound"
  end
end

# Better with inheritance for true is-a relationship
class Dog < Animal
  def speak
    "Woof"
  end
end

Over-Delegating with Forwardable: Forwarding every method from a component creates tight coupling without inheritance's benefits. The containing class becomes a thin wrapper that exposes the entire component interface, defeating composition's encapsulation purpose.

# Over-delegation exposes too much
class UserAccount
  extend Forwardable
  
  def initialize(user)
    @user = user
  end
  
  # Forwarding everything couples UserAccount to User's interface
  def_delegators :@user, :name, :email, :password_hash, :created_at,
                 :updated_at, :admin?, :banned?, :verified?
end

# Better: expose only necessary methods
class UserAccount
  def initialize(user)
    @user = user
  end
  
  def display_name
    @user.name
  end
  
  def contact_email
    @user.email
  end
  
  # Internal methods not exposed
end

Breaking Liskov Substitution: Subclasses that violate parent class contracts break polymorphism. Common violations include throwing exceptions where the parent succeeds, returning different types, or requiring stronger preconditions.

class Rectangle
  attr_accessor :width, :height
  
  def area
    width * height
  end
end

# Violates Liskov Substitution
class Square < Rectangle
  def width=(value)
    @width = @height = value
  end
  
  def height=(value)
    @width = @height = value
  end
end

def test_rectangle(rect)
  rect.width = 5
  rect.height = 10
  rect.area == 50  # Fails for Square
end

# Better: use composition
class Square
  attr_accessor :side
  
  def area
    side * side
  end
end

Mixin Confusion: Ruby's module inclusion inserts methods into the inheritance chain, causing confusion when multiple modules define the same method. The last included module wins, but this behavior is not obvious from the class definition.

module A
  def greet
    "Hello from A"
  end
end

module B
  def greet
    "Hello from B"
  end
end

class MyClass
  include A
  include B  # B's greet overrides A's greet
end

puts MyClass.new.greet  # => Hello from B

# Better: explicit composition makes behavior clear
class MyClass
  def initialize
    @greeter = B.new
  end
  
  def greet
    @greeter.greet
  end
end

Ignoring Inheritance When Appropriate: Forcing composition for clear taxonomic relationships adds complexity without benefit. Payment processors, geometric shapes, and exception classes naturally form hierarchies that inheritance models effectively.

# Forced composition creates unnecessary complexity
class PaymentProcessor
  def initialize(processor_type)
    @processor = case processor_type
      when :credit_card then CreditCardProcessor.new
      when :paypal then PayPalProcessor.new
      when :crypto then CryptoProcessor.new
    end
  end
  
  def process(amount)
    @processor.process(amount)
  end
end

# Better: use inheritance for natural hierarchy
class PaymentProcessor
  def process(amount)
    raise NotImplementedError
  end
end

class CreditCardProcessor < PaymentProcessor
  def process(amount)
    # Process credit card
  end
end

Component State Synchronization: Objects composed of multiple components must coordinate state changes across components. Failing to maintain consistency between components leads to invalid object states.

# Problematic: position and movement can become inconsistent
class Entity
  attr_reader :position, :movement
  
  def initialize
    @position = Position.new(0, 0)
    @movement = Movement.new(5)  # Movement has no reference to position
  end
  
  def teleport(x, y)
    @position.x = x
    @position.y = y
    # Movement component doesn't know position changed
  end
end

# Better: components share state or coordinate through entity
class Entity
  def initialize
    @position = Position.new(0, 0)
    @movement = Movement.new(@position, 5)  # Movement references position
  end
  
  def teleport(x, y)
    @position.x = x
    @position.y = y
    @movement.reset_path  # Coordinate state change
  end
end

Reference

Comparison Matrix

Aspect Inheritance Composition
Relationship Type is-a has-a
Coupling Tight coupling to parent Loose coupling through interfaces
Flexibility Fixed at class definition Can change at runtime
Code Reuse Vertical reuse through hierarchy Horizontal reuse through components
Ruby Support Single inheritance with super Direct delegation or Forwardable
Testing Must test with parent context Components tested independently
Complexity Simple hierarchies, complex at depth More objects, clearer dependencies
Polymorphism Built-in through class hierarchy Manual through duck typing
Method Resolution Automatic through inheritance chain Explicit delegation required

Decision Criteria

Use Inheritance When Use Composition When
True is-a relationship exists has-a or uses-a relationship exists
Shared behavior applies to all subclasses Behavior shared by unrelated classes
Liskov Substitution Principle satisfied Objects need runtime behavior changes
Hierarchy depth stays under 3 levels Combining multiple independent behaviors
Subclasses override specific methods Building objects from reusable parts
Polymorphic behavior needed Interface-based programming preferred
Template Method pattern applies Strategy or Decorator pattern applies
Natural taxonomy exists Mixing capabilities from multiple sources

Ruby Delegation Techniques

Technique Syntax Use Case
Manual delegation def method; @component.method; end Full control over delegation
Forwardable def_delegator :@component, :method Multiple specific methods
SimpleDelegator class MyClass < SimpleDelegator Transparent proxy with overrides
DelegateClass class MyClass < DelegateClass(Array) Delegate to specific class
Method missing def method_missing(name, *args) Dynamic delegation

Common Composition Patterns

Pattern Structure Ruby Implementation
Strategy Context delegates to strategy Object holds strategy reference
Decorator Wrapper adds behavior Nested objects with same interface
Adapter Wrapper translates interface Object wraps incompatible class
Bridge Abstraction separated from implementation Object references implementation
Composite Tree of objects with same interface Recursive component structure

Inheritance Patterns

Pattern Structure Ruby Implementation
Template Method Base class defines algorithm Abstract methods overridden by subclass
Factory Method Subclasses create objects Override factory method in subclass
Hook Methods Base class calls empty methods Subclasses override hooks as needed
Abstract Base Class Parent defines interface Parent raises NotImplementedError

Module Inclusion Order

Method Position in Chain Effect
include After class, before parent Module methods available, class overrides
prepend Before class Module wraps class methods with super
extend Adds to singleton class Module methods become class methods

Method Resolution Order

When Ruby resolves a method call, it searches in this order:

  1. Prepended modules (most recent first)
  2. The class itself
  3. Included modules (most recent first)
  4. Parent class (repeat from step 1)
  5. Object
  6. Kernel
  7. BasicObject

Refactoring Guidelines

From To Trigger
Inheritance Composition Deep hierarchy (3+ levels)
Inheritance Composition Multiple unrelated behaviors needed
Inheritance Composition Subclass uses only subset of parent
Composition Inheritance True is-a relationship discovered
Composition Inheritance Polymorphic substitution required
Manual delegation Forwardable 3+ delegated methods
Forwardable Manual Need to transform delegated data