Εισαγωγή στον αντικειμενοστρεφή προγραμματισμό με το Ruby

Ως φοιτητής πληροφορικής, περνάω πολύ χρόνο μαθαίνοντας και παίζοντας με νέες γλώσσες. Κάθε νέα γλώσσα έχει κάτι μοναδικό να προσφέρει. Τούτου λεχθέντος, οι περισσότεροι αρχάριοι ξεκινούν το ταξίδι προγραμματισμού τους είτε με διαδικαστικές γλώσσες όπως το C είτε με αντικειμενοστρεφείς γλώσσες όπως το JavaScript και το C ++.

Επομένως, είναι λογικό να διαβάσετε τα βασικά του αντικειμενοστραφούς προγραμματισμού, ώστε να μπορείτε να κατανοήσετε τις έννοιες και να τις εφαρμόσετε στις γλώσσες που μαθαίνετε εύκολα. Θα χρησιμοποιήσουμε τη γλώσσα προγραμματισμού Ruby ως παράδειγμα.

Μπορεί να ρωτάτε, γιατί η Ρούμπι; Επειδή «έχει σχεδιαστεί για να κάνει τους προγραμματιστές χαρούμενους» και επίσης επειδή σχεδόν όλα στο Ruby είναι ένα αντικείμενο.

Να πάρει μια αίσθηση του αντικειμενοστρεφούς παραδείγματος (OOP)

Στο OOP, εντοπίζουμε τα «πράγματα» που χειρίζεται το πρόγραμμά μας. Ως άνθρωποι, σκεφτόμαστε τα πράγματα ως αντικείμενα με χαρακτηριστικά και συμπεριφορές και αλληλεπιδρούμε με πράγματα που βασίζονται σε αυτά τα χαρακτηριστικά και συμπεριφορές. Ένα πράγμα μπορεί να είναι ένα αυτοκίνητο, ένα βιβλίο και ούτω καθεξής. Τέτοια πράγματα γίνονται τάξεις (τα σχεδιαγράμματα αντικειμένων) και δημιουργούμε αντικείμενα από αυτές τις τάξεις.

Κάθε παρουσία (αντικείμενο) περιέχει μεταβλητές παρουσίας που είναι η κατάσταση του αντικειμένου (χαρακτηριστικά). Οι συμπεριφορές αντικειμένων αντιπροσωπεύονται με μεθόδους.

Ας πάρουμε το παράδειγμα ενός αυτοκινήτου. Ένα αυτοκίνητο είναι ένα πράγμα που θα το έκανε μια κατηγορία . Ένας συγκεκριμένος τύπος αυτοκινήτου, ας πούμε ότι η BMW είναι ένα αντικείμενο της κατηγορίας Αυτοκίνητο . Τα χαρακτηριστικά / ιδιότητες μιας BMW όπως το χρώμα και ο αριθμός μοντέλου μπορούν να αποθηκευτούν σε μεταβλητές. Και αν θέλετε να εκτελέσετε μια λειτουργία του αντικειμένου, όπως οδήγηση, τότε το "drive" περιγράφει μια συμπεριφορά που ορίζεται ως μέθοδος .

Ένα μάθημα γρήγορης σύνταξης

  • Για να τερματίσετε μια γραμμή σε ένα πρόγραμμα Ruby, ένα ερωτηματικό (;) είναι προαιρετικό (αλλά γενικά δεν χρησιμοποιείται)
  • Ενθαρρύνεται η εσοχή 2 χώρων για κάθε ένθετο επίπεδο (δεν απαιτείται, όπως συμβαίνει στο Python)
  • Δεν χρησιμοποιούνται σγουρά τιράντες {}και η τελική λέξη-κλειδί χρησιμοποιείται για να σηματοδοτήσει το τέλος ενός μπλοκ ελέγχου ροής
  • Για να σχολιάσουμε, χρησιμοποιούμε το #σύμβολο

Ο τρόπος δημιουργίας αντικειμένων στο Ruby είναι καλώντας μια νέα μέθοδο σε μια τάξη, όπως στο παρακάτω παράδειγμα:

