Lesson 6
Applying OOP Principles in Ruby: Real-World Examples
Introduction and Lesson Goal

Today's mission involves using multiple Object-Oriented Programming (OOP) principles to tackle complex tasks. When principles like Encapsulation, Abstraction, Polymorphism, and Composition are blended, the resulting code becomes streamlined and easier to manage.

Our goal is to dissect two real-world examples, gaining insights into how these principles can seamlessly orchestrate solutions.

Real-life Example 1: Building an Online Library System

Let's design an online library system, as we aim to reinforce our understanding of Encapsulation and Polymorphism. Encapsulation will help us protect the attributes of books, members, and transactions, making sure they are accessible in a controlled manner. Polymorphism will demonstrate its power by enabling a single interface to represent different underlying forms, such as digital and print versions of books.

Ruby
1# Base class for different types of library users 2class Member 3 def initialize(name) 4 @name = name 5 end 6 7 def check_out_book(book) 8 puts "#{@name} checked out #{book.get_book_type} book #{book.title}." 9 end 10end 11 12# Base class for different types of books 13class Book 14 attr_reader :title 15 16 def initialize(title) 17 @title = title 18 end 19 20 def get_book_type 21 raise NotImplementedError, "Subclasses must implement the get_book_type method" 22 end 23end 24 25# Inherits from Book, represents a digital book 26class DigitalBook < Book 27 def get_book_type 28 "Digital" 29 end 30end 31 32# Inherits from Book, represents a physical book 33class PhysicalBook < Book 34 def get_book_type 35 "Physical" 36 end 37end 38 39# Library class that manages members and books 40class Library 41 def initialize 42 @members = [] 43 @books = [] 44 end 45 46 def add_member(member) 47 @members << member 48 end 49 50 def add_book(book) 51 @books << book 52 end 53end 54 55my_library = Library.new 56 57alice = Member.new("Alice") 58bob = Member.new("Bob") 59 60my_library.add_member(alice) 61my_library.add_member(bob) 62 63digital_book = DigitalBook.new("The Ruby Handbook") 64physical_book = PhysicalBook.new("Learning Ruby Design Patterns") 65 66my_library.add_book(digital_book) 67my_library.add_book(physical_book) 68 69alice.check_out_book(digital_book) # Prints: Alice checked out Digital book The Ruby Handbook. 70bob.check_out_book(physical_book) # Prints: Bob checked out Physical book Learning Ruby Design Patterns.

In this code snippet, Encapsulation is observed clearly through the class structures and the controlled access to their attributes. Polymorphism is vividly illustrated by how both DigitalBook and PhysicalBook classes inherit from the Book class but provide their own implementations of the get_book_type method. This setup allows objects of DigitalBook and PhysicalBook to be used interchangeably when a book's type needs to be identified, demonstrating Polymorphism's capability to work with objects of different classes through a common interface.

  • Encapsulation ensures that details about members and books are well-contained within their respective classes.
  • Polymorphism showcases flexibility by treating different book types uniformly, making the system more adaptive and scalable.
Real-life Example 2: Building a Shape Drawing Application

Next, we'll develop a shape drawing application capable of drawing various shapes. For this, we'll employ the principles of Abstraction and Composition.

  • Abstraction simplifies the complexity associated with drawing different shapes.
  • Composition takes care of composite shapes.

Here's how we translate these principles into our shape drawing application:

Ruby
1# Define the basic Shape class 2class Shape 3 # Abstract method that will be implemented in each subclass 4 def draw 5 raise NotImplementedError, "Subclasses must implement the draw method" 6 end 7end 8 9# Define the Circle class 10class Circle < Shape 11 # Implement the draw method for circle 12 def draw 13 puts "Drawing a circle." 14 end 15end 16 17# Define the Square class 18class Square < Shape 19 # Implement the draw method for square 20 def draw 21 puts "Drawing a square." 22 end 23end 24 25# Define the ShapeComposite class 26class ShapeComposite < Shape 27 def initialize 28 @shapes = [] 29 end 30 31 # Add a new shape to the composite 32 def add_shape(shape) 33 @shapes << shape 34 end 35 36 # Implement the draw method to draw each shape in composite 37 def draw 38 @shapes.each do |shape| 39 shape.draw 40 end 41 end 42end 43 44circle = Circle.new 45square = Square.new 46 47# Drawing individual shapes 48circle.draw # Output: Drawing a circle. 49square.draw # Output: Drawing a square. 50 51# Create a ShapeComposite instance for composite shapes 52composite_shape = ShapeComposite.new 53 54# Add individual shapes to the composite 55composite_shape.add_shape(circle) 56composite_shape.add_shape(square) 57 58# Drawing the composite shape 59composite_shape.draw 60# Output: 61# Drawing a circle. 62# Drawing a square.

This example unveils how Abstraction streamlines the process of drawing different shapes and Composition handles complex shapes.

Lesson Summary and Practice

Well done! You combined multiple OOP principles to respond to complex tasks. By dissecting real-world examples, we understood how these principles found their applications. Now, it's time to put this knowledge to work. Practice fortifies concepts, transforming knowledge into expertise. So, let's get coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.