CrackedRuby CrackedRuby

Overview

Inheritance establishes hierarchical relationships between classes where a subclass derives properties and behavior from a parent class. This mechanism enables code reuse by defining common functionality once in a base class and extending or specializing it in derived classes. Ruby implements single inheritance, meaning each class can inherit from exactly one parent class, with BasicObject serving as the root of the class hierarchy.

The inheritance model supports polymorphism by allowing subclasses to override inherited methods while maintaining the same interface. This creates substitutability where objects of derived classes can be used wherever the parent class is expected. Ruby extends the basic inheritance model through modules and mixins, which provide multiple inheritance-like capabilities without the complexity of true multiple inheritance.

class Vehicle
  def start_engine
    "Engine starting"
  end
end

class Car < Vehicle
  def start_engine
    "#{super} with ignition key"
  end
end

car = Car.new
car.start_engine
# => "Engine starting with ignition key"

Inheritance creates an "is-a" relationship between classes. A Car is a Vehicle, meaning it possesses all characteristics of vehicles plus additional specialized behavior. This relationship differs from composition ("has-a"), where objects contain instances of other classes rather than inheriting from them.

Key Principles

Class Hierarchy: Inheritance organizes classes into tree structures with BasicObject at the root. Each class except BasicObject has exactly one superclass, accessed through the superclass method. The hierarchy defines method lookup paths and determines which implementation executes when multiple definitions exist.

class Animal; end
class Mammal < Animal; end
class Dog < Mammal; end

Dog.superclass        # => Mammal
Mammal.superclass     # => Animal
Animal.superclass     # => Object
Object.superclass     # => BasicObject
BasicObject.superclass # => nil

Method Resolution Order: When a method is called, Ruby searches for implementations starting at the receiver's class and moving up the hierarchy. The search stops at the first matching method name. This order determines which version of an overridden method executes. Ruby provides the ancestors method to inspect the complete lookup chain including mixed-in modules.

class Base
  def greet
    "Hello from Base"
  end
end

class Derived < Base
  def greet
    "Hello from Derived"
  end
end

obj = Derived.new
obj.greet  # => "Hello from Derived"
Derived.ancestors  # => [Derived, Base, Object, Kernel, BasicObject]

Method Overriding: Subclasses redefine inherited methods to provide specialized implementations. The overridden method completely replaces the parent's version in the method lookup chain. Ruby does not enforce method signatures, so overridden methods can accept different parameters, though this breaks substitutability.

super Keyword: The super keyword invokes the parent class's implementation of the current method. Without arguments, super passes all arguments received by the current method to the parent. With empty parentheses super(), it calls the parent method with no arguments. With explicit arguments super(arg1, arg2), it passes those specific values.

class Logger
  def log(message)
    "[#{Time.now}] #{message}"
  end
end

class FileLogger < Logger
  def log(message)
    entry = super
    File.write("log.txt", entry, mode: "a")
    entry
  end
end

Abstract Methods and Classes: Ruby lacks native abstract method syntax but implements the concept through methods that raise NotImplementedError. This creates template methods that subclasses must override. While Ruby cannot prevent instantiation of "abstract" classes, raising errors in incomplete methods enforces the pattern at runtime.

class AbstractProcessor
  def process(data)
    raise NotImplementedError, "Subclasses must implement process"
  end
end

class JsonProcessor < AbstractProcessor
  def process(data)
    JSON.parse(data)
  end
end

Protected and Private Inheritance: Unlike some languages, Ruby does not support protected or private inheritance at the class level. All inheritance is public, meaning subclasses fully inherit the parent's public interface. However, subclasses inherit private and protected methods, which remain callable internally but maintain their visibility restrictions.

Single Inheritance Constraint: Ruby's single inheritance model prevents diamond problems where a class inherits from two classes that share a common ancestor. This constraint simplifies method resolution but limits expressing complex relationships. Modules and mixins provide an alternative for sharing behavior across unrelated classes.

