Ruby - Orientado a objetos

Ruby es un lenguaje puro orientado a objetos y todo le parece a Ruby como un objeto. Cada valor en Ruby es un objeto, incluso las cosas más primitivas: cadenas, números e incluso verdadero y falso. Incluso una clase en sí misma es un objeto que es una instancia de la clase Class . Este capítulo lo llevará a través de las principales funcionalidades relacionadas con Object Oriented Ruby.

Una clase se usa para especificar la forma de un objeto y combina la representación de datos y métodos para manipular esos datos en un paquete ordenado. Los datos y métodos dentro de una clase se denominan miembros de la clase.

Definición de clase Ruby

Cuando define una clase, define un plano para un tipo de datos. En realidad, esto no define ningún dato, pero define lo que significa el nombre de la clase, es decir, en qué consistirá un objeto de la clase y qué operaciones se pueden realizar en dicho objeto.

Una definición de clase comienza con la palabra clave class Seguido por el class name y se delimita con un end. Por ejemplo, definimos la clase Box usando la clase de palabra clave de la siguiente manera:

class Box
   code
end

El nombre debe comenzar con una letra mayúscula y, por convención, los nombres que contienen más de una palabra se ejecutan junto con cada palabra en mayúscula y sin caracteres de separación (CamelCase).

Definir objetos Ruby

Una clase proporciona los planos de los objetos, por lo que básicamente un objeto se crea a partir de una clase. Declaramos objetos de una clase usandonewpalabra clave. Las siguientes declaraciones declaran dos objetos de la clase Box:

box1 = Box.new
box2 = Box.new

El método de inicialización

los initialize method es un método de clase estándar de Ruby y funciona casi de la misma manera que constructorfunciona en otros lenguajes de programación orientados a objetos. El método de inicialización es útil cuando desea inicializar algunas variables de clase en el momento de la creación del objeto. Este método puede tomar una lista de parámetros y, como cualquier otro método de ruby, estaría precedido pordef palabra clave como se muestra a continuación -

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

Las variables de instancia

los instance variablesson una especie de atributos de clase y se convierten en propiedades de los objetos una vez que los objetos se crean utilizando la clase. Los atributos de cada objeto se asignan individualmente y no comparten ningún valor con otros objetos. Se accede a ellos usando el operador @ dentro de la clase, pero para acceder a ellos fuera de la clase usamospublic métodos, que se llaman accessor methods. Si tomamos la clase definida anteriormenteBox entonces @width y @height son variables de instancia para la clase Box.

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

Los métodos de acceso y establecimiento

Para que las variables estén disponibles desde fuera de la clase, deben definirse dentro accessor methods, estos métodos de acceso también se conocen como métodos de obtención. El siguiente ejemplo muestra el uso de métodos de acceso:

#!/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}"

Cuando se ejecuta el código anterior, produce el siguiente resultado:

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

Similar a los métodos de acceso, que se utilizan para acceder al valor de las variables, Ruby proporciona una forma de establecer los valores de esas variables desde fuera de la clase utilizando setter methods, que se definen a continuación:

#!/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}"

Cuando se ejecuta el código anterior, produce el siguiente resultado:

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

Los métodos de instancia

los instance methods también se definen de la misma manera que definimos cualquier otro método usando defpalabra clave y se pueden usar usando una instancia de clase solo como se muestra a continuación. Su funcionalidad no se limita a acceder a las variables de instancia, sino que también pueden hacer mucho más según sus requisitos.

#!/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}"

Cuando se ejecuta el código anterior, produce el siguiente resultado:

Area of the box is : 200

La clase Métodos y Variables

los class variableses una variable, que se comparte entre todas las instancias de una clase. En otras palabras, hay una instancia de la variable y se accede a ella mediante instancias de objeto. Las variables de clase tienen como prefijo dos caracteres @ (@@). Una variable de clase debe inicializarse dentro de la definición de clase como se muestra a continuación.

Un método de clase se define usando def self.methodname(), que termina con un delimitador final y se llamaría usando el nombre de la clase como classname.methodname como se muestra en el siguiente ejemplo:

#!/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()

Cuando se ejecuta el código anterior, produce el siguiente resultado:

Box count is : 2

El método to_s

Cualquier clase que defina debe tener un to_smétodo de instancia para devolver una representación de cadena del objeto. A continuación se muestra un ejemplo simple para representar un objeto Box en términos de ancho y alto:

#!/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}"

Cuando se ejecuta el código anterior, produce el siguiente resultado:

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

Control de acceso

