Ruby - Orientado a Objetos

Ruby é uma linguagem puramente orientada a objetos e tudo aparece para Ruby como um objeto. Cada valor em Ruby é um objeto, mesmo as coisas mais primitivas: strings, números e até mesmo verdadeiro e falso. Mesmo uma classe em si é um objeto que é uma instância da classe Class . Este capítulo o levará por todas as principais funcionalidades relacionadas ao Ruby Orientado a Objetos.

Uma classe é usada para especificar a forma de um objeto e combina representação de dados e métodos para manipular esses dados em um pacote organizado. Os dados e métodos dentro de uma classe são chamados de membros da classe.

Definição de classe Ruby

Ao definir uma classe, você define um blueprint para um tipo de dados. Na verdade, isso não define nenhum dado, mas define o que significa o nome da classe, ou seja, em que consistirá um objeto da classe e quais operações podem ser executadas nesse objeto.

Uma definição de classe começa com a palavra-chave class seguido pelo class name e é delimitado por um end. Por exemplo, definimos a classe Box usando a classe de palavra-chave da seguinte forma -

class Box
   code
end

O nome deve começar com uma letra maiúscula e, por convenção, os nomes que contêm mais de uma palavra são executados junto com cada palavra maiúscula e sem caracteres de separação (CamelCase).

Definir objetos Ruby

Uma classe fornece os blueprints para objetos, portanto, basicamente, um objeto é criado a partir de uma classe. Declaramos objetos de uma classe usandonewpalavra-chave. As seguintes declarações declaram dois objetos da classe Box -

box1 = Box.new
box2 = Box.new

O método de inicialização

o initialize method é um método de classe Ruby padrão e funciona quase da mesma maneira que constructorfunciona em outras linguagens de programação orientadas a objetos. O método initialize é útil quando você deseja inicializar algumas variáveis ​​de classe no momento da criação do objeto. Este método pode receber uma lista de parâmetros e, como qualquer outro método ruby, seria precedido pordef palavra-chave conforme mostrado abaixo -

class Box
   def initialize(w,h)
      @width, @height = w, h
   end
end

As variáveis ​​de instância

o instance variablessão uma espécie de atributos de classe e se tornam propriedades de objetos, uma vez que os objetos são criados usando a classe. Os atributos de cada objeto são atribuídos individualmente e não compartilham nenhum valor com outros objetos. Eles são acessados ​​usando o operador @ dentro da classe, mas para acessá-los fora da classe que usamospublic métodos, que são chamados accessor methods. Se tomarmos a classe definida acimaBox então @width e @height são variáveis ​​de instância para a classe Box.

class Box
   def initialize(w,h)
      # assign instance variables
      @width, @height = w, h
   end
end

Métodos de acessador e setter

Para disponibilizar as variáveis ​​de fora da classe, elas devem ser definidas dentro accessor methods, esses métodos acessadores também são conhecidos como métodos getter. O exemplo a seguir mostra o uso de métodos de acesso -

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end

   # accessor methods
   def printWidth
      @width
   end

   def printHeight
      @height
   end
end

# create an object
box = Box.new(10, 20)

# use accessor methods
x = box.printWidth()
y = box.printHeight()

puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"

Quando o código acima é executado, ele produz o seguinte resultado -

Width of the box is : 10
Height of the box is : 20

Semelhante aos métodos de acesso, que são usados ​​para acessar o valor das variáveis, Ruby fornece uma maneira de definir os valores dessas variáveis ​​de fora da classe usando setter methods, que são definidos como abaixo -

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end

   # accessor methods
   def getWidth
      @width
   end
   def getHeight
      @height
   end

   # setter methods
   def setWidth=(value)
      @width = value
   end
   def setHeight=(value)
      @height = value
   end
end

# create an object
box = Box.new(10, 20)

# use setter methods
box.setWidth = 30
box.setHeight = 50

# use accessor methods
x = box.getWidth()
y = box.getHeight()

puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"

Quando o código acima é executado, ele produz o seguinte resultado -

Width of the box is : 30
Height of the box is : 50

Os métodos da instância

o instance methods também são definidos da mesma forma que definimos qualquer outro método usando defpalavra-chave e eles podem ser usados ​​usando uma instância de classe apenas como mostrado abaixo. Sua funcionalidade não se limita a acessar as variáveis ​​de instância, mas também podem fazer muito mais de acordo com sua necessidade.

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end
   # instance method
   def getArea
      @width * @height
   end
end

# create an object
box = Box.new(10, 20)

# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"

Quando o código acima é executado, ele produz o seguinte resultado -

Area of the box is : 200

A classe Métodos e Variáveis

o class variablesé uma variável, que é compartilhada entre todas as instâncias de uma classe. Em outras palavras, existe uma instância da variável e ela é acessada por instâncias do objeto. Variáveis ​​de classe são prefixadas com dois caracteres @ (@@). Uma variável de classe deve ser inicializada dentro da definição de classe, conforme mostrado abaixo.

