In Ruby, we can use either class inheritance or modules to provide shared functionality throughout a program. This post explores the difference between class inheritance and modules, and considerations to help determine the appropriate use of each.
Inheritance between classes in Ruby allows for the creation of classes that have access to shared methods while still maintaining distinct, unique classes. When one class, known as the child or subclass, is inherited from another class, known as the parent or “super” class, it is given access to all of the methods of the parent. For example, we might have a parent class “Dog”, with a child class of “Dalmatian”. In the code below, we define these classes without the use of inheritance.
class Dog def bark "Woof!" end def sleep "zzzzz" end def find_that_smell "sniff sniff sniff" end end class Dalmatian def bark "Woof!" end def sleep "zzzzz" end def find_that_smell "sniff sniff sniff" end end
The acronym DRY stands for "don't repeat yourself", and it is an important principle in software design. In the code below, using class inheritance, we can get the same functionality as above, but we have the benefit of keeping our code DRY. Our Dalmatian class now has access to all of the behavior of the Dog class.
class Dog def bark "Woof!" end def sleep "zzzzz" end def find_that_smell "sniff sniff sniff" end end class Dalmatian < Dog end
The use of inheritance allows us to share methods between classes in a way that reflects the real-world hierarchical relationship between our classes, and provides shared functionality accordingly. If a dog can bark, a Dalmatian can bark.
An important note about class inheritance when considering use is that, although you can inherit from a class that inherits from another class that inherits from yet another class- a single class can only inherit from one class at a time when it is defined.
Let's imagine we wanted to define an instance method that returns true if the animal is able to be housebroken. We could define this as an instance method within our dog class and have our individual dog breeds inherit the method. But what if our program had other animal classes, such as cats and rabbits, that we wanted to provide this method to as well?
class Animal end class Dog < Animal def can_be_housebroken? true end end class Cat < Animal def can_be_housebroken? true end end class Rabbit < Animal def can_be_housebroken? true end end
As you can see above, we would have to define those methods over and over again. Sometimes, as in this case, we might want to share functionality between classes that do not have a clear hierarchical arrangement. Instead of making a new class that is built from the blueprint of an existing class, like when we inherit, we may simply want to group methods together and make those methods available to a variety of other classes. Modules allow us to do just that.
Let’s use a module instead of class inheritance. First, we will define our module. Then, within the definition of our classes, we can use the keyword include along with the module name in any number of classes in which we would like to provide access to the functions we defined in our module.
module IndoorPet def can_be_housebroken? true end end class Animal end class Dog < Animal include IndoorPet end class Cat < Animal include IndoorPet end class Rabbit < Animal include IndoorPet end
As you can see, the use of Modules provides a great solution for extending functionality and avoiding repetitive code in situations where there is no clear parent-child relationship. Once it is defined, a module can be included in many classes. There is also no limit to how many modules you can include in an individual class. One thing to keep in mind about modules when considering use is that, unlike with classes, you cannot have an instance of a module.
Class inheritance and modules both allow us to extend functionality throughout our program while reducing repetitive code. When trying to figure out when to use modules vs class inheritance, some considerations include whether or not you need to create instances, whether or not there is a hierarchical relationship between classes, and how the extended functionality is related to the relationships between groups. If you are trying to build a class from the framework of an already defined class, you can use class inheritance to adopt the behaviors of a parent class. If you've created a group of methods that you’d like to use repeatedly throughout your program in many different classes that are not hierarchically related, you can use modules to provide your classes access to the methods defined within your modules.
In our next post, we dig deeper into modules in ruby, exploring the
prepend keywords. You can read that post here.