.NET to Ruby: Classes
In the previous post of this series .NET to Ruby: The Ruby Environment, we went through the fundamental tools that made up Ruby. If you recall we talked about running Ruby on the command line, using IRB, we wrote our first Hello World program, and figured out what was going on below the covers.
In this post we’ll look to compare a fundamental object oriented feature: Classes. Yup that’s it. This post started as classes, methods, and variables, but it grew too big – there’s a lot to cover when discussing classes! So let’s dive in.
Classes
Classes exist within Ruby just like .NET, and their definition stays the same. In both
Ruby and .NET classes are used to encapsulate (or group) pieces of related data and methods into a succinct object
In both cases we can instantiate a class and get an instance of it. Ruby and .NET use the new keyword to create object instances, but the syntax differs slightly:
// In C#
var baz = new FooBar();
# In Ruby
baz = FooBar.new
To actually define a class, in C# we’d do that like this:
public class FooBar {
// Class implementation
}
And in Ruby a class looks like this:
class FooBar
# Class implementation
end
Really simple and straight forward, the difference to notice is that Ruby’s class definition is missing scope. And class scope is one area where Ruby differs significantly from .NET, because Ruby doesn’t allow for private, protected, or internal classes. All classes within Ruby are public.
Why Are Ruby’s Classes all Public?
Ruby has public classes because fundamentally all of Ruby’s classes are Open Classes. What this means is that I can break open the String class (or any other) and add functionality to it. Here’s an example:
class String
def remove_vowels
self.gsub(/[aeiou]/, '')
end
end
puts "this is a sentence".remove_vowels #=> ths s sntnc
Now before anyone jumps on Ruby for allowing such an atrocity, let’s point out that .NET allows for the same functionality in the form of Extension Methods. Let’s write the same code in .NET:
static class StringExtensions
{
public static string ReplaceVowels(this string value)
{
return new Regex("[aeiou]").Replace(value, string.Empty);
}
}
The only place where Ruby’s Open Classes, and .NET’s Extension functionality differ is that with Ruby you can open a class and define class methods, whereas with .NET you cannot define a class method extension.
Since this series on transitioning from .NET to Ruby isn’t about starting flame wars, I’ll leave it to you to determine if Open Classes, and Extensions are worth the class encapsulation violation that they introduce (put your opinion in the comments if you’re especially brave.)
For those of you that want to look at examples of well applied open classes, meander over to GitHub, and have a look at Rails core_ext.
Inheritence
Inheritence in Ruby is just like .NET. With it we can define a superclass, and then inherit from the superclass to create subclasses. The subclasses inherit all the data and methods of the superclass as we’d expect. In C# we’d use inheritence like this:
public class Mammal {
// Methods & data
}
public class Human : Mammal {
}
In Ruby inheritence looks like this:
class Mammal
end
class Dog < Mammal
end
With Ruby the less than symbol < is used to denote the inheritence.
Interfaces
Let’s talk interfaces. Interfaces are another feature that Ruby doesn’t have compared to .NET, and this is because Ruby is duck typed. The basic definition of duck typing is:
If it walks like a duck, swims like a duck, and talks like a duck, then it must be a duck.
Now if you have no idea what duck typing is, that just made things worse. At it’s core duck typing allows you to call a method on any object instance regardless of type, and if that instance responds to the method the instance will execute it. Let’s look at an example:
class Duck
def talk
puts "quack"
end
end
class Dog
def talk
puts "woof"
end
end
duck = Duck.new
duck.talk #=> quack
dog = Dog.new
dog.talk #=> woof
In this example we’re using a Duck instance, and a Dog instance and calling the talk
method on the instance. Since both instances will respond to talk
this program will execute without throwing an error. But what if an instance doesn’t have a method that’s called on it?
class Truck
end
truck = Truck.new
truck.talk
#=> NoMethodError: undefined method `talk' for #<Truck:0x1011d1620>
As can be seen from the example, a NoMethodError
is raised when talk
is called on truck
. This is duck typing: Regardless of the underlying type, if an instance can respond to a method it will, if it can’t it will error. So why does duck typing allow Ruby to forgo interfaces? Because Ruby doesn’t care about types.
In .NET interfaces are used so that we can pass object instances of different types into a method and the compiler will check whether that instance type conforms to the interface. This allows us to define our expectations for an instance without saying how those expectations ought to be met. But because Ruby is duck typed, every instance can be passed into that method – you run the risk of getting a NoMethodError
, but that is part of the risk/reward trade off when working with a dynamic language.
To summarize, because Ruby is a dynamic language that doesn’t deal with type safety checking, and because it is duck typed, Ruby doesn’t define a formal, code based way to declare an interface.
Now don’t get me wrong, interfaces are an important concept and they’ve been invented for a reason. Ruby takes a different approach in that interfaces are informally defined in the form of documentation. Rack is one example of code that formally defines an interface (they call it a protocol) that any code using Rack must conform to.
You see Ruby, and Ruby programmers like the “Don’t Repeat Yourself” principle. So why would you write an interface in code, and then write it into your documentation as well? That’s duplicated effort. Instead write really good documentation for your code, explain your interfaces and their rational, and leave it to the consumer of your code to implement the interface.
With all that said, if you really wanted to, you could define a Ruby interface within code. It’s not something that most Rubyists would do, since it goes against Ruby conventions but you can do it.
The final point to make on Duck typing is that it has a lot of benefit to it. We’ll look at two of those benefits later when we touch on writing mocks for unit test, and when doing meta-programming.
Abstract Classes
Moving onto Abstract classes, let’s outline a basic definition:
A class that is marked Abstract dictates that a superclass cannot be instantiated and that it must instead be instantiated as a subclass
The implications of this definition are that the functionality declared in the superclass is passed on to the subclass. This definition is important because Ruby doesn’t have a concept of abstract classes like .NET does.
Instead Ruby implements something called Mixins. Mixins are a lot like abstract classes in that they can be used to define methods and data that can be added into a class. They can’t be instantiated directly, but instead are included into an existing class. Here’s a brief example:
module Cheerful
def greeting
puts "It's nice to meet you!"
end
end
class Man
include Cheerful
end
bill = Man.new
bill.greeting #=> It's nice to meet you!
For this code we declare a module
called Cheerful
. Modules are also used for namespacing which I’ll elaborate on in another post. We then use the method include
to mixin the Cheerful
module with the Man
class. By mixing Cheerful
into Man
we’re able to call any of the methods, or access any of the data defined within Cheerful
.
Now this is a really basic example, but the power in Mixins comes from our ability to mixin any number of modules that we want to. Effectively mixins allow you to write common code once – like code for enumerating over collections – and have it applied across a broad number of classes (if you read the documentation for Enumerable, there’s an interface definition there too!)
So while Ruby doesn’t use Abstract classes, it instead provides Mixins which can be used to achieve the same end as Abstract classes.
And with that this post on classes is complete. There was a lot of ground covered in this post. We touched on Class implementation, Open Classes, Inheritence, Interfaces, Duck Typing, Abstract Classes, and Mixins. In the next post on Transitioning from .NET to Ruby, we’ll look into how Methods and Variables work within Ruby.
As always comments, and discussion are welcome and appreciated!