Ruby Implementation

Basic Inheritance Syntax: Ruby uses the < operator to declare inheritance. The subclass appears on the left, the superclass on the right. All classes implicitly inherit from Object unless explicitly inheriting from another class or BasicObject.

class Employee
  attr_reader :name, :id
  
  def initialize(name, id)
    @name = name
    @id = id
  end
  
  def details
    "Employee: #{name} (ID: #{id})"
  end
end

class Manager < Employee
  attr_reader :department
  
  def initialize(name, id, department)
    super(name, id)
    @department = department
  end
  
  def details
    "#{super} - Department: #{department}"
  end
end

manager = Manager.new("Alice", 101, "Engineering")
manager.details
# => "Employee: Alice (ID: 101) - Department: Engineering"

Constructor Chaining: Subclass constructors typically call super to execute parent initialization logic before adding subclass-specific setup. Forgetting super means parent instance variables remain uninitialized, causing bugs when inherited methods access them.

class BankAccount
  def initialize(balance)
    @balance = balance
  end
  
  def balance
    @balance
  end
end

class SavingsAccount < BankAccount
  def initialize(balance, interest_rate)
    super(balance)  # Must call parent constructor
    @interest_rate = interest_rate
  end
  
  def apply_interest
    @balance += @balance * @interest_rate
  end
end

Method Visibility Inheritance: Subclasses inherit private and protected methods but cannot change their visibility. A private method in the parent remains private in the child, callable only through other methods in the hierarchy, never directly on an instance.

class Secret
  def public_method
    private_method
  end
  
  private
  
  def private_method
    "Secret information"
  end
end

class Derived < Secret
  def another_public_method
    private_method  # Can call inherited private method
  end
end

obj = Derived.new
obj.another_public_method  # => "Secret information"
obj.private_method  # NoMethodError: private method called

Modules and Mixins: Ruby addresses single inheritance limitations through modules. Classes include modules via include (instance methods) or extend (class methods), inserting module methods into the ancestor chain. Multiple modules can be mixed into one class, providing multiple inheritance-like capabilities.

module Flyable
  def fly
    "Flying through the air"
  end
end

module Swimmable
  def swim
    "Swimming in water"
  end
end

class Duck < Animal
  include Flyable
  include Swimmable
end

duck = Duck.new
duck.fly   # => "Flying through the air"
duck.swim  # => "Swimming in water"
Duck.ancestors  # => [Duck, Swimmable, Flyable, Animal, ...]

Class Variables and Inheritance: Class variables (@@variable) are shared across the entire hierarchy, not independent per class. Modifying a class variable in a subclass affects the parent and all other subclasses, creating unexpected coupling. Instance class variables (@variable defined at class level) provide class-specific storage without sharing.

class Counter
  @@count = 0
  
  def self.increment
    @@count += 1
  end
  
  def self.count
    @@count
  end
end

class SpecialCounter < Counter
end

Counter.increment
SpecialCounter.increment
Counter.count  # => 2 (shared, not 1)

Overriding Built-in Methods: Ruby classes can inherit from built-in classes like Array, Hash, or String and override their methods. This requires careful attention to method chaining since built-in methods often return new instances of the class.

class SortedArray < Array
  def initialize(*args)
    super
    sort!
  end
  
  def <<(element)
    super
    sort!
    self
  end
end

arr = SortedArray.new(3, 1, 4, 1, 5)
# => [1, 1, 3, 4, 5]
arr << 2
# => [1, 1, 2, 3, 4, 5]

Method Aliasing in Subclasses: The alias keyword creates method name copies before overriding, allowing subclasses to preserve and call the original implementation under a different name.

class OriginalClass
  def compute
    42
  end
end

class ExtendedClass < OriginalClass
  alias original_compute compute
  
  def compute
    original_compute * 2
  end
end

obj = ExtendedClass.new
obj.compute            # => 84
obj.original_compute   # => 42

