Object-Oriented Design
Last updated
Last updated
In the introduction of this book I told that we'll inevitably talk about Object-Oriented Programming (OOP) and Software Design. The combination of these concepts can be called Object-Oriented Design.
In this chapter we'll see a really short description of these concepts. And apply them in the context of the Ruby language.
Note that the code shown in this chapter is just an illustration of how Ruby code looks like. Don't worry if you're not familiar with that, more detailed explanations come through the book.
My favorite definition comes from :
Every application is a collection of code; the code's arrangement is the design.
Although the way we organize our code can influence how computers execute it, the main target audience is the humans who need to maintain this code over time.
We organize our code into functions, classes, methods, modules, etc., so we can have a better understanding of our software. With this organization we hope to be able to, effortlessly, change our application whenever necessary - usually implementing new features or fixing bugs.
When we organize our code thinking in objects, we're doing Object-Oriented Design. But what is this OO thing?
First let's remember how procedural programming works.
In procedural languages there's a clear distinction between data and behavior. We put the data into variables and pass them to functions (the behavior). Although we commonly use both data and behavior to solve the same sort of problem, there's a conceptual distance between them. Such distance sometimes brings confusion and unpredictable problems. Object-Oriented Programming is an attempt to prevent such problems.
A simplistic way to describe OOP is to say that it's a way of grouping both data and behavior into the same concept. An object holds the data needed to solve a problem and also the logic that performs operations on that data in order to solve the problem.
Below we'll see the main OOP concepts with a Rubyist perspective.
In OOP we usually model concepts from the real world in our code. During this process we usually think in categories of things that we want to represent in code. For example, we want to model the concept of "something that says Hello, World
", then we need to think in an entity that could encapsulate this "something". Let's say, a Greeter class.
In order to represent the entities, we create a class. In a class we can define the data we need (e.g. the greeter's default language) and the behavior that use that data (e.g. to greet people properly).
Here's an example of Ruby code implementing a class named Greeter and its behavior of returning the Hello, World!
string.
A class is like a blueprint of the concept you're modeling. And to make use of such concept we usually create instances of the classes, also known as objects.
In order to create an object from a class, we use a constructor method.
In Ruby we create objects by calling the new
method, which is known as the constructor method.
One interesting thing is that when we call the new
method, Ruby actually executes the initialize
method. If you want to initialize your new object with some data, then you need to implement an initialize
method for your class.
As an example let's create the initialize
for our Greeter class, where we set the language to be used in the greetings:
Here are some examples of how it would be called to create greeter objects with different languages:
Both objects in the previous example were created from the same class, but have unique characteristics. We passed different arguments to the constructor method, and the contents of those arguments were stored in an instance variable named @language
.
Instance variables have this name because they are variables with values that are unique to each instance. In our example we use an instance variable to store the greeter's default language.
When we create classes we can also define instance methods, representing the behavior of the object. They hold functionalities that may be used to solve something. For example, we could have an instance method named hello
to greet someone using the greeter's default language (or using a specified one). For example:
Creating a hello method able to greet people in different languages is what we are going to do in the next chapter.
In Ruby we invoke methods by sending a message to an object. In the previous example we sent the hello
message, along with the name Johann
, to the german_greeter
object.
If you are coming from another OOP language, you're probably used to use the term "calling a method" instead of "sending a message", but in Ruby literature we use the message sending terminology.
When an object receives a message, it looks into its own class for a corresponding method. If found, the method is invoked, if not, an exception is raised (but that is a topic for a later chapter).
In this chapter we learned that Software Design is basically the way we organize our code.
We also saw that OOP is a nice way to design our software by grouping together data and behavior under the same concept.
We briefly saw concepts like:
classes
constructor method
objects
instance variables
instance methods
Of course there are much more to say about these topics, but with what we have so far we're able to go ahead and start coding. We will learn more as we progress through the book.