One of the biggest challenges of object oriented programming in Ruby is defining the interface of your objects. In other languages, such as Java, there is an explicit way to define an interface that you must conform to. But in Ruby, it’s up to you.
Compounding with this difficulty is the problem of deciding which object should own a method that you want to write. Trying to choose between modules, class methods, instance methods, structs, and lambdas can be overwhelming.
In this post, we’ll look at several ways to solve the Exercism Leap Year problem, exploring different levels of method visiblitiy and scope level along the way.
If you sign up for Exercism.io and start solving Ruby problems, one of the first problems you will look at is called Leap Year. The tests guide you to write a solution that has an interface that works like this:
Year.leap?(1984) #=> true
A leap year is defined as a year that is divisible by four, unless it’s also divisible by 100. Apparently, centuries aren’t leap years. That is, unless they are centuries that are divisible by 400. So there are three rules for leap years:
- If it’s divisible by 400, it’s an exceptional century and is a leap year.
- If it’s divisible by 100, it’s a mundane century and in not a leap year.
- Otherwise, if it’s divisible by 4, it’s a leap year, otherwise it’s not.
The First Approach: Using Class Methods
Here’s one simple solution to this problem. You can rearrange the booleans in a couple of different ways and it will still work. This is the version I came up with:
class Year def self.leap?(year) year % 4 == 0 && !(year % 100 == 0) || year % 400 == 0 end end
This is nice and compact, but not entirely easy to understand. I’m a pretty big fan of self-documenting code, so I used the extract method refactoring pattern to name these three rules.
class Year def self.leap?(year) mundane_leap?(year) || exceptional_century?(year) end def self.mundane_leap?(year) year % 4 == 0 && !century? end def self.century?(year) year % 100 == 0 end def self.exceptional_century?(year) year % 400 == 0 end end
Class Methods and Privacy
This is a lot more understandable, in my mind. But there’s a problem with this.
exceptional_century? are all publicly exposed methods. They really only exist in support of
leap?, and I’m not sure how reusable they are, with the possible exception of
century?. If I write this test, it will pass:
def test_exceptional_century assert Year.exceptional_century?(2400) end
I would like to make
exceptional_century? private, so that it can’t be accessed outside of the
Year class. I can try something like this:
class Year ... private def self.mundane_leap?(year) year % 4 == 0 && !century? end def self.century?(year) year % 100 == 0 end def self.exceptional_century?(year) year % 400 == 0 end end
But, unfortunately, this won’t work. The test still passes, becuase the
private keyword doesn’t work on class methods. Instead, I would have to use
private_class_method after my method definition.
private_class_method :mundane_leap?, :century?, :exceptional_century?
Now if I run that last test, it will raise an error.
All About the Eigenclass
In my mind, what we’ve now done is somewhat better, but there’s still a smell here. I’ll get to exactly what that is in just a moment, but for now I’ll say that it’s due to the fact that we’ve defined all of these methods on the singleton class, or eigenclass, of
Year. If you don’t know about eigenclasses, you can read about them here.
We can define methods on an eigenclass by prepending each method definition with
self., or we can use
class << self or
class << Year and nesting our method definitions inside of that block. Doing it this way makes it possible to use the
private keyword, because we’re now working at the instance level of scope. If we were to introduce
class << self, then, we could do away with our
class Year class << self def leap?(year) mundane_leap?(year) || exceptional_century?(year) end private def mundane_leap?(year) year % 4 == 0 && !century?(year) end def century?(year) year % 100 == 0 end def exceptional_century?(year) year % 400 == 0 end end end
But we haven’t really changed much here. In my mind, there’s still a big smell, which is this. According to Wikipedia, a class is (emphasis mine) ” an extensible program-code-template for creating objects “. Our
Year class never creates a single instance. It’s just an eigenclass that happens to be able to create (mostly) useless
So where should we be putting class-level methods that are not associated with any instance object?
Second Approach: Module Functions
In Ruby, the
Class object inherits from
Module. A module is basically a collection of methods, constants, and classes. Their primary feature is that they can be mixed into other modules and classes to extend their functionality. They’re also frequently used for namespacing (for example, the
ActiveRecord constant is a module).
We can put our
Year implementation into a module without changing much: just swap the word
module. Instead of using
class << self as we did for the class version, we can use
extend self in our module, and get the same effect, allowing us to use the
extend takes the methods defined in a module and makes them into class-level methods on the target module (or class). This is in contrast with
include, which mixes them in as instance-level methods. Thus, if we extend the module into itself, it gets all of it’s own methods at the class level.
module Year extend self def leap?(year) mundane_leap?(year) || exceptional_century?(year) end private def mundane_leap?(year) year % 4 == 0 && !century?(year) end def century?(year) year % 100 == 0 end def exceptional_century?(year) year % 400 == 0 end end
If we wanted to define some other methods in a
Year class that had nothing to do with leap years, we could change the name of our module to
Leap and mix it into a class called
If we want to make the three leap year rule methods private, we now have another choice of how to do it. We can use
module_function will make these methods available to the module, but when they get mixed into the class, they will be private. Module functions allow you to be selective with what can be called by the module itself, while still defining methods that can be mixed into other modules and classes.
module Leap def leap?(year) mundane_leap?(year) || exceptional_century?(year) end def mundane_leap?(year) year % 4 == 0 && !century?(year) end def century?(year) year % 100 == 0 end def exceptional_century?(year) year % 400 == 0 end module_function :mundane_leap?, :century?, :exceptional_century? end class Year; extend Leap; end
Now, if we run the tests, they will all still pass, with the exception of the one we wrote that tries to call
Third Approach: Instance Methods
I want to stay with the idea that we might want a
Year class that can do other things unrelated to determining whether we are talking about leap year or not. Really, there are a bunch of different years, but they all have certain things in common (which era they fall in, whether they are leap, etc.). In my mind, this would be a good use case for instances instead of class level methods.
What would it look like if we brought the responsibility for knowing whether a year is leap back into a
Year class, but passing that behavior onto instances of year, instead of putting it on the eigenclass?
It’s a little weird to be able to do something like
Year.new(2015).year, so I’ll name the state we’re going to store
class Year def self.leap?(year) self.new(year).leap? end attr_reader :reckoning def initialize(reckoning) @reckoning = reckoning end def leap? mundane_leap? || exceptional_century? end private def mundane_leap? reckoning % 4 == 0 && !century? end def century? reckoning % 100 == 0 end def exceptional_century? reckoning % 400 == 0 end end
The instance-based approach has certain advantages. We don’t have to use anything exotic like
class << self, or
module_function, to make our methods private. This might make things easier to understand for future maintainers of our code.
We also gained access to the
reckoning, that could be used to calculate other properites in the future. For example, if we wanted to write
to_hebrew methods, we’d already have the number we’d need to use to calculate that available to us.
Finally, we can now use a
protected section as well. Methods in this section will only be visible to other instances of
Year. We might use it to do something like this:
class Year ... protected def ==(other) self.recoking == other.reckoning end ... end
We’ve looked at several different ways to write a program that can tell us whether a given year is a leap year. Each has its advantages and disadvantages.
We started with a class method that was a single-liner. There’s a lot of value in the compactness of
year % 4 == 0 && !(year % 100 == 0) || year % 400 == 0. But it’s also fairly hard to understand. If we decide to split some of that logic into separate methods, now we’re faced with the problem of how to keep these new methods out of the interface of
We used an eigenclass-based approach, simply hiding the implementation methods with
private_class_method. We solved it using a module, treating the
leap? method both as a mix-in and as a
module_function. Finally, we pushed the functionality down to the instance level, which could help us in dealing with multiple
Year objects down the road.
Which of these approaches is best?
It depends on lots of things: how you feel about privacy, how you expect the program to change in the future, how comfortable you are with scope and the more arcane methods in Ruby. At the very least, it’s nice to know what’s available to you when thinking about what methods you want to expose, and what level of scope you should use it on. I hope this post will factor into your thoughts when making these kind of decisions in the future.
P.S. What do you think? Did this make sense? Should I have used Structs or Lambdas? Throw us a comment below!