Common Patterns

Template Method Pattern: Parent classes define algorithmic structure with hook methods that subclasses override to customize specific steps. This inverts control, with the parent orchestrating the algorithm while subclasses provide details.

class Report
  def generate
    collect_data
    format_data
    write_output
  end
  
  def collect_data
    raise NotImplementedError
  end
  
  def format_data
    raise NotImplementedError
  end
  
  def write_output
    raise NotImplementedError
  end
end

class SalesReport < Report
  def collect_data
    @data = Database.query("SELECT * FROM sales")
  end
  
  def format_data
    @formatted = @data.map { |row| "Sale: $#{row[:amount]}" }
  end
  
  def write_output
    File.write("sales.txt", @formatted.join("\n"))
  end
end

Abstract Base Class Pattern: Classes that define interfaces without complete implementations serve as base classes for concrete implementations. Ruby implements this through methods that raise errors, ensuring subclasses provide required functionality.

class DataSource
  def fetch
    raise NotImplementedError, "#{self.class} must implement fetch"
  end
  
  def transform(data)
    data  # Default implementation that subclasses can override
  end
end

class ApiSource < DataSource
  def initialize(url)
    @url = url
  end
  
  def fetch
    HTTP.get(@url).body
  end
  
  def transform(data)
    JSON.parse(data)
  end
end

class DatabaseSource < DataSource
  def fetch
    Database.execute("SELECT * FROM records")
  end
end

Mixin Hierarchy Pattern: Modules organized in hierarchies provide reusable behavior without the constraints of single inheritance. Classes include relevant modules to compose functionality from multiple sources.

module Persistable
  def save
    Database.save(self)
  end
end

module Timestampable
  def created_at
    @created_at ||= Time.now
  end
end

module Validatable
  def valid?
    validate.empty?
  end
  
  def validate
    []  # Subclasses override to return errors
  end
end

class User
  include Persistable
  include Timestampable
  include Validatable
  
  def validate
    errors = []
    errors << "Name required" if @name.nil?
    errors << "Email required" if @email.nil?
    errors
  end
end

Decorator Pattern Through Inheritance: Subclasses wrap and extend parent functionality while maintaining the same interface. Each decorator adds one responsibility, allowing flexible combination through chained inheritance or composition.

class Coffee
  def cost
    2.0
  end
  
  def description
    "Coffee"
  end
end

class MilkCoffee < Coffee
  def cost
    super + 0.5
  end
  
  def description
    "#{super} with milk"
  end
end

class SugarMilkCoffee < MilkCoffee
  def cost
    super + 0.3
  end
  
  def description
    "#{super} and sugar"
  end
end

coffee = SugarMilkCoffee.new
coffee.cost         # => 2.8
coffee.description  # => "Coffee with milk and sugar"

Strategy Pattern with Inheritance: Abstract base classes define strategy interfaces while concrete subclasses implement different algorithms. The context class accepts any strategy subclass through polymorphism.

class SortStrategy
  def sort(array)
    raise NotImplementedError
  end
end

class QuickSort < SortStrategy
  def sort(array)
    return array if array.length <= 1
    pivot = array[0]
    less = array[1..].select { |x| x <= pivot }
    greater = array[1..].select { |x| x > pivot }
    sort(less) + [pivot] + sort(greater)
  end
end

class MergeSort < SortStrategy
  def sort(array)
    return array if array.length <= 1
    mid = array.length / 2
    merge(sort(array[0...mid]), sort(array[mid..]))
  end
  
  private
  
  def merge(left, right)
    result = []
    until left.empty? || right.empty?
      result << (left.first <= right.first ? left.shift : right.shift)
    end
    result + left + right
  end
end

class Sorter
  def initialize(strategy)
    @strategy = strategy
  end
  
  def sort(array)
    @strategy.sort(array)
  end
end

Practical Examples

Domain Model Hierarchy: Business domains naturally express as class hierarchies. An e-commerce system models different product types sharing common attributes while adding specialized properties.

