TL;DR:A concise tour from OOP to OOD: objects, classes, the four pillars with Ruby examples, SOLID principles, and the 23 GoF design patterns.
8 minutes 57 seconds
For my first real article, I’ll write about Object-Oriented Programming, reviewing what Alexander Shvets1 defines, showing some practical usage in Ruby with references from Russ Olsen2. I’ll also transition from Object-oriented programming to object-oriented design, present the definition of SOLID as discussed by Sandi Metz3, and list the 23 design patterns cataloged by the GoF4, separating them into their purposes.
Core Building
First of all, we need to define what Object-oriented programming is:
As Alexander Shvets writes in his book, “Object-oriented programming is a paradigm based on the concept of wrapping a piece of data, and behavior related to that data, into special bundles called objects, which are constructed from a set of ‘blueprints’, defined by a programmer, called classes.”
But what does he mean by objects and classes, the main points of OOP?
Well, I’ll take an example with something I’m familiar with, rather than use animals or vehicles as usual. Take a football team. It has a name, it belongs to a city, has many titles and has a division. I root for Fluminense. It has all those attributes.
Fluminense is an object, Football Team is a class and all those data points are class fields. Other football teams will also share these class fields and also do the same things: play matches, compete in championships and hire or fire players. These are the class’ methods. Both fields and methods can be referenced as the members of their class.
So, for simple understanding, an object’s fields are referenced as state and an object’s methods define its behavior. A class is the blueprint that defines the structure for objects.
Here is an example of what is a class (Football Team) and what are instances of that class (Fluminense and Gama):
With that briefly explained, we can start talking about the pillars of OOP.
The Four Pillars of OOP
To differentiate itself, OOP is based on four pillars:
Abstraction
A model of a real-world object which represents all details relevant to this context with high accuracy and omits all the rest. For example, take a coffee machine. It has a lot of things going on, we need to boil water, brew the coffee beans and pour it in a cup, but all we need for this situation is the coffee for us to drink.
class CoffeeMachine # Public interface: The user only needs to press one button def make_coffee boil_water brew_coffee pour_in_cup puts "Your coffee is ready! ☕" end
# Abstraction: Complex internal steps are hidden from the user private
def boil_water puts "Boiling water..." end
def brew_coffee puts "Brewing coffee grounds..." end
def pour_in_cup puts "Pouring coffee..." endend
# Usagemachine = CoffeeMachine.newmachine.make_coffeeEncapsulation
The ability of an object to hide parts of its state and behaviors from other objects, exposing only a limited interface to the rest of the program. A common example used is when you use your key or a button to turn on your car. You don’t need to know what is happening inside, but you need to know that it’s working.
class BankAccount def initialize(owner, balance) @owner = owner @balance = balance # Hidden internal state end
# Public method to safely modify internal data def deposit(amount) if amount > 0 @balance += amount puts "Deposited $#{amount}. New balance: $#{@balance}." end end
# Public method to read internal data safely def display_balance puts "#{@owner}'s balance: $#{@balance}." end
private
# Private method hidden from the outside world def internal_audit # Hidden system logic goes here endend
# Usageaccount = BankAccount.new("Alice", 1000)account.deposit(500) # Works: Deposited $500. New balance: $1500.account.display_balance # Works: Alice's balance: $1500.Inheritance
The ability to build new classes on top of existing ones. This helps with code reuse, avoids duplicate code when you need to create a class, and lets you focus on only extending the existing class and putting extra information into the resulting subclass.
# Parent class (Superclass)class Animal def eat "Eating food..." endend
# Child class (Subclass) inherits from Animalclass Dog < Animal def bark "Woof!" end
# Overriding the parent's eat method def eat super + " specifically bones!" endend
# Usagemy_dog = Dog.new
puts my_dog.bark # Output: Woof!puts my_dog.eat # Output: Eating food... specifically bones!Polymorphism
The ability of a program to invoke a method on an object while the correct implementation is determined at runtime based on the object’s actual type, even when the declared type is a parent class or interface. It can also be the ability of an object to be treated as something else, usually a class it extends or an interface.
class Duck def make_sound puts "Quack!" endend
class Dog def make_sound puts "Woof!" endend
# This method does not care about the class type.# It only cares that the object can respond to 'make_sound'.def activate_sound(animal) animal.make_soundend
# Usageduck = Duck.newdog = Dog.new
activate_sound(duck) # Output: Quack!activate_sound(dog) # Output: Woof!With all that in mind, we can start migrating from OOP to OOD. For that, we will look forward to the SOLID Design Principles.
SOLID Design Principles
Single Responsibility (SRP): A class should have only one reason to change. It prevents the creation of massive “god classes”.
Open/Closed (OCP): Software entities should be open for extension but closed for modification.
Liskov’s Principle (LSP): Derived or child classes must be substitutable for their base or parent classes without breaking the functionality of the program.
Interface Segregation (ISP): No client should be forced to depend on methods it does not use.
Dependency Inversion (DIP): High-level modules should not depend on low-level modules.
We’ll explore these topics further in other posts, showing some code examples.
After that, we need to talk about patterns, which provide established solutions to many common design problems we’ll encounter in an Object-Oriented language.
Primary Design Patterns
According to the GoF, there are 23 design patterns separated into 3 purposes: Creational, Structural and Behavioral. Each purpose can have two scopes: class or object.
Creational Patterns
- Creational class patterns defer some part of object creation to subclasses.
- Creational object patterns defer it to another object.
| Scope | Creational Pattern |
|---|---|
| Class | Factory Method |
| Object | Abstract Factory |
| Builder | |
| Prototype | |
| Singleton |
Structural Patterns
- Structural class patterns use inheritance to compose classes
- Structural object patterns describe ways to assemble objects
| Scope | Pattern |
|---|---|
| Class | none |
| Object | Adapter |
| Bridge | |
| Composite | |
| Decorator | |
| Facade | |
| Proxy | |
| Flyweight |
Behavioral Patterns
- Behavioral class patterns use inheritance to describe algorithms and flow of control.
- Behavioral object patterns describe how a group of objects cooperate to perform a task no single object can carry out alone.
| Scope | Behavioral Pattern |
|---|---|
| Class | Interpreter |
| Template Method | |
| Object | Chain of Responsibility |
| Command | |
| Iterator | |
| Mediator | |
| Memento | |
| Observer | |
| State | |
| Strategy | |
| Visitor |
As we can see, it’s a lot to take in. For our next topic, we will examine SRP.
References
Footnotes
Shvets, A. (2018). Dive into design patterns. Refactoring.Guru. ↩
Olsen, R. (2007). Design patterns in Ruby. Addison-Wesley Professional. ↩
Metz, S. (2012). Practical object-oriented design in Ruby: An agile primer. Addison-Wesley Professional. ↩
Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1995). Design patterns: Elements of reusable object-oriented software. Addison-Wesley. ↩
Auth_Verified: 06.18.2026

