Sharing behaviour with modules
After 131 reasons, somehow we haven't yet covered Ruby's Module and its role in sharing behaviour between classes. I guess it's about time!
module Discountable
def discount(percent)
price * (1 - percent / 100.0)
end
end
class Tea
include Discountable
attr_reader :name, :price
def initialize(name, price)
@name = name
@price = price
end
end
class Vinyl
include Discountable
attr_reader :artist, :price
def initialize(artist, price)
@artist = artist
@price = price
end
end
Tea.new("Chai Latte", 5.50).discount(10) # => 4.95
Vinyl.new("The Dark Side of the Moon", 32.00).discount(25) # => 24.0
As you can see in this simple example, modules allow us to share behaviour between classes without needing to use inheritance.
Some Rubyists would argue that modules should always be preferred over inheritance, since it's inherently more flexible. I don't know if I would go that far, but they're certainly an essential part of what makes Ruby so great at code reuse and composition.
History
Modules have been part of Ruby since its very first public release in 1995. Matz designed them deliberately as a cleaner alternative to multiple inheritance, which he considered error-prone and hard to reason about.
The concept of mixins, pluggable units of shared behaviour, was coined in the Flavors dialect of Lisp in the late 1970s, and a likely influence on Ruby's modules.
Other modern languages have landed on similar ideas independently. E.g. Rust's traits and Kotlin's interfaces with default method implementations are similar in nature.