class Product
  attr_reader :name, :price, :sku
  
  def initialize(name, price, sku)
    @name = name
    @price = price
    @sku = sku
  end
  
  def display_info
    "#{name} - $#{price}"
  end
  
  def tax_rate
    0.08  # Base tax rate
  end
  
  def final_price
    price * (1 + tax_rate)
  end
end

class Book < Product
  attr_reader :author, :isbn
  
  def initialize(name, price, sku, author, isbn)
    super(name, price, sku)
    @author = author
    @isbn = isbn
  end
  
  def display_info
    "#{super} by #{author}"
  end
  
  def tax_rate
    0.0  # Books are tax-exempt in some regions
  end
end

class Electronics < Product
  attr_reader :warranty_months
  
  def initialize(name, price, sku, warranty_months)
    super(name, price, sku)
    @warranty_months = warranty_months
  end
  
  def display_info
    "#{super} (#{warranty_months}-month warranty)"
  end
  
  def tax_rate
    0.10  # Higher tax rate for electronics
  end
end

book = Book.new("Ruby Guide", 39.99, "BOOK001", "Smith", "978-0-1234")
book.display_info   # => "Ruby Guide - $39.99 by Smith"
book.final_price    # => 39.99 (no tax)

laptop = Electronics.new("Laptop Pro", 1299.99, "ELEC001", 24)
laptop.display_info  # => "Laptop Pro - $1299.99 (24-month warranty)"
laptop.final_price   # => 1429.99 (with 10% tax)

Authentication System: Different authentication strategies inherit from a common base, allowing the application to switch authentication methods without changing client code.

class Authenticator
  def initialize(credentials)
    @credentials = credentials
  end
  
  def authenticate
    raise NotImplementedError
  end
  
  def authorized?(resource)
    false  # Override in subclasses
  end
end

class PasswordAuthenticator < Authenticator
  def authenticate
    user = User.find_by(username: @credentials[:username])
    return nil unless user
    
    if user.password_hash == hash_password(@credentials[:password])
      Session.new(user)
    else
      nil
    end
  end
  
  def authorized?(resource)
    @credentials[:user].permissions.include?(resource)
  end
  
  private
  
  def hash_password(password)
    Digest::SHA256.hexdigest(password)
  end
end

class TokenAuthenticator < Authenticator
  def authenticate
    token = Token.find_by(value: @credentials[:token])
    return nil unless token && token.valid?
    
    Session.new(token.user)
  end
  
  def authorized?(resource)
    token = Token.find_by(value: @credentials[:token])
    token && token.scopes.include?(resource)
  end
end

class OAuthAuthenticator < Authenticator
  def authenticate
    response = OAuth.validate(@credentials[:access_token])
    return nil unless response.valid?
    
    user = User.find_or_create_from_oauth(response.user_info)
    Session.new(user)
  end
  
  def authorized?(resource)
    OAuth.check_scope(@credentials[:access_token], resource)
  end
end

# Usage
auth = PasswordAuthenticator.new(username: "alice", password: "secret")
session = auth.authenticate
if session && auth.authorized?(:admin_panel)
  # Grant access
end

File Processing Pipeline: Different file processors inherit common parsing and validation logic while implementing format-specific handling.

class FileProcessor
  def initialize(filepath)
    @filepath = filepath
  end
  
  def process
    validate_file
    data = read_file
    parsed = parse_data(data)
    transform(parsed)
  end
  
  def validate_file
    raise "File not found" unless File.exist?(@filepath)
    raise "Invalid extension" unless valid_extension?
  end
  
  def read_file
    File.read(@filepath)
  end
  
  def parse_data(data)
    raise NotImplementedError
  end
  
  def transform(parsed_data)
    parsed_data  # Default: no transformation
  end
  
  def valid_extension?
    raise NotImplementedError
  end
end

