08/06/2015
1
Lic. Ariel Trellini
Departamento de Ciencias e Ingeniería de la ComputaciónUniversidad Nacional del Sur
Arquitectura y Diseño de Sistemas • 2 Lic. Ariel Trellini • DCIC • UNS
Prácticas de Diseño
Desacoplando
08/06/2015
2
Arquitectura y Diseño de Sistemas • 3 Lic. Ariel Trellini • DCIC • UNS
Programando a Interfaces
Prácticas de Diseño
¿Quién puede definir qué es una interfaz?
Una interfaz es el contrato entre una clase y el mundo exterior.
Cuando una clase implementa una interfaz, promete proveer el
comportamiento indicado por la interfaz.
Ejemplo: Interfaz e implementación de un Logger
Arquitectura y Diseño de Sistemas • 4 Lic. Ariel Trellini • DCIC • UNS
Consideraciones
Implementar una interfaz le permite ser más formal a una clase al definir el comportamiento que se compromete a proveer.
Una interfaz constituye el contrato entre la clase que la implementa y el mundo exterior, y este contrato es forzado en tiempo de compilación.
Una interfaz establece “qué” hace un objeto, en vez de “cómo” lo hace.
Una interfaz define el vocabulario puro de una colaboración, ya que no se ensucia con detalles de implementación. Una vez que entendemos las interfaces, entendemos la colaboración.
Prácticas de Diseño Programando a Interfaces
08/06/2015
3
Arquitectura y Diseño de Sistemas • 5 Lic. Ariel Trellini • DCIC • UNS
Diferencias entre Interfaces y Clases Abstractas
Prácticas de Diseño Programando a Interfaces
Característica Interfaz Clase Abstracta
Herencia múltiple Una clase puede implementar varias interfaces
Una clase puede heredar de solamente una clase abstracta
Implementación por defecto
No provee comportamiento, sólo la firma de los métodos.
Podría proveer comportamiento por defecto para los métodos.
Modificadores de acceso
No se pueden definir modificadores de acceso
Puede tener modificadores de acceso para todos sus miembros.
Impronta Define las capacidades de una clase.
Define la identidad central de una clase. Es usada por objetos del mismo tipo.
Extensibilidad Si se agrega una feature, hay que modificar la clase concreta.
Si se agrega una feature, se podría proveer una implementación por defecto y así, todo sigue funcionando.
Variables de instancia y constantes
No Sí
Arquitectura y Diseño de Sistemas • 6 Lic. Ariel Trellini • DCIC • UNS
Prácticas de Diseño Programando a Interfaces
¿Qué significaría Programar a Interfaces?
Programar a interfaces, no a clases. Esto desacopla las interfaces
de sus implementaciones. Disminuir el acoplamiento entre
objetos promueve la flexibilidad.
“Programar a una interfaz, no a una implementación” es un principio acuñado en el libro Design Patterns: Elements of Reusable Object-Oriented Software (GoF).
Apunta a las relaciones de dependencia que tienen que ser cuidadosamente manejadas en una aplicación de mediana/gran escala.
Es muy fácil agregarle una dependencia a una clase. Sin embargo, deshacerse de una dependencia no deseada puede generar un trabajo de refactoring pesado o aun peor, puede evitar que se reuse código
08/06/2015
4
Arquitectura y Diseño de Sistemas • 7 Lic. Ariel Trellini • DCIC • UNS
Ventajas La posibilidad de cambiar la clase de implementación de cualquier
componente de la aplicación sin afectar al código cliente.
Esto permite parametrizar cualquier parte de la aplicación sin romper otros componentes
Libertad total para implementar interfaces. No hay necesidad de acoplarse a una jerarquía de herencia.
Sin embargo, aun es posible reusar código, utilizando herencias concretas en la implementación de una interfaz.
La capacidad de proveer stubs/mocks simples que implementen interfaces, facilitando el testeo de otras clases y permitiendo que múltiples equipos trabajen en paralelo luego que acuerdan las interfaces.
Facilita la definición de la API publicada
Vale distinguir entre la API pública de un componente (clases, métodos y propiedades públicas) y la API publicada que es la que debe ser usada por los clientes del componente.
Prácticas de Diseño Programando a Interfaces
Arquitectura y Diseño de Sistemas • 8 Lic. Ariel Trellini • DCIC • UNS
Consideraciones No es necesario tener el diseño de interfaces completo antes de comenzar
a codificar. Se puede comenzar con clases concretas e ir descubriendo las interfaces una vez que se tenga un conocimiento pormenorizado del problema.
No significa que hay que escribir una interfaz para cada clase. Criterio posible:
La API de un componente es una excelente candidata
Clases que podrían variar su implementación
Clases que necesitan mockearse para testear a sus clientes.
No es forzoso el uso de interfaces. Las clases abstractas también podrían servir para cumplir con el objetivo.
Provee más flexibilidad en la evolución. Podemos agregar un método a una clase abstracta proveyendo un comportamiento por defecto. Todo esto, sin romper el contrato.
Puede combinarse. Exponer una interfaz e implementarla con una jerarquía de clases.
Prácticas de Diseño Programando a Interfaces
08/06/2015
5
Arquitectura y Diseño de Sistemas • 9 Lic. Ariel Trellini • DCIC • UNS
Ejemplo: Componente para envío de e-mails
Prácticas de Diseño Programando a Interfaces
Arquitectura y Diseño de Sistemas • 10 Lic. Ariel Trellini • DCIC • UNS
Ejemplo: Componente para envío de e-mails (cont)
Prácticas de Diseño Programando a Interfaces
Interfaz Descripción Implementación Descripción
IMailFactory Encargada de la creación de un Nuevo e-mail. Es parte de la interfaz pública del componente
DotNetMailFactory Crea instancias de DotNetMail
IMail Interfaz fluent sobre la que se completan todos los datos del e-mail a enviar. También forma parte de la interfaz pública.
DotNetMail Utiliza System.Net.Mailcomo API subyacente
IMailConfigFactory Se encarga de obtener la configuración del componente.
ConfigSectionMailConfigFactory
Obtiene la configuración desde una sección del web/app.config
IMailConfig Representación de la configuración del componente
MailConfigSection Representación de la sección de configuración.
ITemplateFactory Se encarga de obtener la definición de un template de acuerdo a su nombre
TemplateFactory Recupera los templates de la configuración y el sistema de archivos.
08/06/2015
6
Arquitectura y Diseño de Sistemas • 11 Lic. Ariel Trellini • DCIC • UNS
Ejemplo: Componente para envío de e-mails (cont)
Prácticas de Diseño Programando a Interfaces
Interfaz Descripción Implementación Descripción
IContentRenderer Se encarga de renderizar un template de e-mail inyectando los datos del modelo
RazorRenderer Renderiza los templates utilizando RazonEngine.
IMailManager Componente de negocio que provee métodos para cada una de las situaciones de envío de mail.
MailManager Utiliza el componente de envío de mails para implementar el envío de los mails necesarios por la aplicación
Arquitectura y Diseño de Sistemas • 12 Lic. Ariel Trellini • DCIC • UNS
Inversión de Control
Ejemplo Command Line (sin IoC)
Prácticas de Diseño
El flujo de control no es manejado por nuestro código, sino existe
un framework que decide cuándo invocar a nuestro código.
El control está en nuestro código
08/06/2015
7
Arquitectura y Diseño de Sistemas • 13 Lic. Ariel Trellini • DCIC • UNS
Ejemplo (cont.) .Net Win-Forms (con IoC)
Prácticas de Diseño Inversión de Control
El control lo tiene el framework en el que implementamos el formulario.
Arquitectura y Diseño de Sistemas • 14 Lic. Ariel Trellini • DCIC • UNS
Prácticas de Diseño Inversión de Control
Una característica importante de un framework es que los
métodos definidos por el usuario para adaptarlo (tailoring) a su
problemática, generalmente son llamados desde dentro del
framework, más bien que desde el código de la aplicación del
usuario.
El framework generalmente juega el rol de “programa
principal”, coordinando y secuenciando la actividad de la
aplicación. Esta inversión de control les da a los frameworks el
poder de servir como esqueletos extensibles. Los métodos
suministrados por el usuario adaptan los algoritmos genéricos
definidos en el framework a una aplicación particular.
Ralph Johnson and Brian Foote
08/06/2015
8
Arquitectura y Diseño de Sistemas • 15 Lic. Ariel Trellini • DCIC • UNS
Consideraciones No es un concepto nuevo.
Se comenzó a tratar en 1988, en un artículo Designing Reusable Classes, de Ralph E. Johnson y Brian Foote.
Es una característica intrínseca de los framewoks.
Inversión de Control es una parte clave de lo que hace a un framework diferente de una librería.
Una librería es, esencialmente, un conjunto de funciones que se invocan. Cada llamada realiza algún trabajo y luego retorna el control al cliente.
Un framework encapsula un diseño abstracto. Para usarlo, se debe insertar comportamiento en distintos lugares del framework, ya sea por herencia o por plug-ins. De esta manera, el código del framework llama a nuestro comportamiento.
También se lo conoce como Principio de Hollywood
No nos llames… nosotros te llamamos.
No debe confundirse con Inyección de Dependencias (DependencyInjection), que es un tipo especial de Inversión de Control.
Prácticas de Diseño Inversión de Control
Arquitectura y Diseño de Sistemas • 16 Lic. Ariel Trellini • DCIC • UNS
Ejemplos Conocidos UI Frameworks
Window Forms
Swing
Tecnologías web
Páginas ASP.NET
ASP.NET MVC
JSF
EJBs
Template Methods
Una superclase define el flujo de control a través de un Template Method.
Las sub-clases extienden o implementan los métodos abstractos de la superclase.
Frameworks de testing unitario automático
JUnit
NUnit
Etcéteras.
Prácticas de Diseño Inversión de Control
08/06/2015
9
Arquitectura y Diseño de Sistemas • 17 Lic. Ariel Trellini • DCIC • UNS
Inyección de Dependencias
Prácticas de Diseño
SHOWTIME !Refactoring hacia Inyección de Dependencias.
Sin Inyección de Dependencias Con Inyección de Dependencias
Dependiente (PeliculasService) Dependencias (PeliculasRepository) Provider (Ensamblador)
Participantes:
Arquitectura y Diseño de Sistemas • 18 Lic. Ariel Trellini • DCIC • UNS
Objetivos Eliminar la lógica de gestión del ciclo de vida de las dependencias
Desacoplar las clases de sus dependencias concretas
Idea Básica Convencionalmente, si un objeto (Dependiente) necesita de otro
(Dependencia) para realizar una tarea particular, será responsable de instanciarlo y (eventualmente) liberarlo, lo cual agrega cierta complejidad adicional al objeto Dependiente.
Con Inyección de Dependencias, el objeto Dependiente es provisto de sus Dependencias, moviendo el código relacionado con el ciclo de vida de las Dependencias a un lugar más apropiado.
El Ensamblador se encarga de resolver las dependencias entre los componentes de la aplicación.
Prácticas de Diseño Inyección de Dependencias
Es un tipo específico de Inversión de Control donde el aspecto de
control que se invierte es el de resolución de dependencias.
Martin Fowler
08/06/2015
10
Arquitectura y Diseño de Sistemas • 19 Lic. Ariel Trellini • DCIC • UNS
Tipos de Inyección de Dependencias
Inyección por Interfaz (Tipo 1) Cada dependencia provee una interfaz que su usuario tiene que
implementar para obtener la dependencia en ejecución.
Prácticas de Diseño Inyección de Dependencias Tipos
Interface Injection
Arquitectura y Diseño de Sistemas • 20 Lic. Ariel Trellini • DCIC • UNS
Inyección por Setter (Tipo 2) El dependiente provee un método “setter” el cual el ensamblador utiliza
para inyectarle la dependencia.
Inyección por Constructor (Tipo 3) Las dependencias son inyectadas a través del constructor de la clase.
Prácticas de Diseño Inyección de Dependencias Tipos
08/06/2015
11
Arquitectura y Diseño de Sistemas • 21 Lic. Ariel Trellini • DCIC • UNS
Contenedor de Inyección de Dependencias
¿Por qué Container y no Factory? Los Containers toman más responsabilidades que sólo instanciar los objetos
e inyectar sus dependencias.
Alcance del objeto: Singleton, Transient, , etc.
Inicializar y liberar.
Etc.
El Container eventualmente se queda con una referencia al objeto creado (para singletons y flyweights).
Prácticas de Diseño Inyección de Dependencias Contenedor
Implementa el Ensamblador del patrón Dependency Injection. Se
encarga de manejar el ciclo de vida de los objetos administrados
por el container: instanciar, configurar, resolver dependencias,
liberar.
Arquitectura y Diseño de Sistemas • 22 Lic. Ariel Trellini • DCIC • UNS
Ejemplos .NET:
Unity
Spring.NET
Ninject
Castle Windsor
Java
Spring Framework
Guice
PicoContainer
HiveMind
Dependency Injection for Java (API reference)
Avalon
PHP
Symfony
Bucket
Pimple
Prácticas de Diseño Inyección de Dependencias Contenedor
08/06/2015
12
Arquitectura y Diseño de Sistemas • 23 Lic. Ariel Trellini • DCIC • UNS
Service Locator
Prácticas de Diseño Inyección de Dependencias Service Locator
Es un objeto que conoce como obtener referencias a cada
componente de la aplicación.
Martin Fowler
Arquitectura y Diseño de Sistemas • 24 Lic. Ariel Trellini • DCIC • UNS
Ejemplos
Uso en conjunción con un DI container Cuando se necesita una instancia manejada por el container desde un
objeto no manejado por el container.
Cuando se requiere una nueva instancia de una dependencia en cada invocación, y el objeto dependiente tiene un ciclo de vida mayor.
Prácticas de Diseño Inyección de Dependencias Service Locator
08/06/2015
13
Arquitectura y Diseño de Sistemas • 25 Lic. Ariel Trellini • DCIC • UNS
Consideraciones
Service Locator (SL) vs Inyección de Dependencias (DI) Ambos proveen el desacoplamiento básico: El objeto dependiente es
agnóstico de la implementación de la dependencia.
La gran diferencia entre los dos patrones es la forma en que la instancia concreta de la dependencia es suministrada al objeto dependiente:
Con SL, la clase dependiente necesita interpelar explícitamente al SL para recuperar la instancia.
Con DI, no hay un requerimiento explícito, sino la dependencia aparece seteadaen el objeto dependiente, gracias a DI.
DI hace más explícito el reconocimiento de las dependencias.
DI puede introducir cierta complejidad adicional a la de un SL.
SL requiere que la clase dependiente tenga una referencia al SL.
Ambos facilitan el testing, aunque DI lo hace de manera más explícita.
Prácticas de Diseño Inyección de Dependencias Consideraciones
Arquitectura y Diseño de Sistemas • 26 Lic. Ariel Trellini • DCIC • UNS
Consideraciones (cont.)
Inyección por Setter (SI) vs Inyección por Constructor (CI) La elección entre estas dos Tipos de DI es interesante ya que atiende un
poblema más general de la POO: ¿Las propiedades de un objeto debieranllenarse en un constructor o a través de setters?
CI permite crear objetos válidos desde su concepción
CI permite ocultar cualquier propiedad que sea inmutable, no proveyendoel setter para la misma.
Si la cantidad de dependencias de un objeto es muy elevada, convendríautilizar SI, ya que mejoraría la legibilidad de la clase.
Con SI cada setter tiene un nombre que representa a la propiedad en cuestión. Con CI, muchas veces se debe confiar en la posición del parámetro.
Prácticas de Diseño Inyección de Dependencias Consideraciones
08/06/2015
14
Arquitectura y Diseño de Sistemas • 28 Lic. Ariel Trellini • DCIC • UNS
Beneficios Desacopla los componentes de sus dependencias concretas
DI hace posible eliminar, o al menos reducir, las dependencias innecesarias sobre un componente.
Retrasa la resolución de dependencias Las dependencias se resuelven en tiempo de configuración o de ejecución.
Código más reusable Reducir las dependencias entre componentes generalmente facilita el reuso
de los componentes en otros contextos.
El hecho que las dependencias puedan ser inyectadas y configuradasexternamente, incrementa la reusabilidad de un componente.
Código más testeable Cuando se inyectan dependencias en un componente, es posible inyectar
mocks/stubs de estas dependencias
Prácticas de Diseño Inyección de Dependencias Beneficios
Arquitectura y Diseño de Sistemas • 29 Lic. Ariel Trellini • DCIC • UNS
Beneficios (Cont.)
Código Más Legible DI mueve las dependencias a la interfaz del componente. Esto facilita ver
qué dependencias tiene un componente.
Acarreo de Dependencias Reducido El acarreo de dependencias se manifiesta cuando un objeto tiene un
parámetro en uno de sus método que no usa para nada. Sin embargo, lo necesita para crear a una de sus dependencias.
DI elimina este acarreo dependencias.
Promueve la Programación a Interfaces
Favorece el desarrollo de arquitecturas pluggeables.
Prácticas de Diseño Inyección de Dependencias Beneficios
08/06/2015
15
Arquitectura y Diseño de Sistemas • 30 Lic. Ariel Trellini • DCIC • UNS
Problemas
El uso excesivo puede aumentar la complejidad de las aplicaciones.
Puede dificultar el mantenimiento de las aplicaciones
Anarquía de configuración
Prácticas de Diseño Inyección de Dependencias Problemas