OOP and classes
Object-oriented programming (OOP) is one of the most widespread programming paradigms in the world today. In Ruby, OOP plays a central role, and knowing the basic principles of OOP is essential to understanding the language and its Ruby on Rails framework.
The basic principles of OOP include the idea that a program consists of objects that interact with each other, rather than procedures or functions that work independently of each other. This means that a program is viewed as a set of interacting objects, each of which has its own properties and methods.
In Ruby, everything is an object, which means that every element you create is an object. This includes numbers, strings, arrays, hashes, and other data types. Ruby doesn't have primitive data types like C++, Java, or Python.
In Ruby, objects have their own properties and methods, which are executed using the dot (.) operator, which points to the object's method. For example, if you have a string name = "John", you can use the length method to get the length of the string: name.length. Ruby also has many built-in methods that allow you to work with strings, numbers, arrays, and other data types.
In addition to built-in methods, you can create your own classes in Ruby, which are templates for creating objects with common properties and methods. Classes in Ruby are created using the class keyword and a class name, which usually starts with a capital letter. In a class, you can define properties using instance variables and methods using the def keyword.
For example, let's create a Person class that contains the name property and the introduce method:
In this example, we use the initialize method, which is called when a new class object is created. In the initialize method, we accept the name parameter, which we set as an instance variable of the @name. In the 'introduce' method, we use the @name instance variable to display a message about ourselves.
Now we can create a new object of the Person class and call its methods:
This code creates a new object of class Person named "John" and calls its introduce method. The result of executing this code will be the display of the message Hi, my name is John.
OOP allows you to create more complex programs that are easier to understand and modify. By using classes and objects, you can organize your code into logical blocks that interact with each other and reuse these blocks in different parts of your program.
In Ruby, OOP is a fundamental concept that you must understand to become a successful programmer. As the complexity of your code grows, knowing OOP becomes even more important, and Ruby provides powerful tools to implement these principles.
One of the key features of OOP in Ruby is inheritance. Inheritance allows you to create new classes that inherit properties and methods from the parent class. Inheritance in Ruby is implemented using the class keyword, followed by the name of the class you are inheriting, and the < keyword, followed by the name of the parent class.
For example, let's create a Student class that inherits the properties and methods of the Person class:
In this example, we use the class keyword followed by the name of the Student class and the < keyword followed by the name of the parent class Person. The Student class has a new learn method that is not in the Person class.
Now we can create a new object of the Student class and call its methods:
This code creates a new object of the Student class named "Jane" and calls its introduce and learn methods. The result of executing this code will be the display of the messages Hi, my name is Jane and I'm learning!
Ruby also has the ability to override the methods of a parent class in a child class. This is called polymorphism. For example, let's create a new Teacher class that inherits the introduce method from the Person class, but overrides it to add a teacher's title:
In this example, we create a new Teacher class that inherits the introduce method from the Person class, but overrides it by adding a teaching title. Now we can create a new object of the Teacher class and call its methods:
This code creates a new object of the Teacher class named "John" and calls its introduce method. The result of executing this code will be the output of the message Hello, my name is John and I'm a teacher.
Encapsulation is an important part of object-oriented programming because it allows you to hide the details of a class implementation from the outside world and provides access to the class only through a specific interface. In Ruby, encapsulation can be accomplished by using the level of access to objects and methods.
There are three levels of access to methods and variables in Ruby: public, protected, and private. The public level allows access to methods and variables from anywhere in the program, protected allows access from objects of the same class or its subclasses, and private allows access only from methods of the same class.
Let's take a look at an example that demonstrates encapsulation using the access level to methods and variables. Let's say we create a Person class that contains basic information about a person, such as first name, last name, and age. This class will have methods to change this information.
In this class, we use the attr_accessor method to create methods that allow us to get and change the values of the name, surname, and age variables. However, we may not be sure that users of our code will use these methods correctly. For example, we may not want to allow changing the first and last name after the object has been created.
In order to ensure that this data is encapsulated, we can use an access level for objects and methods. The access level determines which objects and methods can be used from outside the class.
Public is the default access level for all methods and variables in the class. This means that methods and variables can be used from outside the class. Let's look at the following example:
In this example, we use the attr_accessor method to define the name variable and create a getter and setter for it. We also define a greet method that outputs a string containing the person's name.
When we create a new Person object and call the greet method, we get a string containing the person's name. This is because the greet method is a public method, and we can call it from outside the class.
Private is an access level that allows you to restrict access to class methods and variables. Private methods and variables cannot be called or accessed from outside the class. They can only be used within the class itself. Let's look at the following example that shows the use of private methods and variables in a class:
In this example, we have defined two variables (@name and @age) and three methods (initialize, introduce, and birthday). The initialize method sets the values for the @name and @age variables. The introduce method uses these variables to display the message on the screen. The birthday method congratulates the user on their birthday and increments the @age variable by 1 using the private method increment_age.
In this example, we use the private keyword to make the increment_age method private. This means that the increment_age method cannot be called from outside the Person class. The example also shows that an attempt to call the private increment_age method from outside the class results in a NoMethodError error.
Let's return to our example with the Person class. Let's define a compare_age method that will compare the ages of two people, but will be available only within the class and its subclasses. To do this, we will use the protected access level.
Note the protected def age line, which means that the age method is available only within the class and its subclasses. By using this method in the compare_age method, we can compare the age of another person with our age.
In our example, the compare_age method can be called from any part of the program, since it has the public access level. However, the age method is protected, so it cannot be called from outside the class or its subclasses. It is used only within the class and its subclasses to compare the age. Thus, we provide protection against unauthorized access to a person's age.
Now that we understand how encapsulation works in Ruby and how to protect data and methods from unauthorized access, we can create more complex classes and objects with more variables and methods.
Let's take a look at an example of the User class that contains the following variables: first name, last name, email, password, and access level. We want the password and access level information to be available only within the class, and the first name, last name, and email information to be available from the outside, but only for reading, not changing. This is how we can do it with access levels:
Note that we used attr_reader on the first_name, last_name, and email variables to allow them to be read externally, but not modified. We used attr_accessor for the level variable to allow it to be changed externally.
We also created an authenticate method that takes a password and compares it to the user's stored password. This method can be called from outside the class.
We made the password method protected, because we want it to be available for calling within the class and its subclasses, but not from outside the class.
The level method determines the level of access to the class object. If we want the level of access to some objects to be limited, we can use the level method with the value "private" or "protected".
The "private" access level provides encapsulation, or hiding of data and methods, so that they cannot be called or accessed from outside the class. They can only be used within the class itself.
The "protected" access level provides access to data and methods for methods of the class and its subclasses.
Let's take a look at the following example:
In this example, we have created a Person class with the name and age variables, which are protected from access from outside the class, and the level method, which returns the level of access to the class object.
We also created two subclasses, Doctor and Patient, which inherit properties and methods from Person. The Doctor class has a compare_age_with_patient method that compares the patient's age to the doctor's age, and a compare_age method that compares the person's age to the age of another person.
The age method is protected, so it is available to the methods of the class and its subclasses, but not available from outside the class. For example, if we create a Student subclass and try to access the age method, it will be possible:
So, we can use the protected age method in the get_age method of the Student subclass.
However, trying to access the age method from outside the class will result in an error:
Thus, a protected access level allows you to provide access to class methods and variables only for its methods and subclasses, but does not allow access from outside the class.
In this way, the use of access levels allows you to provide encapsulation of data and methods in a class, which helps to preserve its integrity and protect against unauthorized access to private parts of the class. The use of public methods and variables allows third-party users of the class to use only those methods and variables that were intended for public use. In addition, the use of protected methods and variables allows children of the class to access these elements, but does not allow external users of the class to use them.
Telegram channel: https://t.me/ruby4you
Course author: Шкоропад Даниїл