class CsvProcessor < FileProcessor
  def valid_extension?
    @filepath.end_with?('.csv')
  end
  
  def parse_data(data)
    require 'csv'
    CSV.parse(data, headers: true).map(&:to_h)
  end
  
  def transform(parsed_data)
    parsed_data.map { |row| row.transform_keys(&:downcase) }
  end
end

class JsonProcessor < FileProcessor
  def valid_extension?
    @filepath.end_with?('.json')
  end
  
  def parse_data(data)
    require 'json'
    JSON.parse(data)
  end
  
  def transform(parsed_data)
    return [parsed_data] unless parsed_data.is_a?(Array)
    parsed_data
  end
end

class XmlProcessor < FileProcessor
  def valid_extension?
    @filepath.end_with?('.xml')
  end
  
  def parse_data(data)
    require 'nokogiri'
    doc = Nokogiri::XML(data)
    doc.xpath('//record').map { |node| parse_node(node) }
  end
  
  private
  
  def parse_node(node)
    node.children.each_with_object({}) do |child, hash|
      hash[child.name] = child.text if child.element?
    end
  end
end

# Usage
processor = CsvProcessor.new('data.csv')
records = processor.process
# => [{name: "Alice", age: "30"}, {name: "Bob", age: "25"}]

Design Considerations

Inheritance vs Composition: Inheritance creates tight coupling between parent and child classes. Changes to the parent propagate to all subclasses, sometimes unexpectedly. Composition ("has-a" relationships) offers more flexibility by assembling objects from independent components. Prefer composition when relationships are temporary, optional, or need runtime changes. Use inheritance when subclasses truly specialize the parent's behavior and the "is-a" relationship is fundamental to the domain.

# Inheritance - tight coupling
class Vehicle
  def start
    "Starting engine"
  end
end

class ElectricVehicle < Vehicle
  def start
    "Starting electric motor"  # Must override
  end
end

# Composition - loose coupling
class Engine
  def start
    "Starting engine"
  end
end

class ElectricMotor
  def start
    "Starting electric motor"
  end
end

class Vehicle
  def initialize(power_source)
    @power_source = power_source
  end
  
  def start
    @power_source.start
  end
end

car = Vehicle.new(Engine.new)
tesla = Vehicle.new(ElectricMotor.new)

Depth of Hierarchy: Deep inheritance hierarchies (more than 3-4 levels) become difficult to understand and maintain. Each level adds complexity to method resolution and increases the chance of fragile base class problems. Consider flattening hierarchies by moving shared behavior into modules or extracting components into composed objects.

Liskov Substitution Principle: Subclasses must be substitutable for parent classes without breaking program correctness. This means maintaining the parent's contract: preconditions cannot be strengthened, postconditions cannot be weakened, and invariants must be preserved. Violations create subtle bugs when polymorphic code assumes parent behavior.

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

class Square < Rectangle
  def width=(value)
    @width = @height = value
  end
  
  def height=(value)
    @width = @height = value
  end
end

# Violation: Square violates Rectangle's contract
def test_rectangle(rect)
  rect.width = 5
  rect.height = 10
  rect.area  # Expected 50, but Square returns 100
end

Interface Segregation: Large parent classes with many methods force subclasses to inherit functionality they do not need. This violates the interface segregation principle. Split large base classes into smaller, focused classes or use modules to provide optional functionality that classes can include selectively.

Open/Closed Principle: Classes should be open for extension (through inheritance) but closed for modification. Well-designed base classes anticipate extension points without requiring modifications. Template methods, hook methods, and protected helper methods enable subclasses to customize behavior without changing parent code.

class EmailSender
  def send(recipient, subject, body)
    message = build_message(recipient, subject, body)
    validate_message(message)
    deliver(message)
    log_delivery(message)
  end
  
  protected
  
  def build_message(recipient, subject, body)
    { to: recipient, subject: subject, body: body }
  end
  
  def validate_message(message)
    raise "Invalid recipient" if message[:to].nil?
  end
  
  def deliver(message)
    # Actual delivery logic
  end
  
  def log_delivery(message)
    # Default logging
  end
