Mi blog personal en el que comparto temas relacionados al desarrollo de software, futbol y otros temas de mi interés
Mostrando las entradas con la etiqueta decorador. Mostrar todas las entradas
Mostrando las entradas con la etiqueta decorador. Mostrar todas las entradas
sábado, 6 de agosto de 2016
Single responsibility principle (parte 2)
Esta es la segunda parte de mi post sobre SRP, en la primer parte comentamos que es y una de las formas en que nos afecta, esta vez hablaremos de otra forma en que nos afecta y veremos algo de código, así que agaaaarrrrrrense...
Primero una de las cosas que hay que tomar en cuenta es que el software y en particular las clases en POO son una representación del negocio, por lo que es una forma muy habitual de "encapsular" ese negocio el crear una clase por ejemplo EmployeeBusiness en donde se mete todo lo que tiene que ver con el empleado, a simple vista esto es correcto ya que coo decimos, representa el negocio, pero el problema es que lo estamos viendo desde el punto de vista de la entidad y ahí es donde esta jodido este asunto, que va a pasar con esta clase?
1. Va a crecer enormemente dado todas las operaciones que se pueden hacer con el empleado.
2. Va a tener un montón de dependencias por lo que se corre el riesgo de un alto acoplamiento.
3. Va tener muchos motivos por los cuales puede cambiar, y acá es donde se rompe este principio.
Pero entonces como CREO YO que se deben de hacer las cosas? bien, pues afectivamente separar bien las cosas de acuerdo al negocio, por ejemplo una clase para manejar las vacaciones del empleado, otra para el cálculo de su nómina, otra para cuando se da de baja y así, como se dan cuenta la separación es efectivamente por negocio y nos damos cuenta ya que cada una de estas clases tiene un "stakeholder" específico y no necesariamente el mismo, por ejemplo la gente de RH es el "encargado" de las modificaciones a las reglas de vacaciones, la gente de nómina es el "encargado" de las modificaciones a las reglas de cálculo de nómina y así sucesivamente. Así tendremos las siguientes características en cada clase:
1. Se hacen pequeñas ya que cada una sirve a un propósito my específico.
2. Las dependencias disminuyen al sólo tener las necesarias para realizar su trabajo.
3. Sólo tiene un motivo por el cual va a cambiar y no sólo un motivo, sino un solo rol que dicta que cambia cómo y porqué.
4. Es mucho más fácil de probar.
5. Cumple con una alta cohesión.
Un ejemplo de este principio lo pueden ver en mi post de patrón decorador para validar en el que las responsabilidades están muy bien definidas y son totalmente independientes cada una de las clases.
Ya se que les prometí código en este post, pero creo que la explicación es lo suficientemente buena como para convencerse de que vale la pena que nuestras clases cumplan con este principio y les dejo la liga mi post donde pueden ver la aplicación de una forma muy clara y sencilla.
Etiquetas:
clean code,
coding,
decorador,
decorator,
java,
patrones de diseño,
principios,
programación,
solid,
srp
sábado, 7 de mayo de 2016
Patrón decorator para validar
Cuantas veces hemos oido de el principio de responsabilidad única (Single responsability) de los principios SOLID, en el que dice que una clase debe de tener una y sólo una responsabilidad, eso dice la teoría, pero ya en la práctica es muy dificil de cumplir ese principio y una de las razones de ello es derivado de las validaciones ya que uno dice que cierta clase debe de hacer cierta acción, pero a su vez para poder hacer esa acción es necesario validar algunas cosas, por ejemplo que un atributo no sea null, pero entonces lo que pasa es que esa clase ya no tiene una sola responsabilidad, sino que aparte de hacer su chamba también tiene que validar algunos valores, en este caso que algo no sea null. En este tipo de escenarios es en donde entra en juego una de las variantes del patrón decorador, con el que lo que hacemos es que la clase base sólo hace su chamba y nada más que eso, y las validaciones pues las creamos como una decoración de la clase base y de esta forma tenemos dos clases donde cada una de ellas tiene una responsabilidad única, una hacer el "negocio" principal y la otra el de validar pero pues veamos algo de código.
veamos la clase, por ejemplo Report
en esta clase se tiene el método getBytes con el que se obtiene un arreglo de bytes que contiene el reporte listo para ser descargado, como vemos, se hacen ciertas validaciones antes de que se genere y descargue el arreglo de bytes, parece todo bien no? pues entonces de lo que hablamos antes, Single responsability, en este caso esta clase hace validaciones y la conversión y obtención del arreglo de bytes del reporte, pues bien, acá es donde podemos aplicar el patrón decorador para separar las validaciones de lo demás quedando las clases de la siguiente forma:
y la forma en que se usan estas clases es:
como se ve, se crea una interfaz que define que método debe de existir, y de allí se definen 3 implementaciones de esa interfaz, en donde una de ellas es la que hace el "negocio" de obtener y transformar el reporte y esa es su única responsabilidad, mientras que las otras dos implementaciones se dedican exclusivamente a hacer la validación que le corresponde a cada una, pero estas dos implementaciones a diferencia de la primera reciben en su constructor un Report, con lo que se puede ir haciendo una cadena de validaciones hasta llegar al "negocio" y así poder ejecutar la ación esperada.
A esto que sucede acá es a lo que se llama "decoración" ya que se tiene un "negocio" que se quiere hacer y que está encapsulada en una clase específica y luego esa clase se "decora" con otra que se dedica a hacer la validación de existencia del archivo y luego esa clase se "decora" con la que se encarga de hacer la validación de que le parámetro no sea nulo, así aplicando este patrón se logra que cada una de las clases tenga una responsabilidad única.
Pues bien, chingón, ya se tiene todo bien separado y demás, pero ahora ¿cuál es la ventaja de tener las clases así? pues bien aunque al parecer es más código, esto nos da la ventaja de que las clases sólo cambian por una única razón por lo que posibles errores y cambios son mas localizados y si se quiere se pueden agregar mas validaciones con más decoraciones de ser necesario, por ejemplo si ahora nos dicen que sólo los reportes con extensión .txt son soportados basta con hacer el nuevo decorador y unirlo a la cadena de llamadas o si se encuentra un bug en como obtener el arregla de bytes del archivo pues se sabe específicamente en que clase se debe de hacer el cambio. Otra gran ventaja es la testeabilidad ya que al depender de una interfaz solamente, en las clases de tests se pueden crear mocks y pasarlos como parámetros y así poder testear cada parte por separado.
veamos la clase, por ejemplo Report
public class Report { public byte[] getBytes(File report) { if(report != null) { if (report.exists()) { return getBytesFromFile(report); } else { throw new FotoEmpleadoInvalida("The report doesn't exists"); } } else { throw new IllegalArgumentException("Unknown report"); } } private byte[] getBytesFromFile(File report) { return new byte[0]; } }
en esta clase se tiene el método getBytes con el que se obtiene un arreglo de bytes que contiene el reporte listo para ser descargado, como vemos, se hacen ciertas validaciones antes de que se genere y descargue el arreglo de bytes, parece todo bien no? pues entonces de lo que hablamos antes, Single responsability, en este caso esta clase hace validaciones y la conversión y obtención del arreglo de bytes del reporte, pues bien, acá es donde podemos aplicar el patrón decorador para separar las validaciones de lo demás quedando las clases de la siguiente forma:
public interface Report { public byte[] getBytes(File report); }
public class NormalReport implements Report { @Override public byte[] getBytes(File report) { return getBytesFromFile(report); } private byte[] getBytesFromFile(File report) { return new byte[0]; } }public class ReportWithFileExistsValidarion implements Report { private final Report report; public ReportWithFileExistsValidarion(Report report) { this.report = report; } @Override public byte[] getBytes(File file) { if (file.exists()) { return this.report.getBytes(file); } else { throw new FotoEmpleadoInvalida("The report doesn't exists"); } } }public class ReportWithNullValidation implements Report { private final Report report; public ReportWithNullValidation(Report report) { this.report = report; } @Override public byte[] getBytes(File file) { if (file != null) { return this.report.getBytes(file); } else { throw new IllegalArgumentException("Unknown report"); } } }
y la forma en que se usan estas clases es:
public class Main { public static final void main(String... args) { Report report = new ReportWithNullValidation( new ReportWithFileExistsValidarion( new NormalReport())); byte[] reportInBytes = report.getBytes(new File("report.csv")); } }
como se ve, se crea una interfaz que define que método debe de existir, y de allí se definen 3 implementaciones de esa interfaz, en donde una de ellas es la que hace el "negocio" de obtener y transformar el reporte y esa es su única responsabilidad, mientras que las otras dos implementaciones se dedican exclusivamente a hacer la validación que le corresponde a cada una, pero estas dos implementaciones a diferencia de la primera reciben en su constructor un Report, con lo que se puede ir haciendo una cadena de validaciones hasta llegar al "negocio" y así poder ejecutar la ación esperada.
A esto que sucede acá es a lo que se llama "decoración" ya que se tiene un "negocio" que se quiere hacer y que está encapsulada en una clase específica y luego esa clase se "decora" con otra que se dedica a hacer la validación de existencia del archivo y luego esa clase se "decora" con la que se encarga de hacer la validación de que le parámetro no sea nulo, así aplicando este patrón se logra que cada una de las clases tenga una responsabilidad única.
Pues bien, chingón, ya se tiene todo bien separado y demás, pero ahora ¿cuál es la ventaja de tener las clases así? pues bien aunque al parecer es más código, esto nos da la ventaja de que las clases sólo cambian por una única razón por lo que posibles errores y cambios son mas localizados y si se quiere se pueden agregar mas validaciones con más decoraciones de ser necesario, por ejemplo si ahora nos dicen que sólo los reportes con extensión .txt son soportados basta con hacer el nuevo decorador y unirlo a la cadena de llamadas o si se encuentra un bug en como obtener el arregla de bytes del archivo pues se sabe específicamente en que clase se debe de hacer el cambio. Otra gran ventaja es la testeabilidad ya que al depender de una interfaz solamente, en las clases de tests se pueden crear mocks y pasarlos como parámetros y así poder testear cada parte por separado.
Etiquetas:
clean code,
coding,
decorador,
decorator,
java,
programación
Suscribirse a:
Entradas (Atom)