Ruby le ofrece tres niveles de protección a nivel de métodos de instancia, que pueden ser public, private, or protected. Ruby no aplica ningún control de acceso sobre las variables de instancia y clase.

  • Public Methods- Cualquiera puede llamar a los métodos públicos. Los métodos son públicos de forma predeterminada, excepto initialize, que siempre es privado.

  • Private Methods- No se puede acceder a los métodos privados, ni siquiera verlos desde fuera de la clase. Solo los métodos de clase pueden acceder a miembros privados.

  • Protected Methods- Un método protegido solo puede ser invocado por objetos de la clase definitoria y sus subclases. El acceso se mantiene dentro de la familia.

A continuación se muestra un ejemplo simple para mostrar la sintaxis de los tres modificadores de acceso:

#!/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()

Cuando se ejecuta el código anterior, produce el siguiente resultado. Aquí, el primer método se llama con éxito pero el segundo método dio un problema.

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

Herencia de clase

Uno de los conceptos más importantes de la programación orientada a objetos es el de herencia. La herencia nos permite definir una clase en términos de otra clase, lo que facilita la creación y el mantenimiento de una aplicación.

La herencia también brinda la oportunidad de reutilizar la funcionalidad del código y el tiempo de implementación rápido, pero desafortunadamente Ruby no admite múltiples niveles de herencias, pero Ruby admite mixins. Un mixin es como una implementación especializada de herencia múltiple en la que solo se hereda la parte de la interfaz.

Al crear una clase, en lugar de escribir miembros de datos y funciones de miembros completamente nuevos, el programador puede designar que la nueva clase herede los miembros de una clase existente. Esta clase existente se llamabase class or superclass, y la nueva clase se conoce como derived class or sub-class.

Ruby también apoya el concepto de subclasificación, es decir, herencia y el siguiente ejemplo explica el concepto. La sintaxis para extender una clase es simple. Simplemente agregue un carácter <y el nombre de la superclase a su declaración de clase. Por ejemplo, a continuación, defina una clase BigBox como una subclase 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()

Cuando se ejecuta el código anterior, produce el siguiente resultado:

Big box area is : 200

Anulación de métodos

Aunque puede agregar una nueva funcionalidad en una clase derivada, a veces le gustaría cambiar el comportamiento de un método ya definido en una clase principal. Puede hacerlo simplemente manteniendo el mismo nombre del método y anulando la funcionalidad del método como se muestra a continuación en el ejemplo:

#!/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 del operador

Nos gustaría que el operador + realice la suma vectorial de dos objetos Box usando +, el operador * para multiplicar el ancho y la altura de un Box por un escalar, y el operador unario - para negar el ancho y alto de Box. Aquí hay una versión de la clase Box con 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 [email protected]           # Define unary minus to negate width and height
      Box.new([email protected], [email protected])
   end

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

Congelar objetos

A veces, queremos evitar que se cambie un objeto. El método freeze en Object nos permite hacer esto, convirtiendo efectivamente un objeto en una constante. Cualquier objeto puede congelarse invocandoObject.freeze. Un objeto congelado no puede modificarse: no puede cambiar sus variables de instancia.

Puede verificar si un objeto dado ya está congelado o no está usando Object.frozen?método, que devuelve verdadero en caso de que el objeto se congele, de lo contrario, se devuelve un valor falso. El siguiente ejemplo aclara el concepto:

#!/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}"

Cuando se ejecuta el código anterior, produce el siguiente resultado:

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

Constantes de clase

Puede definir una constante dentro de una clase asignando un valor numérico o de cadena directo a una variable, que se define sin usar @ o @@. Por convención, mantenemos los nombres constantes en mayúsculas.

Una vez que se define una constante, no puede cambiar su valor, pero puede acceder a una constante directamente dentro de una clase como una variable, pero si desea acceder a una constante fuera de la clase, entonces debería usar classname::constant como se muestra en el siguiente ejemplo.

#!/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}"

Cuando se ejecuta el código anterior, produce el siguiente resultado:

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

Las constantes de clase se heredan y se pueden anular como métodos de instancia.

Crear objeto usando asignar

Puede haber una situación en la que desee crear un objeto sin llamar a su constructor initializees decir, utilizando un nuevo método, en tal caso, puede llamar a allocate , que creará un objeto no inicializado para usted como en el siguiente ejemplo:

#!/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}"

Cuando se ejecuta el código anterior, produce el siguiente 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

Información de la clase

Si las definiciones de clase son código ejecutable, esto implica que se ejecutan en el contexto de algún objeto: self debe hacer referencia a algo. Averigüemos qué es.

#!/usr/bin/ruby -w

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

Cuando se ejecuta el código anterior, produce el siguiente resultado:

Type of self = Class
Name of self = Box

Esto significa que se ejecuta una definición de clase con esa clase como objeto actual. Esto significa que los métodos de la metaclase y sus superclases estarán disponibles durante la ejecución de la definición del método.