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 |