Filosofía
El paradigma de la programación orientada a objetos es la implementación del pensamiento orientado a objetos en la programación.
POO nos dice:
- Pensar todo en términos de objetos.
- Representar los objetos de la forma más cercana a cómo expresamos las cosas en la vida real.
- Dar prioridad a los objetos y no a la funcionalidad.
- Pensar en el propósito general del programa como un todo antes de subdividirlo.
- Los programas se definen en términos de objetos, propiedades, métodos, y la interacción (comunicación) entre objetos.
Ejemplo de pensamiento orientado a objetos :
En este ejemplo, se tiene la abstracción de que el objeto Carro tiene diferentes propiedades o características tal como la marca, el color, el modelo,
el peso que corresponden a los atributos del objeto y ciertas
funcionalidades o procedimientos como encender, acelerar y frenar que son los métodos del objeto.
El mundo visto por un programador orientado a objetos :
Principios
SOLID: Es un acrónimo mnemónico introducido por Robert C. Martín a comienzos de la década del 2000 que representa cinco
principios básicos de la programación orientada a objetos y el diseño. Cuando estos principios se aplican en conjunto es más probable que un desarrollador
cree un sistema que sea más fácil de mantener y ampliar con el tiempo. Los principios SOLID son guías que pueden ser aplicadas en el desarrollo de software
para eliminar código sucio provocando que el programador tenga que refactorizar el código fuente hasta que sea legible y extensible.
Robert Cecil Martin
- Principio de una sola responsabilidad
- Principio abierto/cerrado
- Principio de sustitución Liskov
- Principio de segregación de interfaz
- Principio de Inversión de dependencia
Principio de una sola responsabilidad
Cada clase debe tener una única responsabilidad, y esta debe estar contenida únicamente esa la clase. Así:
- Una clase debería tener sólo una razón para cambiar.
- Cada responsabilidad es el eje del cambio.
- Para contener la propagación del cambio, se deben separar las responsabilidades.
- Si una clase asume más de una responsabilidad, será más sensible al cambio y las responsabilidades tendrán acoplamiento.
Ejemplo:
Se tiene la clase Rectángulo que tiene las siguientes responsabilidades: calcular el area y dibujar, como se puede observar son responsabilidades
totalmente diferentes, es decir la clase Rectángulo no tiene una responsabilidad concreta y específica.
La solución a este mal diseño es aplicar el principio de única responsabilidad, para ello lo que se hace es
delegar la responsabilidad de dibujar a otra clase llamada RectanguloGrafico y de esta forma ya se tienen clases con una responsabilidad bien
definida:
Principio abierto/cerrado
Este principio establece que una entidad de software (clase, módulo, función, etc.) debe quedar abierta para su extensión, pero cerrada para su modificación.
Es decir, se debe poder extender el comportamiento de tal entidad pero sin modificar su código fuente.
Una clase está cerrada, dado que puede ser compilada, almacenada en una librería y usada por otras clases.
Pero también está abierta, dado que a partir de ella podríamos crear nuevas subclases que incorporaran características nuevas.
Y al crear una subclase, no hay ninguna necesidad de modificar la superclase.
- Se dice que un módulo está abierto si se puede extender.
- Se dice que un módulo queda cerrado si no se puede modificar por otros módulos.
- Si un cambio impacta a varios modulos, entonces la aplicacion no está bien diseñada.
- Se deben diseñar modulos que nunca cambien para reutilizarlos más adelante a través de su extensión (herencia).
Ejemplo:
En este ejemplo se debe calcular el bono que recibiran los programadores y los gerentes, como se logra ver en la imagen eso se hace mediante una sentencia switch,
lo cual es ineficiente ya que si existiera más tipos de empleados, por ejemplo 100, este bloque switch se llenaría de 100 casos lo cual dificultaría el
mantenimiento del codigo.
La solución a este mal diseño es aplicar el principio de abierto/cerrado, para ello lo que hacemos es extender el comportamiento de la clase Empleado, y
crear el método Calcular bono en la clase Empleado para que todos los subtipos de Empleado puedan redefinir este metodo:
Principio de sustitución Liskov
Los objetos de un programa deberían ser reemplazables por instancias de sus subtipos sin alterar el correcto funcionamiento del programa.
- Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas.
- Funciones que usen punteros o referencias a clases base deben poder usar objetos de clases derivadas sin saberlo.
Ejemplo:
Se puede ver que al lado izquierdo de la imagen que se quiere calcular el bono para programador y gerente, pero en dado caso que hayan 100 tipos de empleados
este metodo se llenaría de 100 sentencias if lo cual es ineficiente pues esto dificultaria el mantenimiento del codigo.
Una alternativa a este mal diseño es aplicar el principio de sustitucion de Liskov, para ello lo que hacemos es recorrer la lista de todos los empleados
y como tenemos que calcular el bono a todo tipo de empleado y los bonos pueden ser diferentes para cada tipo de empleado entonces dejamos que cada empleado
implemente su comportamiento, en otras palabras, se permite que todos los tipos de empleados llamen al metodo calcular bono,
pero cada uno implementará ese método de acuerdo al sueldo que reciba. En ultimas, se ha aplicado el concepto de polimorfismo de subtipos.
Principio de segregación de interfaz
Este principio hace referencia a que muchas interfaces cliente específicas son mejores que una interfaz de propósito general.
- Este principio se aplica a una interfaz amplia y compleja para dividirla en otras más pequeñas y específicas, de tal forma que cada cliente use
sólo aquella que necesite, pudiendo así ignorar al resto. A este tipo de interfaces reducidas se les llama "interfaces de rol".
- Los clientes de un programa dado sólo deberían conocer los métodos que realmente van usar, y no aquellos que no necesitan usar.
- Fue concebido para mantener a un sistema desacoplado respecto a los sistemas de los que depende, y así resulte más fácil refactorizarlo, modificarlo y redesplegarlo.
Ejemplo:
Se puede visualizar que la clase Escorpión al extender de la clase abstracta Animal, se debería alimentar y acariciar al objeto, lo cual en este contexto
sería fatal pues el escorpión es un animal peligroso que no se debe acariciar y tan solo se debería alimentar.
La solución a este inconveniente es aplicar el principio de segregación de la interfaz, este nos permitirá que la clase escorpión no se vea obligada
a tener que implementar todos los comportamientos de un animal, sino que implemente un comportamiento específico. Para ello lo que se hace es dividir
los métodos de la clase Animal para que de esta forma el animal solo se pueda alimentar y en definitiva la clase Escorpión pueda implementar solo este
comportamiento particular.
Principio de inversión de dependencias
Este principio establece:
- Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de abstracciones.
- Las abstracciones no deben depender de los detalles. Los detalles deben depender de abstracciones.
Puede implementarse con: inyeccion de dependencias o inversion del control.
Ejemplo:
Imaginemos que tenemos una cesta de compra que lo que hace es almacenar la información y llamar al método de pago para que ejecute la operación. Nuestro código sería algo así:
Aquí estamos incumpliendo el principio. Una clase de más alto nivel, como es ShoppingBasket (cesta de compra), está dependiendo de otras de alto
nivel las cuales son: SqlDatabase (Base de datos) y CreditCard (tarjeta de credito), y luego se encarga de crear objetos de estas clases y después
utilizarlos.
Si se quieren añadir métodos de pago o enviar la información a un servidor en vez de guardarla en una base de datos local, no hay forma de hacer todo esto
sin desmontar toda la lógica. ¿Cómo lo solucionamos? El primer paso es dejar de depender de clases. Se tienen que definir interfaces que definan el
comportamiento que debe dar una clase para poder funcionar como mecanismo de persistencia o como método de pago.
¿Se nota la diferencia? Ahora ya no hay dependencia de la implementación particular que uno decida. Pero aún tenemos que seguir instanciándolo en ShoppingBasket.
Nuestro segundo paso es invertir las dependencias. Vamos a hacer que estos objetos se pasen por constructor.
De esta forma ya se tendría implementado el principio de inversion de dependencias.
Buenas Prácticas
Patrones de Diseño
Los patrones de diseño buscan dar solución a problemas comunes en la programación.
Tipos de patrones
- Patrones creacionales.
- Patrones estructurales.
- Patrones de comportamiento.
Patrones creacionales
Singleton
Problema
Si cada objeto de la clase Session crea una instancia de Database se puede sobrecargar la base de datos.
Solución 1: Usar una variable global del tipo Database a la que todos tengan acceso.
Las variables globales no son deseadas.
Solución 2
Garantiza que sólo haya una instancia.
El código es intuitivo.
Se debe evitar la clonación.
El constructor debe ser privado o protegido.
Patrones estructurales
Proxy
Problema
Un editor que puede incluir imágenes dentro de un documento. Se requiere que la apertura del documento sea rápida pero las imágenes son muy grandes para ser cargadas rápidamente.
Solución