class Car def initialize(name, color) @name = name @color = color end
 def get_info "Name: #{@name}, and Color: #{@color}" endend
my_car = Car.new("Fiat", "Red")puts my_car.get_info

Για να καταλάβετε τι συμβαίνει στον παραπάνω κώδικα:

  • Έχουμε μια τάξη που ονομάζεται Carμε δύο μεθόδους, initializeκαι get_info.
  • Οι μεταβλητές παρουσίας στο Ruby ξεκινούν με @(Για παράδειγμα @name). Το ενδιαφέρον είναι ότι οι μεταβλητές δεν δηλώνονται αρχικά. Αναδύονται όταν χρησιμοποιούνται για πρώτη φορά και μετά είναι διαθέσιμα σε όλες τις μεθόδους παρουσίας της τάξης.
  • Η κλήση της newμεθόδου προκαλεί τη χρήση της initializeμεθόδου. initializeείναι μια ειδική μέθοδος που χρησιμοποιείται ως κατασκευαστής.

Πρόσβαση σε δεδομένα

Οι μεταβλητές παρουσίας είναι ιδιωτικές και δεν είναι δυνατή η πρόσβαση τους εκτός της τάξης. Για να αποκτήσουμε πρόσβαση σε αυτά, πρέπει να δημιουργήσουμε μεθόδους. Οι μέθοδοι παρουσίας έχουν δημόσια πρόσβαση από προεπιλογή. Μπορούμε να περιορίσουμε την πρόσβαση σε αυτές τις μεθόδους παρουσίας, όπως θα δούμε αργότερα σε αυτό το άρθρο.

Για να λάβουμε και να τροποποιήσουμε τα δεδομένα, χρειαζόμαστε τις μεθόδους "getter" και "setter", αντίστοιχα. Ας δούμε αυτές τις μεθόδους λαμβάνοντας το ίδιο παράδειγμα ενός αυτοκινήτου.

class Car def initialize(name, color) # "Constructor" @name = name @color = color end
 def color @color end
 def color= (new_color) @color = new_color endend
my_car = Car.new("Fiat", "Red")puts my_car.color # Red
my_car.color = "White"puts my_car.color # White

Στο Ruby, το "getter" και το "setter" ορίζονται με το ίδιο όνομα με τη μεταβλητή παρουσίας που αντιμετωπίζουμε.

Στο παραπάνω παράδειγμα, όταν λέμε my_car.color, καλεί πραγματικά τη colorμέθοδο που με τη σειρά της επιστρέφει το όνομα του χρώματος.

Σημείωση: Δώστε προσοχή στον τρόπο με τον οποίο το Ruby επιτρέπει ένα κενό μεταξύ του colorκαι ισούται με την υπογραφή κατά τη χρήση του ρυθμιστή, παρόλο που το όνομα της μεθόδου είναιcolor=

Το γράψιμο αυτών των μεθόδων getter / setter μας επιτρέπει να έχουμε περισσότερο έλεγχο. Αλλά τις περισσότερες φορές, η απόκτηση της υπάρχουσας τιμής και ο καθορισμός μιας νέας τιμής είναι απλή. Επομένως, πρέπει να υπάρχει ένας ευκολότερος τρόπος αντί να ορίζονται πραγματικά οι μέθοδοι getter / setter.

Ο ευκολότερος τρόπος

Χρησιμοποιώντας τη attr_*φόρμα αντ 'αυτού, μπορούμε να πάρουμε την υπάρχουσα τιμή και να ορίσουμε μια νέα τιμή.

  • attr_accessor: και για τα δύο
  • attr_reader: Μόνο για τους λήπτες
  • attr_writer: μόνο για ρυθμιστή

Ας δούμε αυτήν τη φόρμα λαμβάνοντας το ίδιο παράδειγμα ενός αυτοκινήτου.