Um método de classe é definido usando def self.methodname(), que termina com delimitador final e seria chamado usando o nome da classe como classname.methodname conforme mostrado no exemplo a seguir -

#!/usr/bin/ruby -w

class Box
   # Initialize our class variables
   @@count = 0
   def initialize(w,h)
      # assign instance avriables
      @width, @height = w, h

      @@count += 1
   end

   def self.printCount()
      puts "Box count is : #@@count"
   end
end

# create two object
box1 = Box.new(10, 20)
box2 = Box.new(30, 100)

# call class method to print box count
Box.printCount()

Quando o código acima é executado, ele produz o seguinte resultado -

Box count is : 2

O método to_s

Qualquer classe que você definir deve ter um to_smétodo de instância para retornar uma representação de string do objeto. A seguir está um exemplo simples para representar um objeto Box em termos de largura e altura -

#!/usr/bin/ruby -w

class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end
   # define to_s method
   def to_s
      "(w:#@width,h:#@height)"  # string formatting of the object.
   end
end

# create an object
box = Box.new(10, 20)

# to_s method will be called in reference of string automatically.
puts "String representation of box is : #{box}"

Quando o código acima é executado, ele produz o seguinte resultado -

String representation of box is : (w:10,h:20)

Controle de acesso

Ruby oferece três níveis de proteção no nível dos métodos de instância, que podem ser public, private, or protected. Ruby não aplica nenhum controle de acesso sobre variáveis ​​de instância e classe.

  • Public Methods- Os métodos públicos podem ser chamados por qualquer pessoa. Os métodos são públicos por padrão, exceto para inicializar, que é sempre privado.

  • Private Methods- Os métodos privados não podem ser acessados ​​ou mesmo visualizados de fora da classe. Apenas os métodos de classe podem acessar membros privados.

  • Protected Methods- Um método protegido pode ser chamado apenas por objetos da classe de definição e suas subclasses. O acesso é mantido dentro da família.

A seguir está um exemplo simples para mostrar a sintaxe de todos os três modificadores de acesso -

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end

   # instance method by default it is public
   def getArea
      getWidth() * getHeight
   end

   # define private accessor methods
   def getWidth
      @width
   end
   def getHeight
      @height
   end
   # make them private
   private :getWidth, :getHeight

   # instance method to print area
   def printArea
      @area = getWidth() * getHeight
      puts "Big box area is : #@area"
   end
   # make it protected
   protected :printArea
end

# create an object
box = Box.new(10, 20)

# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"

# try to call protected or methods
box.printArea()

Quando o código acima é executado, ele produz o seguinte resultado. Aqui, o primeiro método é chamado com sucesso, mas o segundo método deu um problema.