end

class PriorityEmailSender < EmailSender
  protected
  
  def build_message(recipient, subject, body)
    super.merge(priority: 'high')
  end
  
  def log_delivery(message)
    Logger.priority_mail(message)
  end
end

When to Use Inheritance: Choose inheritance when subclasses represent specialized versions of the parent concept, share significant implementation code, and form a stable hierarchy unlikely to change. Inheritance works well for frameworks where the base class defines lifecycle hooks and subclasses implement application-specific logic.

When to Avoid Inheritance: Avoid inheritance for code reuse alone when classes are not conceptually related. Avoid when flexibility to change behavior at runtime is needed. Avoid when the relationship is optional or temporary. Consider modules for sharing behavior across unrelated classes, or composition for building complex objects from simpler parts.

Common Pitfalls

Fragile Base Class Problem: Changes to parent classes ripple through all subclasses, potentially breaking functionality. This fragility increases with hierarchy depth and number of subclasses. Subclasses that depend on internal parent implementation details rather than public interfaces are most vulnerable.

class Logger
  def log(message)
    write(format_message(message))
  end
  
  private
  
  def format_message(message)
    "[LOG] #{message}"
  end
  
  def write(formatted)
    puts formatted
  end
end

class FileLogger < Logger
  private
  
  def write(formatted)
    File.write("log.txt", formatted, mode: "a")
  end
end

# Problem: If Logger changes log() to call write() twice,
# FileLogger writes duplicate entries without intending to

Inappropriate Inheritance: Inheriting for code reuse when no "is-a" relationship exists creates confusing, brittle designs. The classic example is making Stack inherit from Array because it reuses array operations, even though a stack is not a specialized array and needs to hide most array methods.

# Bad: Stack is not an Array
class Stack < Array
  # Inherits push, pop, but also exposes [], insert, etc.
  # which violate stack semantics
end

# Good: Stack uses an Array
class Stack
  def initialize
    @items = []
  end
  
  def push(item)
    @items.push(item)
  end
  
  def pop
    @items.pop
  end
  
  def peek
    @items.last
  end
end

Forgotten super Calls: Overridden methods that forget to call super lose parent functionality. This commonly occurs in constructors when subclasses fail to initialize parent instance variables, causing nil reference errors when inherited methods run.

class Account
  def initialize(balance)
    @balance = balance
  end
  
  def balance
    @balance
  end
end

class CheckingAccount < Account
  def initialize(balance, overdraft_limit)
    # Missing super(balance) call
    @overdraft_limit = overdraft_limit
  end
end

account = CheckingAccount.new(1000, 500)
account.balance  # => nil, expected 1000

Class Variable Sharing: Class variables are shared across the entire inheritance hierarchy, not per class. Modifying class variables in one class affects the parent and all siblings, creating unexpected state coupling.

class Config
  @@setting = "default"
  
  def self.setting
    @@setting
  end
  
  def self.setting=(value)
    @@setting = value
  end
end

class ProductionConfig < Config
end

class DevelopmentConfig < Config
end

DevelopmentConfig.setting = "development"
ProductionConfig.setting  # => "development", not "default"

Deep Inheritance Hierarchies: Hierarchies exceeding 3-4 levels become difficult to understand and maintain. Developers must examine multiple classes to understand behavior. Deep hierarchies often indicate missing abstractions or misuse of inheritance for code organization rather than conceptual relationships.

Breaking Parent Contracts: Subclasses that violate parent method contracts cause bugs when code polymorphically uses instances without checking actual types. Strengthening preconditions (accepting fewer inputs), weakening postconditions (returning more types), or changing method semantics all break substitutability.

class DataValidator
  def validate(data)
    data.all? { |item| item.is_a?(String) }
  end
end

class EmailValidator < DataValidator
  def validate(data)
    # Contract violation: requires different input type
    data.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
  end
