After all the content we've created so far, it's time to introduce you to a concept that, at the same time, is the central paradigm of Ruby and other modern programming languages: object-oriented programming.
But as always, we have to have a good context before going deeper on the topic and also see what other kinds of programming paradigms are out there.
What is a programming paradigm?
I think we need to understand first what a paradigm is. Or at least to remember what it is. A paradigm is a distinct set of concepts or thought patterns in science and philosophy, including theories, research methods, postulates, and standards for legitimate contributions to a field. So, as you can see, we're talking about patterns or a way of thought here programming creators lie on to build software. So now that we refresh the paradigm term, let's check what a programming paradigm is.
According to Wikipedia: Programming paradigms are a way to
classify programming languages based on their features. Languages can be classified into multiple paradigms. For example, some paradigms are concerned mainly with implications for the language's execution model, such as allowing side effects or whether the execution model defines the sequence of operations. Other paradigms are concerned mainly with how code is organized, such as grouping a code into units and the state modified by the code. Yet others are primarily concerned with the style of syntax and grammar.
Standard programming paradigms include:
- * Imperative: in which the programmer instructs the machine how to change its state,
- * procedural, which groups instructions into procedures,
- * object-oriented, which groups instructions with the part of the state they operate on,
- * Declarative: in which the programmer merely declares properties of the desired result, but not how to compute it
- * functional in which the desired result is declared as the value of a series of function applications,
- * logic in which the desired result is declared as the answer to a question about a system of facts and rules,
- * mathematical in which the desired result is declared as the solution of an optimization problem
- * reactive in which the desired result is declared with data streams and the propagation of change
As you can see, the world of paradigms is extensive and sometimes a bit complex to understand. And almost always, the best way to understand each one of them is creating code, but maybe you'll need to use different programming languages, which is not the purpose of this blog post. For that reason, we're going to focus on object-oriented programming languages and then try to go deeper with examples in Ruby.
What Is Object-Oriented Programming?
Object-Oriented Programming (OOP) is a programming paradigm that relies on the concept of classes and objects. It structures a software program into simple, reusable code blueprints (usually called classes) used to create individual objects. Many object-oriented programming languages, including Ruby, JavaScript, C++, Java, and Python.
OOP is all about the mindset and less about programming power or simplicity (in most cases). The benefit is that it's one way of breaking complex problems into more manageable ones - and for many people, it's quick to grasp and provides a consistent method for structuring their code.
Think about how code evolves from simple problems to complex ones under the OOP line of thinking:
For elementary problems, you're essentially writing a script.
The next step is to find code that you (would) repeat in your script. That code is then generalized into functions.
If you look at extensive collections of functions, patterns start to emerge. Many perform work on some type of "thing." In an object-oriented world, that "thing" becomes an object, and all the functions working on it become methods. It's a great way to group and think about your functions. It's a simplistic OOP world at this point.
After that, patterns start emerging between different objects. You have many things with similar methods, though not quite the same. They're also used in very similar situations. What they do have in common are their abstract behaviors. At this point, you introduce interfaces (or abstract base classes) and subclassing to help define those behaviors consistently and ensure that they can be used interchangeably.
Even further! You start noticing patterns between essentially different classes. There may be certain portions of functionality that sets of classes have in common. You generalize those into protocols/mixins and use multiple inheritances (or similar methodologies) to use those generalized collections of methods/behavior in your classes.
A class is an abstract blueprint to create more specific, concrete objects. Classes often represent broad categories, like Car or Dog that share attributes. These classes define what attributes an instance of this type will have, like color, but not the value of those attributes for a specific object.
Classes can also contain functions, called methods, available only to objects of that type. These functions are defined within the Class and perform some action helpful to that specific object.
I know there is a lot of new jargon to understand. Later we'll be creating classes, objects, and methods in Ruby. But for now, it's necessary to have some theoretical knowledge about it.
What are the main reasons to use OOP?
You could ask what the advantages of using the OOP paradigm are. We'll here we'll be discussing some things to have in mind.
Faster development: Reuse enables speedier development. Object-oriented programming languages come with rich objects libraries, and code developed during projects is also reusable in future projects.
Higher-quality software: Faster software development and lower cost of the product allow more time and resources to be used to verify the software. Although quality depends upon the teams' experience, object-oriented programming results in higher-quality software.
Software-development productivity: Object-oriented programming is modular, as it provides separation of duties in object-based program development. It is also extensible, as objects can be extended to include new attributes and behaviors. Finally, objects can also be reused within and across applications. Because of these three factors, object-oriented programming provides improved software-development productivity over traditional procedure-based programming techniques.
Software maintainability: For the reasons mentioned above, object-oriented software is also easier to maintain. Since the design is modular, part of the system can be updated without making large-scale changes.
Lower cost of development: The reuse of software also lowers the cost of the product. Typically, more effort is put into object-oriented analysis and design, which reduces the overall cost of development.
However, we always have to see the other side of the coin, so what could be some cons of using OOP?
Larger program size: Object-oriented programs typically involve more lines of code than procedural programs.
Slower programs: Object-oriented programs are typically slower than procedure-based ones, as they usually require more execution instructions.
Steep learning curve: The thought process involved in object-oriented programming may not be natural for some people, and it can take time to get used to it. It is complex to create programs based on the interaction of objects. Some of the essential programming techniques, such as inheritance and polymorphism, can be challenging to comprehend initially.
Not suitable for all types of problems: Some problems lend themselves well to functional-programming style, logic-programming style, or procedure-based programming style, and applying object-oriented programming in those situations will not result in inefficient programs.
Creating our first Class and Objects
Before going deeper, we must understand the syntax to create classes and objects. I'll be explaining this from scratch so pay attention to these concepts. Then, start following me with your text editor.
The first thing to do is create a new file with the extension .rb
➜ touch oop.rb
Then open your text editor are we'll declare our first Class.
class Person
end
As you can see, we used a reserved word in Ruby called "class," and then we gave it a name. Is the name class, in this case, "Person." After that, we have a blank space to start creating the body of the Class, and finally, we have the "end" keyword to close the body.
If we execute this code in the console, it won't print anything. Let's try it.
So, we're ok, no errors, but also showing nothing.
We have a class declared, and now we're creating an object. To do that, let's write outside the Class next thing.
class Person
end
p1 = Person.new
puts p1
Here, things become interesting because we have a class and an object. Line number 5 is doing something called "instantiation." Instantiation is creating a new object and assigning it to a variable. To instantiate a class, we need to call the class name (Person) and then the method ".new". Right here is where the magic happens.
If we print out the result, we'll see the following output.
What is that weird output? It's the object allocation in memory. The object is stored by ruby in your memory in plain text. Do you remember our
blog post about memory? This is like the memory address to quickly access the object once we need it again.
As discussed in the advantages, the good thing about classes and objects is that we can reuse the code inside the Class and create different objects that are "born" from that Class. That means we can create multiple "instances" from just one Class.
class Person
end
p1 = Person.new
p2 = Person.new
p3 = Person.new
puts p1, p2, p3
If we print this code, we receive 3 different objects.
Finally, let's see the logic inside the Class. But, first, let's greet our users.
class Person
# behavior or logic to apply in each instance
def greet
"Hello world!"
end
end
p1 = Person.new.greet
p2 = Person.new
p3 = Person.new.greet
puts p1, p2, p3
Let's execute it
Could you see the difference?
The method is just returning a string. And when we're instantiating the first and third person, we call that method (immediately after the instantiation). So the second sentence doesn't reach the method. This is intentional to show you that the greeting method is invoked by those who call it. Pretty awesome!
Let's refactor the code to have it ready to do more different tasks.
#1. Class declaration
class Person
# 2. Behavior or logic to apply in each instance
def greet
"Hello world!"
end
end
#3. Instantiation
p1 = Person.new
p2 = Person.new
p3 = Person.new
#4. Printing out and calling a class method in some of them
puts p1.greet
puts p2
puts p3.greet
Let's execute it
Here the behavior and the output does not change. We've just refactored the code and left the instance variables like p1 or p2 alone with their own instance (Person.new). And below that, we printed out the instance with their method.
These are the first steps in the OOP world. We'll be doing more exercises and going a bit deeper on this. So stay tuned
I hope you learned a lot.
Thanks for reading
Daniel Morales