En introduktion til objektorienteret programmering med Ruby

Som studerende på datalogi bruger jeg meget tid på at lære og lege med nye sprog. Hvert nyt sprog har noget unikt at tilbyde. Når det er sagt, starter de fleste begyndere deres programmeringsrejse med enten proceduremæssige sprog som C eller med objektorienterede sprog som JavaScript og C ++.
Derfor er det fornuftigt at gennemgå det grundlæggende i objektorienteret programmering, så du kan forstå begreberne og anvende dem på de sprog, du lærer let. Vi bruger Ruby programmeringssproget som et eksempel.
Du spørger måske, hvorfor Ruby? Fordi det er “designet til at gøre programmører glade” og også fordi næsten alt i Ruby er et objekt.
Få en fornemmelse af det objektorienterede paradigme (OOP)
I OOP identificerer vi de “ting”, som vores program håndterer. Som mennesker tænker vi på ting som objekter med attributter og adfærd, og vi interagerer med ting baseret på disse attributter og adfærd. En ting kan være en bil, en bog og så videre. Sådanne ting bliver klasser (tegninger af objekter), og vi skaber objekter ud af disse klasser.
Hver forekomst (objekt) indeholder forekomstvariabler, der er objektets tilstand (attributter). Objektadfærd er repræsenteret ved metoder.
Lad os tage eksemplet med en bil. En bil er en ting, der gør det til en klasse . En bestemt biltype, siger BMW er et objekt i klassen Bil . De attributter / egenskaber af en BMW såsom farven og modelnummeret kan lagres i instansvariabler. Og hvis du vil udføre en operation af objektet, såsom kørsel, så beskriver "kørsel" en adfærd, der er defineret som en metode .
En hurtig syntaks-lektion
- For at afslutte en linje i et Ruby-program er et semikolon (;) valgfrit (men bruges normalt ikke)
- 2-plads indrykning for hvert indlejrede niveau tilskyndes (kræves ikke, som det er i Python)
- Ingen krøllede parenteser
{}
bruges, og enden nøgleordet bruges til at markere afslutningen på et flow kontrol blok - For at kommentere bruger vi
#
symbolet
Måden, hvorpå objekter oprettes i Ruby, er ved at kalde en ny metode på en klasse, som i eksemplet nedenfor:
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
For at forstå, hvad der foregår i koden ovenfor:
- Vi har en klasse navngivet
Car
med to metoder,initialize
ogget_info
. - Instansvariabler i Ruby begynder med
@
(for eksempel@name
). Den interessante del er, at variablerne ikke oprindeligt er deklareret. De springer i eksistens, når de først bruges, og derefter er de tilgængelige for alle instansmetoder i klassen. - At kalde
new
metoden fårinitialize
metoden til at påberåbe sig.initialize
er en speciel metode, der bruges som konstruktør.
Adgang til data
Forekomstvariabler er private og kan ikke tilgås uden for klassen. For at få adgang til dem er vi nødt til at oprette metoder. Instansmetoder har offentlig adgang som standard. Vi kan begrænse adgangen til disse instansmetoder, som vi vil se senere i denne artikel.
For at få og ændre dataene har vi brug for henholdsvis “getter” og “setter” -metoder. Lad os se på disse metoder med det samme eksempel på en bil.
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
I Ruby defineres "getter" og "setter" med samme navn som den instansvariabel, som vi har at gøre med.
I eksemplet ovenfor, når vi siger my_car.color
, kalder det faktisk color
metoden, som igen returnerer farvenavnet.
Bemærk: Vær opmærksom på, hvordan Ruby tillader et mellemrum mellem color
og lig med at underskrive, mens du bruger setteren, selvom metodens navn ercolor=
At skrive disse getter / setter-metoder giver os mulighed for at have mere kontrol. Men for det meste er det enkelt at få den eksisterende værdi og indstille en ny værdi. Så der skal være en lettere måde i stedet for faktisk at definere getter / setter-metoder.
Den lettere måde
Ved at bruge attr_*
formularen i stedet kan vi hente den eksisterende værdi og indstille en ny værdi.
attr_accessor
: for getter og setter beggeattr_reader
: kun for getterattr_writer
: kun til setter
Lad os se på denne form med det samme eksempel på en bil.
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
På denne måde kan vi springe over getter / setter definitionerne helt.
Taler om bedste praksis
I eksemplet ovenfor initialiserede vi ikke værdierne for @name
og @color
instansvariablerne, hvilket ikke er en god praksis. Da instansvariablerne er indstillet til nul, giver objektet car1
ikke nogen mening. Det er altid en god praksis at indstille instansvariabler ved hjælp af en konstruktør som i eksemplet nedenfor.
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
Klassemetoder og klassevariabler
Så klassemetoder påberåbes på en klasse, ikke på en forekomst af en klasse. Disse svarer til statiske metoder i Java.
Bemærk: self
uden for metoden henviser definitionen til klasseobjektet. Klassevariabler begynder med@@
Nu er der faktisk tre måder at definere klassemetoder i Ruby på:
Inde i klassedefinitionen
- Brug af nøgleordet selv med navnet på metoden:
class MathFunctions def self.two_times(num) num * 2 endend
# No instance createdputs MathFunctions.two_times(10) # => 20
2. Brug <<
; selv
class MathFunctions class << self def two_times(num) num * 2 end endend
# No instance createdputs MathFunctions.two_times(10) # => 20
Uden for klassedefinitionen
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:
- Specifying public, protected, or private and everything until the next access control keyword will have that access control level.
- 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
Bemærk: Den offentlige og private adgangskontrol bruges mest.
Konklusion
Dette er det grundlæggende i Objektorienteret programmering i Ruby. Nu ved at kende disse begreber kan du gå dybere og lære dem ved at bygge seje ting.
Glem ikke at klappe og følge, hvis du nød det! Fortsæt med mig her.