end

# Code expecting DataValidator breaks with EmailValidator
def process(validator, items)
  if validator.validate(items)  # Expects array, but EmailValidator needs string
    # ...
  end
end

Overuse of Protected and Private: Making too many methods protected or private in parent classes limits subclass customization. Template method pattern requires protected hooks that subclasses can override. Balance encapsulation against extensibility by identifying genuine extension points.

Mixin Ordering Issues: When including multiple modules, method resolution follows the order of inclusion. Later included modules take precedence, potentially shadowing earlier ones. Understanding ancestor chains becomes critical when debugging method resolution.

module A
  def greet
    "Hello from A"
  end
end

module B
  def greet
    "Hello from B"
  end
end

class Example
  include A
  include B  # B's greet shadows A's greet
end

Example.new.greet  # => "Hello from B"
Example.ancestors  # => [Example, B, A, Object, ...]

Reference

Inheritance Syntax

Syntax Purpose Example
class Child < Parent Declare inheritance class Manager < Employee
super Call parent method with all args super
super() Call parent method with no args super()
super(args) Call parent method with specific args super(name, id)
obj.class.superclass Get parent class Manager.superclass => Employee
obj.class.ancestors Get method resolution order Manager.ancestors
obj.is_a?(Class) Check inheritance hierarchy manager.is_a?(Employee)
obj.instance_of?(Class) Check exact class manager.instance_of?(Manager)
obj.kind_of?(Class) Alias for is_a? manager.kind_of?(Employee)

Method Visibility in Inheritance

Visibility Inherited Callable in Subclass Overridable
public Yes Yes Yes
protected Yes Yes, from instances of same hierarchy Yes
private Yes Yes, from methods in hierarchy Yes

Module Inclusion Methods

Method Effect Method Type Ancestor Position
include Add module to instance Instance methods After class, before superclass
prepend Add module before instance Instance methods Before class
extend Add module to class singleton Class methods In singleton ancestor chain

Common Method Resolution Examples

Call Resolution Order Result
obj.method Class -> Included Modules -> Superclass First matching method
super Next in ancestor chain after current Parent or module implementation
super() Same as super but no args passed Parent with empty arg list

Inheritance vs Composition Decision Matrix

Factor Favor Inheritance Favor Composition
Relationship Is-a relationship Has-a or uses-a relationship
Coupling Tight coupling acceptable Loose coupling preferred
Flexibility Relationship fixed at compile time Behavior changes at runtime
Reuse Inheriting entire interface Reusing specific functionality
Testing Test with parent fixtures Test components independently
Hierarchy Stable, well-understood Complex, frequently changing

Template Method Pattern Structure

Component Role Implementation
Abstract Class Define algorithm skeleton Public method calling protected hooks
Hook Methods Extension points Protected methods raising NotImplementedError
Concrete Class Implement hooks Override protected methods
Default Implementations Optional behavior Protected methods with default logic

Common Inheritance Anti-patterns

Anti-pattern Problem Solution
Yo-yo Problem Deep hierarchy requires navigating up and down Flatten hierarchy, use composition
Refused Bequest Subclass does not use inherited functionality Use composition instead
Base Class Depends on Derived Parent knows about subclasses Invert dependency, use polymorphism
Inheritance for Reuse No is-a relationship Extract shared code to module or service
Empty Override Override just to do nothing Redesign parent interface

Ruby Inheritance Hierarchy Roots

Root Class Purpose Subclasses
BasicObject Minimal object interface Object
Object Standard object functionality Most Ruby classes
Module Class and module functionality Class
Class Class-specific functionality User-defined classes

Protected vs Private Inheritance Behavior

Scenario Protected Private
Call from subclass method Allowed Allowed
Call via explicit receiver in subclass Allowed if receiver is same hierarchy Not allowed
Call from outside hierarchy Not allowed Not allowed
Visibility in subclass Remains protected Remains private