POO Lección 3: Conceptos principales

Conceptos principales de POO

La programación orientada a objetos esta basada en ciertos pilares, conceptos que lo diferencian de otros paradigmas de programación.

  • Abstraction
  • Encapsulation
  • Polymorphism
  • Inheritance

Abstracción

La mayoría del tiempo cuando creamos programas con POO, creamos objetos basados en objetos de la vida real. Sin embargo, los objetos del programa no representan los reales con el 100% de precision (y es raro que esto pueda suceder) En cambio, tus objetos solo modelan atributos y comportamientos del objeto real en un contexto especifico que necesitamos, ignorando el resto.

Por ejemplo, una clase Airplane podríamos considerar su existencia tanto un simulador de vuelos como en una aplicación de reservas de avión. Pero en el contexto de la aplicación de reservas, deberias preocuparte solo sobre mostrar el mapa de asientos y si estan disponibles o no.

La Abstracción es un modelo de un fenomeno u objeto del mundo real, limitado a un contexto especifico, que representa todos los detalles relacionados a ese contexto con una alta precisión y omitiendo todo el resto.

Encapsulación

Para arrancar un motor del auto, solamente necesitamos presionar un botón o girar una llave dependiendo el tipo de auto. No necesitamos conectar cables debajo del capó, girar el cigüeñal, los cilindros, e iniciar el ciclo de potencia del motor. Todos estos detalles estan ocultos debajo del capó del auto. Solamente tenemos una simple interfaz: un boton de encendido, la dirección del auto y algunos pedales. Esto basicamente nos ilustra como un objeto tiene una interfaz, es decir, una parte publica a la que pueden acceder quienes la utilizan, para lograr interaccionar con el objeto en cuestión.

Encapsulación es la habilidad de los objetos para esconder partes de sus estados y comportamientos, de otros objetos, exponiendo solamente una interfaz limitada al resto del programa, con los funcionamientos que deseamos exponer al público o al cliente de nuestra clase.

Encapsular algo significa hacerlo private, y que esto sea accesible solamente desde dentro de la clase mediante métodos. Existe un tipo algo menos restrictivo llamado protected, el cual permite además que los hijos de esa clase, si existen, que puedan acceder a los estados y/o métodos de esa clase padre.

Interfaces y clases/métodos abstractos en la mayoría de los lenguajes de programación estan basados en los conceptos de abstracción y encapsulación. En los lenguajes de POO modernos, los mecanismos de interfaces (usualmente declarados con la palabra reservada interface) dejan que definamos contratos de interacción entre objetos. Esta es una de las razones por las que las interfaces solamente se preocupan sobre el comportamiento de los objetos y el porque no podes declarar un campo en la interfaz.

Imaginemos que tenemos una interfaz FlyingTransport con un metodo fly(origin, destination, passengers). Cuando diseñamos un simulador de transporte, podemos restringir que la clase Airport trabaje solo con objetos que implementan la interfaz FlyingTransport. Esto te asegura que cada objeto pasado al objeto Airport, sin importar si es un Airplane, un Helicopter o un maldito DomesticatedGryphon estara habilitado para aterrizar o despegar de este Aeropuerto.

Podemos cambiar la implementación del metodo fly de la forma que querramos. Siempre y cuando la firma del método (origin, destination, passengers) se mantenga de la misma manera en que fue declarada en la interfaz. Todas las instancias (es decir, un objeto particular de la clase) de la clase Airport pueden funcionar correctamente siempre que los objetos que reciban respeten la interfaz FlyingTransport correctamente.

Herencia

La herencia es la habilidad de crear clases por arriba de las existentes. El principal beneficio de la herencia, es la reutilización de codigo.

Si deseas crear una clase que sea ligeramente diferente de una existente, no hay necesidad de duplicar el código. En cambio, podemos extender la clase existente y poner la funcionalidad adicional en la subclase, que hereda los campos y métodos de la superclase.

La consecuencia de utilizar herencia es que las subclases tienen la misma interfaz que sus clases padres. Además no podemos esconder un metodo en la subclase si este fue declarado en la superclase. También estamos obligados a implementar todos los métodos abstractos que esten declarados en el padre, así estos no tengan sentido en nuestra subclase (quedara vacio en tal caso, aunque no es una buena práctica).

En la mayora de los lenguajes de programación una subclase puede extender solamente de una clase. Por otro lado, cualquier clase puede implementar muchas interfaces al mismo tiempo. Pero, como mencionamos antes, si la superclase implementa una interfaz, todos los hijos o subclases deben también implementarlos.

Polimorfismo

Veremos algunos ejemplos sobre animales. La mayoría de los Animals pueden hacer sonidos. Podemos anticipar que todas las subclases van a necesitar sobreescribir el metodo base makeSound para tener su propio comportamiento, entonces cada subclase puede emitir el sonido correcto según el animal que sea; por lo que podemos declarar este método de manera abstracta para obligar su propia implementación en cada subclase. Esto permite que omitamos cualquier implementación por defecto en la superclase lo cual no tendría sentido alguno (ya que no hay un sonido generico para “Animales”), y además fuerza a las subclases a tener su propio comportamiento, de lo contrario no podrán ser del tipo Animals.

Imaginemos que ponemos muchos gatos y perros dentro de una gran bolsa. Luego, con los ojos cerrados, tomamos los animales uno a uno fuera de la bolsa. Luego de tomar un animal de la bolsa, nosotros no estamos seguros de que tipo es (sabemos solamente que es del tipo Animal). Sin embargo, si lo acariciamos lo suficiente, el animal emitirá un sonido específico, de alegría seguramente, pero dependiendo de su clase concreta tendrá la capacidad de emitir un sonido diferente según sea un gato o un perro en este caso.

Posible código de implementación:

1 bag = [new Cat(), new Dog(), new Cat()];
2
3 foreach (Animal particularAnimal : bag)
4 particularAnimal.makeSound()
5
6 // Meow!
7 // Woof!
8 // Meow!

Este programa no sabe el tipo concreto que reside dentro de la variable particularAnimal; pero, gracias a el mecanismo llamado polimorfismo, el programa puede seguir hasta la subclase del objeto actual al que le queremos ejecutar una acción determinada, donde el método correspondiente es ejecutado con su apropiada implementación, es decir, por más que sea del tipo Animals el programa recorre hasta la implementación concreta de esa interfaz, en el primer caso iría hasta la clase Cat buscaría su método makeSound correspondiente y ejecutaría ese comportamiento, luego en la segunda haría lo mismo pero en este caso sabría que es un Dog por lo tanto tendrá otro comportamiento, pero el mecanismo es el mismo.

Polimorfismo es la habilidad del programa para detectar la clase real del objeto y llamar a su implementación aún cuando el tipo real es “desconocido” (Ya que pareciera ser del tipo Animals) en el contexto actual.

También puedes pensar sobre el polimorfismo como la habilidad de un objeto de “pretender” ser algo más, usualmente una clase que extiende o implementa una interface. En nuestro ejemplo, los perros y gatos en la bolsa pretenden ser animales genericos.

Basicamente estos son los principios en los que se sustenta la programación orientada a objetos, después existen más caracteristicas adicionales según el lenguaje de programación que lo implementa que ayudan mucho a implementar más facilmente estas bases, pero entendiendo esto es la gran base para poder continuar con lo demás.

Written on March 5, 2020