POO en ruby parte II
Herencia, encapsulamiento y módulos
La programación orientada a objetos es y sigue sido bastante usada en el mundo del software hoy en día, es por eso que veremos algunas otras características que nos brida Ruby como lenguaje.
Si estás iniciando en el mundo de la programación y todavía desconoces algunos conceptos te recomiendo que leas primero este artículo:
Herencia
El la vida cotidiana una herencia puede significar algo muy bueno o muy malo. Podemos heredar una fortuna o muchísimas deudas, lo cierto es que la herencia comúnmente nos permiten transmitir, bienes, derechos y obligaciones, así podemos acceder a elementos que antes no teníamos y por ende extiende nuestro alcance.
El la programación orientada a objetos (POO) la herencia facilita la creación de objetos a partir de otros ya existentes e implica que una subclase obtiene todo el comportamiento (métodos) y eventualmente los atributos (variables) de su superclase. (wiki)
El propósito de la herencia, es crear versiones especializadas de una clase, veamos un ejemplo:
Food -> Fruit -> Orange
Como podemos ver la clase padre (superclass) siempre es más genérica que las subclases
Una de las características más notables es la herencia de comportamiento veamos un ejemplo real:
Tanto como TextDocument
como HTMLDocument
heredan de Document
, esto les permite a las instancias de ambas acceder a los métodos definidos en Document
html = HTMLDocument.new(name: hello_word, encode: 'utf-8', readonly: false)
html.name
=> "hello_world"
html.print
=> "Parsing"
=> "Printing document"
html.full_name
=> "hello_world.html"
Algo que habrás notado es la palabra reservada super
, esto no es más que un método que solo se puede ejecutar dentro de un método y que tiene como función ejecutar el método de la superclase con el mismo nombre.
El primer ejemplo que notamos es que super
fue invocado desde el initialize
de TextDocumet
y HTMLDocument
, aquí super
busca el método initialize
en la superclase es decir en Document
y simplemente le pasa a Document
todos los argumentos que le fueron inyectados a las instancias de las clases TextDocument
y HTMLDocument
Una característica interesante de super
es que puede pasarle argumentos específicos a la superclase como se puede notar en el initialize
de HTMLDocument
Y podemos usar el retorno de super
ser reutilizado por nuestros métodos de instancia como en el caso de full_name
Encapsulamiento
- Métodos públicos: Los métodos públicos son todos métodos accesibles para cualquier instancia externa a la clase. Un ejemplo de ello es nuestros método
name
:
text = TextDocument.new(readonly: false)
text.name
=> "unnamed"
Hasta ahora solo hemos definido métodos públicos en los ejemplos anteriores, veamos como definir métodos privados
- Métodos privados: Algunas veces queremos tener métodos que solo puedan ejecutare dentro de la definición de la clase, métodos que sirvan de apoyo para crear métodos públicos y nos permitan organizar mejor nuestro código. Hagamos un cambio en uno de los ejemplos anteriores para visualizar que se quiere lograr:
Convertimos el método parse
en un método privado, pero para ello utilizamos la palabra reservada private
. Así que todo método que se defina debajo de la palabra reservada private
se convertirá en un método privado. Entonces ¿seguirá funcionando el método print
?
html = HTMLDocument.new(encode: "utf-8", readonly: false)
html.print
=> Parsing
=> Printing document
¡Funciona!, ¿qué pasaría si llamamos la método parse
directamente?
html.parse
NoMethodError (private method `parse' called for #<HTMLDocument:0x00005618e7b29278>)
El método parse
ya no es accesible más desde fuera de la definición de la clase.
- Métodos protegidos: Los método públicos y los métodos privados son los más comunes a la hora de escribir nuestro código, pero Ruby presenta una característica interesante para otras situaciones no tan comunes. Imaginemos que queremos tener una combinación entre un método público y un método privado, para ellos nuestro método “protegido” debe seguir al menos estas dos reglas.
- Desde afuera de nuestra class, el método
protected
se comporta como un métodoprivado
- Desde dentro de nuestra clase, el método
protected
es accesible como un métodopublic
Aquí un ejemplo:
Aquí podemos ver como el método protegido es accesible dentro de la clase y podemos acceder a su información:
fido = Animal.new
fido.a_public_method
=> Will this work? Yes, I'm protected!
Este ejemplo demuestra la segunda regla, no se puede llamar un método protegido desde fuera de la clase:
fido.a_protected_method
=> NoMethodError: protected method `a_protected_method' called for #<Animal:0x007fb174157110>
Módulos
Los módulo es una de las características más interesantes de Ruby, Se pueden usar para añadir un comportamiento especifico a nuestras clases, también pueden usarse para organizar el código que usamos y aplicar técnicas de composición en lugar de herencia.
Veamos algunos ejemplos:
Include:
Incluir métodos a nuestras clases es la forma más común de usar los módulos, cuando llamamos include
en la definición de la clase, Ruby inserta nuevos métodos disponibles únicamente para las instancia de dicha clase:
Los métodos render_pdf
y render_csv
son incluidos en la clase Document
es por eso que toda instancia de Document
y toda instancia de HTMLDocument
(porque hereda de Document
) podrán tener acceso a ellos:
document = Document.new(readonly: false)
document.print_csv
=> "rendering in csv file: unnamed"html = HTMLDocument.new(name: 'example', encode: 'utf-8', readonly: false)
html.print_pdf
=> "rendering in pdf file: example.html"
Extend:
Usar extend
en la clase importará como métodos de clase los métodos definidos en el modulo
document = Document.new(readonly: false)
Document.destroy(document)
=> "Destroying #{unnamed}"
Usar herencia, principios de encapsulamiento y módulos son clave para desarrollar programas mantenibles, escalables y participativos. Si nuestro código está bien organizado, cuando alguien quiera colaborar en él no sufrirá tratando de entender como interactúa cada parte de nuestro código.