Area of the box is : 200
test.rb:42: protected method `printArea' called for #
<Box:0xb7f11280 @height = 20, @width = 10> (NoMethodError)

Herança de classe

Um dos conceitos mais importantes na programação orientada a objetos é o de herança. A herança nos permite definir uma classe em termos de outra classe, o que torna mais fácil criar e manter um aplicativo.

A herança também oferece uma oportunidade de reutilizar a funcionalidade do código e tempo de implementação rápido, mas infelizmente Ruby não suporta vários níveis de heranças, mas Ruby suporta mixins. Um mixin é como uma implementação especializada de herança múltipla na qual apenas a parte da interface é herdada.

Ao criar uma classe, em vez de escrever membros de dados e funções de membro completamente novos, o programador pode designar que a nova classe deve herdar os membros de uma classe existente. Esta classe existente é chamada debase class or superclass, e a nova classe é chamada de derived class or sub-class.

Ruby também suporta o conceito de subclasse, ou seja, herança e o exemplo a seguir explica o conceito. A sintaxe para estender uma classe é simples. Basta adicionar um caractere <e o nome da superclasse à sua instrução de classe. Por exemplo, a seguir define uma classe BigBox como uma subclasse de Box -

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end
   # instance method
   def getArea
      @width * @height
   end
end

# define a subclass
class BigBox < Box

   # add a new instance method
   def printArea
      @area = @width * @height
      puts "Big box area is : #@area"
   end
end

# create an object
box = BigBox.new(10, 20)

# print the area
box.printArea()

Quando o código acima é executado, ele produz o seguinte resultado -

Big box area is : 200

Substituição de métodos

Embora você possa adicionar uma nova funcionalidade em uma classe derivada, às vezes você gostaria de alterar o comportamento do método já definido em uma classe pai. Você pode fazer isso simplesmente mantendo o mesmo nome do método e substituindo a funcionalidade do método, conforme mostrado abaixo no exemplo -

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end
   # instance method
   def getArea
      @width * @height
   end
end

# define a subclass
class BigBox < Box

   # change existing getArea method as follows
   def getArea
      @area = @width * @height
      puts "Big box area is : #@area"
   end
end

# create an object
box = BigBox.new(10, 20)

# print the area using overriden method.
box.getArea()

Sobrecarga do operador

Gostaríamos que o operador + realizasse a adição vetorial de dois objetos Box usando +, o operador * para multiplicar a largura e a altura de uma Box por um escalar e o operador unário - para negar a largura e a altura da Box. Aqui está uma versão da classe Box com operadores matemáticos definidos -

class Box
   def initialize(w,h)     # Initialize the width and height
      @width,@height = w, h
   end

   def +(other)       # Define + to do vector addition
      Box.new(@width + other.width, @height + other.height)
   end

   def -@           # Define unary minus to negate width and height
      Box.new(-@width, -@height)
   end

   def *(scalar)           # To perform scalar multiplication
      Box.new(@width*scalar, @height*scalar)
   end
end

Congelando objetos

Às vezes, queremos evitar que um objeto seja alterado. O método freeze em Object nos permite fazer isso, transformando efetivamente um objeto em uma constante. Qualquer objeto pode ser congelado invocandoObject.freeze. Um objeto congelado não pode ser modificado: você não pode alterar suas variáveis ​​de instância.

Você pode verificar se um determinado objeto já está congelado ou não usando Object.frozen?método, que retorna verdadeiro caso o objeto esteja congelado, caso contrário, um valor falso é retornado. O exemplo a seguir esclarece o conceito -

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end

   # accessor methods
   def getWidth
      @width
   end
   def getHeight
      @height
   end

   # setter methods
   def setWidth=(value)
      @width = value
   end
   def setHeight=(value)
      @height = value
   end
end

# create an object
box = Box.new(10, 20)

# let us freez this object
box.freeze
if( box.frozen? )
   puts "Box object is frozen object"
else
   puts "Box object is normal object"
end

# now try using setter methods
box.setWidth = 30
box.setHeight = 50

# use accessor methods
x = box.getWidth()
y = box.getHeight()

puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"

Quando o código acima é executado, ele produz o seguinte resultado -

Box object is frozen object
test.rb:20:in `setWidth=': can't modify frozen object (TypeError)
   from test.rb:39

Constantes de classe

Você pode definir uma constante dentro de uma classe atribuindo um valor numérico direto ou string a uma variável, que é definida sem usar @ ou @@. Por convenção, mantemos nomes constantes em maiúsculas.

Uma vez que uma constante é definida, você não pode alterar seu valor, mas você pode acessar uma constante diretamente dentro de uma classe muito parecido com uma variável, mas se você quiser acessar uma constante fora da classe, você terá que usar classname::constant conforme mostrado no exemplo abaixo.

#!/usr/bin/ruby -w

# define a class
class Box
   BOX_COMPANY = "TATA Inc"
   BOXWEIGHT = 10
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end
   # instance method
   def getArea
      @width * @height
   end
end

# create an object
box = Box.new(10, 20)

# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"
puts Box::BOX_COMPANY
puts "Box weight is: #{Box::BOXWEIGHT}"

Quando o código acima é executado, ele produz o seguinte resultado -

Area of the box is : 200
TATA Inc
Box weight is: 10

As constantes de classe são herdadas e podem ser substituídas como métodos de instância.

Criar objeto usando alocar

Pode haver uma situação em que você deseja criar um objeto sem chamar seu construtor initializeou seja, usando um novo método, nesse caso, você pode chamar alocar , que criará um objeto não inicializado para você, como no exemplo a seguir -

#!/usr/bin/ruby -w

# define a class
class Box
   attr_accessor :width, :height

   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end

   # instance method
   def getArea
      @width * @height
   end
end

# create an object using new
box1 = Box.new(10, 20)

# create another object using allocate
box2 = Box.allocate

# call instance method using box1
a = box1.getArea()
puts "Area of the box is : #{a}"

# call instance method using box2
a = box2.getArea()
puts "Area of the box is : #{a}"

Quando o código acima é executado, ele produz o seguinte resultado -

Area of the box is : 200
test.rb:14: warning: instance variable @width not initialized
test.rb:14: warning: instance variable @height not initialized
test.rb:14:in `getArea': undefined method `*' 
   for nil:NilClass (NoMethodError) from test.rb:29

Informação da Classe

Se as definições de classe são códigos executáveis, isso implica que são executadas no contexto de algum objeto: self deve fazer referência a algo. Vamos descobrir o que é.

#!/usr/bin/ruby -w

class Box
   # print class information
   puts "Type of self = #{self.type}"
   puts "Name of self = #{self.name}"
end

Quando o código acima é executado, ele produz o seguinte resultado -

Type of self = Class
Name of self = Box

Isso significa que uma definição de classe é executada com essa classe como o objeto atual. Isso significa que os métodos da metaclasse e de suas superclasses estarão disponíveis durante a execução da definição do método.