class Car attr_accessor :name, :colorend
car1 = Car.newputs car1.name # => nil
car1.name = "Suzuki"car1.color = "Gray"puts car1.color # => Gray
car1.name = "Fiat"puts car1.name # => Fiat

Με αυτόν τον τρόπο μπορούμε να παραλείψουμε εντελώς τους ορισμούς λήψης / ρυθμιστή.

Μιλώντας για βέλτιστες πρακτικές

Στο παραπάνω παράδειγμα, δεν ξεκινήσαμε τις τιμές για τις μεταβλητές @nameκαι το @colorinstance, κάτι που δεν είναι καλή πρακτική. Επίσης, καθώς οι μεταβλητές παρουσίας έχουν οριστεί σε μηδέν, το αντικείμενο car1δεν έχει νόημα. Είναι πάντα καλή πρακτική να ορίζετε μεταβλητές παρουσίας χρησιμοποιώντας έναν κατασκευαστή όπως στο παρακάτω παράδειγμα.

class Car attr_accessor :name, :color def initialize(name, color) @name = name @color = color endend
car1 = Car.new("Suzuki", "Gray")puts car1.color # => Gray
car1.name = "Fiat"puts car1.name # => Fiat

Μέθοδοι κλάσης και μεταβλητές κλάσης

Έτσι, οι μέθοδοι τάξης επικαλούνται μια τάξη, όχι σε μια παρουσία μιας τάξης. Αυτά είναι παρόμοια με τις στατικές μεθόδους στην Java.

Σημείωση: selfεκτός του ορισμού της μεθόδου αναφέρεται στο αντικείμενο κλάσης. Οι μεταβλητές τάξης ξεκινούν με@@

Τώρα, υπάρχουν στην πραγματικότητα τρεις τρόποι για τον ορισμό των μεθόδων τάξης στο Ruby:

Μέσα στον ορισμό της τάξης

  1. Χρησιμοποιώντας τη λέξη-κλειδί self με το όνομα της μεθόδου:
class MathFunctions def self.two_times(num) num * 2 endend
# No instance createdputs MathFunctions.two_times(10) # => 20

2. Χρησιμοποιώντας <<? εαυτός

class MathFunctions class << self def two_times(num) num * 2 end endend
# No instance createdputs MathFunctions.two_times(10) # => 20

Εκτός του ορισμού της τάξης

3. Using class name with the method name

class MathFunctionsend
def MathFunctions.two_times(num) num * 2end
# No instance createdputs MathFunctions.two_times(10) # => 20

Class Inheritance

In Ruby, every class implicitly inherits from the Object class. Let’s look at an example.

class Car def to_s "Car" end
 def speed "Top speed 100" endend
class SuperCar < Car def speed # Override "Top speed 200" endend
car = Car.newfast_car = SuperCar.new
puts "#{car}1 #{car.speed}" # => Car1 Top speed 100puts "#{fast_car}2 #{fast_car.speed}" # => Car2 Top speed 200

In the above example, the SuperCar class overrides the speed method which is inherited from the Car class. The symbol &lt; denotes inheritance.

Note: Ruby doesn’t support multiple inheritance, and so mix-ins are used instead. We will discuss them later in this article.

Modules in Ruby

A Ruby module is an important part of the Ruby programming language. It’s a major object-oriented feature of the language and supports multiple inheritance indirectly.

A module is a container for classes, methods, constants, or even other modules. Like a class, a module cannot be instantiated, but serves two main purposes:

  • Namespace
  • Mix-in

Modules as Namespace

A lot of languages like Java have the idea of the package structure, just to avoid collision between two classes. Let’s look into an example to understand how it works.

module Patterns class Match attr_accessor :matched endend
module Sports class Match attr_accessor :score endend
match1 = Patterns::Match.newmatch1.matched = "true"
match2 = Sports::Match.newmatch2.score = 210

In the example above, as we have two classes named Match, we can differentiate between them and prevent collision by simply encapsulating them into different modules.

Modules as Mix-in

In the object-oriented paradigm, we have the concept of Interfaces. Mix-in provides a way to share code between multiple classes. Not only that, we can also include the built-in modules like Enumerable and make our task much easier. Let’s see an example.

