Principios de la programación orientada a objetos
Del artículo en la Wikipedia:
La programación orientada a objetos (POO) es un paradigma de programación que parte del concepto de “objetos” como base, los cuales contienen información en forma de campos (a veces también referidos como atributos cualidades o propiedades) y código en forma de métodos.
La programación orientada a objetos se basa en varios principios que ayudan a diseñar sistemas más eficientes y mantenibles. A continuación, algunos de los principios fundamentales de la POO:
Clases:
Una clase es una especie de “plantilla” en la que se definen los atributos y métodos predeterminados de un tipo de objeto. Esta plantilla se crea para poder crear objetos fácilmente. A la acción de crear nuevos objetos mediante la lectura y recuperación de los atributos y métodos de una clase se le conoce como instanciacion.
Un ejemplo de una clase sería algo como:
Lenguaje: Ruby
|
|
Objetos:
Los objetos son instancias de clases. Cada objeto tiene su propio estado (datos) y comportamiento (métodos). La POO se centra en modelar el mundo real mediante la creación y manipulación de objetos.
Teniendo en cuenta la clase anterior, crear un objeto a partir de ella sería algo como:
Interprete Ruby: Irb
En este ejemplo, fulano
sería el objeto que hemos instanciado (usando el método new
) a partir de la clase User
. Una vez creado el objeto, podemos acceder a sus atributos name
, age
y método sayhello
.
Encapsulación:
La encapsulación implica ocultar los detalles internos de una clase y exponer solo una interfaz pública. Esto permite controlar el acceso a los datos y protegerlos de modificaciones no autorizadas.
En el ejemplo de arriba, se requiere la edad (age
) al momento de instanciar el objeto, pero sí en lugar de solicitar la edad, tan solo solicitamos el año
de nacimiento, podríamos calcular la edad. Además este proceso de cálculo, no necesitaría ser accesible al público, ya que no sería necesario.
Lenguaje: Ruby
|
|
Ahora creamos de nuevo el objeto con la clase recién modificada:
Interprete Ruby: Irb
|
|
Nótese que en el ejemplo, marcamos al método age_calc
como privado, esto hace que dicho método no pueda ser llamado desde el objeto; sin embargo,
si podemos llamarlo desde la misma clase. Lo cual es lo que hacemos en el método say_hello
para realizar el cálculo de edad antes de mostrarla.
Cabe destacar que al atributo age
ahora no puede ser accedido desde el objeto, esto debido a que lo eliminamos del método attr_accessor
, el cual es un método
especial en ruby que genera los métodos getter
(para leer/acceder al atributo desde el objeto) y setter
(para modificar el atributo desde el objeto) automáticamente.
Hacer esta modificación en age
también ayuda a conseguir la característica de encapsulación, ya que nos obliga a manipular a dicha propiedad a través de
la misma clase y evitando así las modificaciones o manipulaciones no autorizadas.
En ruby, los métodos que no están debajo de una declaración private
o protected
son de acceso público, como por ejemplo el método sayhello
y say_age
Herencia:
La herencia permite crear nuevas clases basadas en clases existentes. Una clase derivada (subclase) hereda propiedades y métodos de su clase base (superclase). Esto fomenta la reutilización de código y la jerarquía de clases.
Digamos que queremos crear otra clase, por ejemplo Seller
, pero esta debe tener las mismas características de la clase User
, lo primero que pensaríamos sería
en copiar tal cual los mismos atributos y métodos en la nueva clase. Pero esto haría que nuestro código se repita en múltiples lugares. Para solventar esto
podemos usar la Herencia. Creamos una clase Person
que contenga el código común entre las clases User
y Seller
y hacemos que dichas clases simplemente
hereden esas características.
Un ejemplo de herencia podría ser:
Lenguaje: Ruby
|
|
Ahora instanciamos algunos objetos y comprobamos:
Interprete Ruby: Irb
|
|
Nótese como las clases User
y Seller
están totalmente vacías, sin embargo, sus objetos tienen atributos y métodos funcionales. Esta es la herencia en acción,
todos los atributos y métodos en dichas clases están siendo heredados de la clase Person
. Se podrían agregar nuevos atributos y métodos más específicos
a la clase User
y Seller
y aun así, seguir heredando métodos y atributos comunes desde la clase Person
para evitar así código repetido.
Abstracción:
La abstracción es la capacidad de representar conceptos complejos mediante modelos simplificados. En POO, esto se logra mediante la creación de clases y objetos que encapsulan datos y comportamientos relacionados.
En el ejemplo de encapsulación, de hecho, podemos ver también un ejemplo de abstracción al usar el método say_age
. Al llamar este método se debe primero
calcular la edad y almacenarla en el atributo @age
; sin embargo, como usuario al utilizar fulano.say_age
ni nos enteramos de este cálculo. Ni siquiera
nos enteramos de que hay un atributo adicional @age
en donde se almacena el valor del cálculo. Esto de hecho como usuario no necesitamos saberlo en primera
instancia y es por eso que en el código se abstrae ese comportamiento.
De hecho, podríamos ir más allá y agregar comportamientos adicionales en este método say_age
y su uso no se vería afectado.
Lenguaje: Ruby
|
|
Acá hemos movido el método sayhello
debajo de la declaración private
para que sea ahora de acceso privado, eso solo deja como método público
al método say_age
, es lo único que el usuario necesitaría saber, el usuario no necesita saber del atributo age
ni de los otros métodos en la clase.
Interprete Ruby: Irb
Incluso con el cambio, podemos seguir usando el método say_age
sin problemas, el comportamiento obtenido es prácticamente el mismo.
Polimorfismo:
El polimorfismo permite que objetos de diferentes clases respondan de manera similar a un mismo conjunto de métodos o mensajes. Esto significa que un objeto puede tomar diferentes formas o comportarse de diferentes maneras según el contexto. Por ejemplo, un método “calcularArea()” puede funcionar de manera diferente para diferentes formas geométricas (círculo, cuadrado, triángulo). Esto se logra mediante la implementación de interfaces o la sobrescritura de métodos.
Un ejemplo de polimorfismo podría ser:
Lenguaje: Ruby
|
|
Ahora creamos algunos objetos:
Interprete Ruby: Irb
|
|
Nótese que a pesar de que en las clases JetPasajeros
y Avioneta
estamos heredando métodos de la clase Avion
, al usar el método motor
heredado en los objetos,
correspondientes, el comportamiento es distinto en cada uno. Esto debido a que estamos sobreescribiendo el comportamiento de este método motor
al heredarse.
Este es el polimorfismo en acción, en este caso usado en una herencia para la sobrescritura de métodos. Acá las clases hijas pueden comportarse como la clase padre ya que está heredando sus características y comportamiento; sin embargo, eso no las ata a que no puedan también modificar alguno de los comportamientos heredados.
Otro ejemplo de polimorfismo sería:
Lenguaje: Ruby
|
|
Ahora creamos algunos objetos y probamos:
Interprete Ruby: Irb
|
|
Nótese como el objeto porlamar
correspondiente a la clase GasStation
puede ejecutar métodos de la clase Cheap
así como
también métodos de la clase Expensive
. Eso es polimorfismo en acción, dado que podemos trabajar con las capacidades de un objeto
sin importar su tipo específico.
Acá podría agregar más clases que hagan referencias a tipos de gasolina y sus costos,
mientras las nuevas clases contengan métodos públicos type
y price
, los objetos de la clase GasStation
podrán trabajar con
esos nuevos objetos sin problemas.