Inheritance

Inheritance is an important piece of OOP patterns. In simple terms, a class inheriting from another class means it'll get the parent classes methods, instance variables and it'll be able to add/overwrite existing ones.

Here's a basic example of inheritance:

class Animal
  property name : String
  property age : Int32

  def initialize(@name, @age)
  end
end

class Dog < Animal
  property domesticated = true
  property dexterity = 10
end

tex = Animal.new "Tex", 12
rex = Dog.new "Rex", 3

rex.domesticated = true
rex.dexterity = 8

# You can see below that the child class satisfies
# both the Parent and Child when is_a method is raised:

p tex.is_a? Animal # => true
p tex.is_a? Dog    # => false
p rex.is_a? Animal # => true
p rex.is_a? Dog    # => true

Inheritance Continued: Overriding

Overriding works very much the same way as it works with other OOP languages.

Basic example:

class Person
  property name : String

  def initialize(@name)
  end
end

class Employee < Person
  property salary = 0
end

tt = Employee.new "TT"

Reopening A Class

You might want to reopen a class for different reasons, Crystal allows this and the additions you make with the reopened class will be combined into a single class.

Have a look at the following example:

class Employee < Person
  def yearly_salary
    12 * @salary
  end
end

class SalesEmployee < Employee
  property bonus = 0

  def yearly_salary
    12 * @salary + @bonus
  end
end

john = Person.new "John"
jane = Employee.new "Jane"
rich = SalesEmployee.new "Rich"

jane.salary = 2000
p jane.yearly_salary # => 24000

rich.salary = 2000
rich.bonus = 1000
p rich.yearly_salary # => 25000

This example can be further enhanced by the usage of the super method where by calling super we get to use the super class functionality:

class HrEmployee < Employee
  property bonus = 0

  def yearly_salary
    super + @bonus
  end
end

herbert = HrEmployee.new "Herbert"
herbert.salary = 2000
herbert.bonus = 2000
p herbert.yearly_salary # => 26000

Abstract Classes

A great use for abstract classes are where you want to have a class but don't want to create objects based on the class. By defining an abstract class we can create a blueprint for subclasses.

Following example demonstrates inheriting from an abstract class but having different constructors:

abstract class Shape
end

# Circle inherits from Shape

class Circle < Shape
  def initialize(@radius : Float64)
  end
end

class Rectangle < Shape
  def initialize(@width : Float64, @height : Float64)
  end
end

crc = Circle.new(4)
rec = Rectangle.new(2, 4)

The following example is a more common pattern where we define an abstract method on the abstract class, thus making sure that the classes which implement from the abstract, implement the abstract methods. If they don't, you'll get a compilation error:

abstract class Shape
  abstract def area : Number
end

# Circle inherits from Shape

class Circle < Shape
  def initialize(@radius : Float64)
  end

  def area : Number
    MATH::PI * radius * 2
  end
end

crc = Circle.new(12)
p crc.area # => 323......

# The following class won't compile because it doesn't implement
# area method:

class Rectangle < Shape
  def initialize(@width : Float64, @height : Float64)
  end
end