module PrintName attr_accessor :name def print_it puts "Name: #{@name}" endend
class Person include PrintNameend
class Organization include PrintNameend
person = Person.newperson.name = "Nishant"puts person.print_it # => Name: Nishant
organization = Organization.neworganization.name = "freeCodeCamp"puts organization.print_it # => Name: freeCodeCamp 

Mix-ins are extremely powerful, as we only write the code once and can then include them anywhere as required.

Scope in Ruby

We will see how scope works for:

  • variables
  • constants
  • blocks

Scope of variables

Methods and classes define a new scope for variables, and outer scope variables are not carried over to the inner scope. Let’s see what this means.

name = "Nishant"
class MyClass def my_fun name = "John" puts name # => John end
puts name # => Nishant

The outer name variable and the inner name variable are not the same. The outer name variable doesn’t get carried over to the inner scope. That means if you try to print it in the inner scope without again defining it, an exception would be thrown — no such variable exists

Scope of constants

An inner scope can see constants defined in the outer scope and can also override the outer constants. But it’s important to remember that even after overriding the constant value in the inner scope, the value in the outer scope remains unchanged. Let’s see it in action.

module MyModule PI = 3.14 class MyClass def value_of_pi puts PI # => 3.14 PI = "3.144444" puts PI # => 3.144444 end end puts PI # => 3.14end

Scope of blocks

Blocks inherit the outer scope. Let’s understand it using a fantastic example I found on the internet.

class BankAccount attr_accessor :id, :amount def initialize(id, amount) @id = id @amount = amount endend
acct1 = BankAccount.new(213, 300)acct2 = BankAccount.new(22, 100)acct3 = BankAccount.new(222, 500)
accts = [acct1, acct2, acct3]
total_sum = 0accts.each do |eachAcct| total_sum = total_sum + eachAcct.amountend
puts total_sum # => 900

In the above example, if we use a method to calculate the total_sum, the total_sum variable would be a totally different variable inside the method. That’s why sometimes using blocks can save us a lot of time.

Having said that, a variable created inside the block is only available to the block.

Access Control

When designing a class, it is important to think about how much of it you’ll be exposing to the world. This is known as Encapsulation, and typically means hiding the internal representation of the object.

There are three levels of access control in Ruby:

  • Public - no access control is enforced. Anybody can call these methods.
  • Protected - can be invoked by objects of the defining classes or its sub classes.
  • Private - cannot be invoked except with an explicit receiver.

Let’s see an example of Encapsulation in action:

class Car def initialize(speed, fuel_eco) @rating = speed * comfort end
 def rating @rating endend
puts Car.new(100, 5).rating # => 500

Now, as the details of how the rating is calculated are kept inside the class, we can change it at any point in time without any other change. Also, we cannot set the rating from outside.

Talking about the ways to specify access control, there are two of them:

  1. Specifying public, protected, or private and everything until the next access control keyword will have that access control level.
  2. Define the method regularly, and then specify public, private, and protected access levels and list the comma(,) separated methods under those levels using method symbols.

Example of the first way:

class MyClass private def func1 "private" end protected def func2 "protected" end public def func3 "Public" endend

Example of the second way:

class MyClass def func1 "private" end def func2 "protected" end def func3 "Public" end private :func1 protected :func2 public :func3end

Σημείωση: Τα δημόσια και ιδιωτικά στοιχεία ελέγχου πρόσβασης χρησιμοποιούνται περισσότερο.

συμπέρασμα

Αυτά είναι τα βασικά του αντικειμενοστρεφούς προγραμματισμού στο Ruby. Τώρα, γνωρίζοντας αυτές τις έννοιες, μπορείτε να πάτε πιο βαθιά και να τις μάθετε δημιουργώντας υπέροχα πράγματα.

Μην ξεχάσετε να χτυπήσετε και να ακολουθήσετε αν σας άρεσε! Συνεχίστε μαζί μου εδώ.