Descargar la presentación
La descarga está en progreso. Por favor, espere
Publicada porJulia Ortiz de Zárate Carrizo Modificado hace 6 años
1
Tema 2. Principios del Diseño Orientado a Objetos
Objetivo: El alumno analizará a fondo los conceptos de la teoría Orientada a Objetos, sus implicaciones y alcances, aplicándolos a un Diseño Orientado a Objetos.
2
2 Principios del Diseño Orientado a Objetos
Conceptos de Programación Orientada a Objetos Implementación de conceptos de la POO en el lenguaje Java Principios de Diseño Orientado a Objetos Principios SOLID para diseño de clases. Principios de organización de clases en paquetes.
3
Principios fundamentales de la Programación orientada a objetos
Principios S.O.L.I.D Principios fundamentales de la Programación orientada a objetos
4
Principios S.O.L.I.D. Principios S.O.L.I.D.
Acrónimo mnemónico introducido por Robert C. Martin 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. Single responsibility Open-closed Liskov substitution Interface segregation Dependency inversion
5
las reglas del ajedrez no te hace un buen jugador,
Principios S.O.L.I.D. Es muy fácil entender las definiciones de herencia, composición, abstracción, encapsulamiento o polimorfismo. Pero así como conocer las reglas del ajedrez no te hace un buen jugador, lo mismo pasa con la programación orientada a objetos: se puede conocer los conceptos pero no saber utilizarlos
6
Principios S.O.L.I.D. Entender para qué sirve la orientación a objetos fue un proceso que demoró varios años. Si un programador viene del paradigma de la programación estructurada, lo primero que hará será escribir clases como si fueran TDAs, archivos en donde poder agrupar atributos y métodos y nada más.
7
Principios S.O.L.I.D. SOLID resume buenas practicas en el uso de las distintas técnicas que provee el paradigma, prácticas fundamentales para el buen diseño que propician la mantenibilidad, extensibilidad, adaptabilidad y escalabilidad del código, y sobre todo la salud de los programadores que tendrán que leer y/o modificar nuestro código (que podemos ser nosotros mismos dentro de unos años). Estos principios son fundamentales para escribir un buen Modelo de Dominio orientado a objetos.
8
Principios S.O.L.I.D. Quién lleva años programando sabe que en el 99.99% de los casos los requisitos iniciales se ven alterados una vez ha comenzado el desarrollo o incluso cuando se ha terminado. Esto es una realidad que tenemos que asumir los programadores y tenemos que tenerlo muy en cuenta en nuestros desarrollos.
9
Principios S.O.L.I.D. Para desarrollar programas eficientes con POO, es necesario conocer algunas reglas importantes al respecto, puesto que no es sólo importante conocer la construcción correcta de los objetos sino también la relación que tienen entre ellos. Un buen diseño de POO significa que a la hora de darle mantenimiento al programa, va a ser mucho más entendible y sencillo además de tener alta facilidad para hacer cambios.
10
Principios S.O.L.I.D. SOLID describe cinco principios fundamentales, uno por cada letra, sobre el diseño orientado a objetos. Cuando estos principios se aplican en conjunto es más probable que un desarrollador cree un sistema que sea 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.
11
Principios S.O.L.I.D.
12
S: Single responsibility principle
SRP - Principio de responsabilidad única Este principio indica que una clase debe tener una única responsabilidad y esta responsabilidad debe estar encapsulada en su totalidad por la clase. "Nunca debería haber más de un motivo por el que cambiar una clase" — Robert Martin, paper sobre SRP enlazado desde Los Principios del Diseño Orientado a Objetos.
13
S: Single responsibility principle
Muchas veces para no construir más clases o por no tener claro el propósito de un objeto, se tiende a programar funciones en una clase que no tienen nada que ver con el concepto que ese objeto representa, pero que tiene alguna similitud a las acciones que ya realiza. Por ejemplo, cuando tenemos un algoritmo de formateo de números en una clase destinada a leer de la base de datos porque fue el primer sitio donde se empezó a utilizar. Esto conlleva a tener métodos difíciles de detectar y encontrar de manera que el código hay que tenerlo memorizado en la cabeza.
14
S: Single responsibility principle
Ejemplo:
15
S: Single responsibility principle
Separando responsabilidades…
16
S: Single responsibility principle
En la práctica la mayoría las clases tienen uno o dos métodos nada más. Este principio es quizás el más importante de todos, el más sencillo y a la vez el más complicado de llevar a cabo. Una clase que siga el SRP será más sencilla de modificar que otra que tenga muchas responsabilidades.
17
S: Single responsibility principle
Si tenemos la lógica del cálculo, la lógica de la base de datos y la lógica de la presentación mezcladas en una clase puede ser difícil modificar una parte sin estropear las otras. Mezclar responsabilidades hace, además, que la clase sea más difícil de entender, más difícil de probar, e incrementa el riesgo de duplicar lógica en otras partes del diseño.
18
S: Single responsibility principle
Las violaciones del SRP son fáciles de detectar: Las clases parecen hacer demasiadas cosas, ser demasiado grandes y excesivamente complicadas. La forma más sencilla de arreglarlo es partir la clase. El mejor truco para cumplir con SRP es decidir cómo definir la responsabilidad única. Hay varias formas de descomponer una funcionalidad en responsabilidades, pero la forma ideal es utilizar responsabilidades que puedan cambiar independientemente, de ahí la descripción oficial: "Nunca debería haber más de un motivo por el que cambiar una clase".
19
S: Single responsibility principle
Ejemplo: Se tiene que diseñar una aplicación para un supermercado, una primera aproximación podría ser: Tenemos una clase empleado que puede cobrar a los clientes en la caja registradora, pero también repone el Stock.
20
S: Single responsibility principle
Al principio no parece mala idea. Pero supongamos que nos piden que cambie el proceso de cobro a clientes añadiendo funcionalidades de pago con tarjeta por ejemplo. O que el supermercado crezca y ahora se contrate gente específicamente para reponer el stock de productos. En ambos casos tenemos que modificar la clase Empleado, y es posible que una modificación en una funcionalidad pueda tener efectos colaterales en la otra.
21
S: Single responsibility principle
Si seguimos este principio deberíamos haber hecho un modelo similar a este: Así las peticiones de cambio serán más sencillas de implementar y una nueva incorporación al equipo de desarrollo no tendrá tantos problemas para entender el código. Hay que recordar que en orientación a objetos, los objetos no tienen necesariamente que corresponderse con objetos del mundo real, y que aunque en realidad exista una sola persona que se ocupe de ambas cosas, podemos crear un objeto para cada rol que desempeña.
22
S: Single responsibility principle
Ejemplo: ¿Que ocurre si el modo de conexión cambia? Por ejemplo de usar la línea telefónica a el uso de fibra óptica ¿Y si cambia el protocolo de intercambio de datos?
23
S: Single responsibility principle
La implementación de conexión puede variar e intercambiarse ImplementacionModem solo se encarga del protocolo de intercambio de datos
24
S: Single responsibility principle
Ejercicios:
25
S: Single responsibility principle
El ______________ puede ____________ él mismo.
26
S: Single responsibility principle
27
S: Single responsibility principle
28
S: Single responsibility principle
29
S: Single responsibility principle
Además de hacer su tarea, Division contiene la lógica de bitácora de errores. La lógica de bitácora se reparte por todas las clases del sistema ¿Qué pasará cuando se decida cambiar el archivo donde se guardan los errores? ¿Y cuando quiera que los errores se reporten por correo?
30
S: Single responsibility principle
Division solo indica que un error debe ser reportado, alguien más sabrá como hacerlo. Division solo queda con la responsabilidad de dividir enteros. Logger puede ser configurado para guardar en archivos, enviar por correo, mostrar en consola, etc. Logger sólo tiene la responsabilidad de manejar los mensajes de la bitácora.
31
S: Single responsibility principle
32
O: Open/closed principle
OCP - Principio de abierto/cerrado "Todas las entidades software (clases, módulos, funciones, etc.) deberían estar abiertas a extensión, pero cerradas a modificación" — Robert Martin, paper sobre OCP enlazado desde Los Principios del Diseño Orientado a Objetos. Es decir, se debe poder extender el comportamiento de tal entidad pero sin modificar su código fuente. La idea radica en usar técnicas de Orientación a Objetos como herencia y composición para cambiar (o extender) el comportamiento de una clase sin tener que modificar la propia clase.
33
O: Open/closed principle
“Las entidades de software deben se abiertas para extender su funcionalidad, pero cerradas para su modificación” Abierto - Debe ser fácil utilizarlas en ámbitos para los que no estaban pensadas originalmente, agregando otras entidades. Cerrado - Hay que evitar que se necesite modificar su comportamiento básico o su código fuente.
34
O: Open/closed principle
Por ejemplo, si tenemos una clase llamada ValidacionPedido con un método largo llamado validar(Pedido pedido) que contiene todas las reglas necesarias para validar un pedido. Si las reglas cambian necesitamos cambiar la clase ValidacionPedido, con lo que estamos violando el OCP. Si la clase ValidacionPedido incluyera una colección de objetos IReglaValidacion que contuvieran las reglas podríamos hacer que validar(Pedido pedido) ejecutase todas esas reglas para validar el pedido. De este modo, si las reglas cambian sólo necesitamos crear una nueva IReglaValidacion e incluirla en la instancia de ValidacionPedido en tiempo de ejecución en lugar de hacerlo en la propia definición de la clase.
35
O: Open/closed principle
Cumplir con el OCP debería conseguir que el comportamiento fuese más fácil de cambiar, y además nos ayuda a evitar romper el comportamiento actual mientras se realizan cambios. El OCP además nos hace pensar sobre qué zonas de la clase pueden cambiar, lo cual nos ayuda a elegir abstracciones correctas necesarias para nuestro diseño.
36
O: Open/closed principle
Si se necesita modificar una zona concreta del código constantemente (por ejemplo, las reglas de validación) probablemente sea el momento de utilizar OCP y abstraerse de la parte cambiante del código. Otra señal de una posible violación del OCP es la aparición de estructuras switch que utilizan tipos (si se crea otro tipo nuevo es necesario modificar el switch). En ese caso una saludable dosis de polimorfismo es el mejor tratamiento. Por lo general el OCP es una señal de advertencia de que algunos patrones de diseño deberían ser utilizados.
37
O: Open/closed principle
Ejemplo: Se tiene una empresa que desde sus comienzos ha estado vendiendo agua embotellada, y su sistema esta diseñado de la siguiente manera:
38
O: Open/closed principle
Ahora ha surgido una oportunidad de negocio y quiere empezar a vender botellas de té helado, por lo que necesita un cambio en su modelo. Una primera aproximación sería algo similar a esto: Pero implicaría realizar cambios en la empresa, que tiene que aprender a comunicarse con el nuevo tipo de botella, que probablemente sea muy parecida a la anterior.
39
O: Open/closed principle
Si hubieran seguido el OCP su diagrama de clases habrían utilizado una interfaz que comunicaría el resto del sistema con las botellas, por lo que si no se cambia el api, el resto del sistema puede trabajar ajeno al cambio.
40
O: Open/closed principle
Ejercicios: ¿Que pasará cuando el sitio ya no use MD5 como método de verificación?
41
O: Open/closed principle
No necesitamos modificar el código de AdministradorDescargas para cambiar el tipo de algoritmo de verificación, pues se puede configurar en tiempo de ejecución usando el constructor Esta cerrado para su modificación pues no es necesario modificar su código. Esta abierto para su extensión pues podemos agregar otros algoritmos de verificación que no se conocen en el momento de diseñar la clase.
42
O: Open/closed principle
Si queremos agregar un idioma es necesario modificar BuenosModales.
43
O: Open/closed principle
Las clases de implementación no necesitan modificarse si se agrega un nuevo idioma. Son cerradas a modificación por cambios en el lenguaje y abiertas a extensión porque se pueden agregar más lenguajes. Pero... ¿Qué sucede si queremos agregar un método “ofrecerAyuda”? Este diseño no permite la extensión en tipos de acciones.
44
O: Open/closed principle
Los saludos se pueden guardar en archivos. Un archivo por idioma N entradas en los archivos, una por cada tipo de “frase”. Ni Cliente ni BuenosModales se tienen que modificar cuando se agregan idiomas (un archivo más) ni frases (una entrada más en los archivos) Pero ¿Que pasa si queremos leer las frases no de un archivo, sino de una base de datos? ...
45
O: Open/closed principle
46
L: Liskov substitution principle
LSP - Principio de substitución de Liskov "Las funciones que utilicen apuntadores o referencias a clases base deben ser capaces de usar objetos de clases derivadas sin saberlo" — Robert Martin, paper sobre LSP enlazado desde Los Principios del Diseño Orientado a Objetos. En otras palabras, las subclases deberían comportarse correctamente cuando se utilizan en lugar de las clases padre.
47
L: Liskov substitution principle
LSP es engañosamente sencillo: Deberíamos ser capaces de sustituir una instancia de una subclase por su clase padre y todo debería seguir funcionando. ¿Parece fácil? Bueno, realmente no lo es, lo que probablemente se deba a que por lo general se nos ha aconsejado usar composición en lugar de herencia. Asegurarse de que una subclase funciona en cualquier situación en la que la clase padre también lo hace es realmente complicado, entonces es una buena idea tener el LSP en mente cuando se vaya a utilizar herencia.
48
L: Liskov substitution principle
El ejemplo típico de violación de LSP es la relación Cuadrado ES-UN Rectángulo. Matemáticamente un cuadrado es un caso particular de rectángulo ya que tiene todos sus lados iguales, pero esto no encaja bien cuando se modela en el código. ¿Qué debería hacer la función setAncho(int ancho) cuando ésta se invoca en un Cuadrado? ¿Debería cambiar el largo también? ¿Cómo debería funcionar si se trata como su clase padre, la clase Rectángulo? Si el código espera un comportamiento pero recibe otro en función al subtipo de la clase en sí puede que nos enfrentemos a errores muy difíciles de encontrar.
49
L: Liskov substitution principle
Ejemplo: Asesorías fue creada como una implementación de Materia para poder agregarla a la carga académica del Alumno.
50
L: Liskov substitution principle
El usuario de la Interfaz materia confía en que todas las implementaciones se comportarán de manera regular y las usa sin hacer distinción. ¿Que ocurre cuando se intenta calcular el promedio? El problema radica en que Asesorias implementa una interfaz que no puede satisfacer completamente
51
L: Liskov substitution principle
Ahora todas las implementaciones de las interfaces pueden ser usadas en el ámbito para el que fueron pensadas. El compilador nos impide usar un objeto donde no se debe
52
L: Liskov substitution principle
53
I: Interface segregation principle
ISP - Principio de segregación de la interfaz "Los clientes no deberían estar obligados a depender de interfaces que no utilicen". — Robert Martin, paper sobre ISP enlazado desde Los Principios del Diseño Orientado a Objetos. ISP trata de mantener las interfaces (tanto las interfaces en sí como las clases abstractas) pequeñas y limitadas únicamente a una necesidad muy concreta (a una única responsabilidad).
54
I: Interface segregation principle
El crear interfaces grandes obliga a desarrollar implementaciones muy extensas a todo aquel que quiera seguir el contrato definido en la interfaz. Peor todavía es hacer clases que sólo dan una implementación real de una parte pequeña de la interfaz grande, lo cual elimina totalmente las ventajas de usar interfaces .
55
I: Interface segregation principle
56
I: Interface segregation principle
En caso que solo necesitemos usar la utilidad que permite asegurar páginas en base al perfil del usuario usando el archivo web.config, significaría que únicamente se necesita implementar GetRolesForUser(...) e Initialize(...) ¿Cuál sería entonces la implementación del resto de métodos?. Exacto, throw new NotImplementedException() El hecho de tener una clase que implemente RoleProvider y no tengamos ni idea de qué métodos realmente implementa es malo. Aparte de esto también ocurre que dejamos un montón de ruido inútil dentro de nuestra clase.
57
I: Interface segregation principle
Ejemplo: Estamos implementando un zoo, y queremos crear una interfaz que sirva para todas las aves. Pensamos en loros, flamencos, gaviotas, aves rapaces y gorriones, por lo que implementamos los métodos de comer y volar. Posteriormente el zoo consigue presupuesto extra y compra una pareja de avestruces, por lo que definimos también el método correr. No nos podemos olvidar tampoco de los pingüinos, necesitamos un método para nadar. Como no hemos ido refactorizando entre estos pasos, ahora nuestro sistema tiene esta pinta.
58
I: Interface segregation principle
El problema viene con que, por ejemplo, el avestruz tiene que implementar métodos que no usa, y con la llegada del pingüino tuvo que cambiar innecesariamente para implementar el método de nadar.
59
I: Interface segregation principle
Una forma correcta de haber modelizado el problema seria haber dividido la interfaz en otras mas pequeñas de esta manera De esta manera cada pájaro concreto solo tiene lo que realmente necesita y se pueden añadir nuevas clases sin modificar otras zonas que no estén afectadas.
60
I: Interface segregation principle
Un código cliente no debe ser forzado a depender de interfaces que no usa Se deben separar los métodos de una clase en varias interfaces orientadas a clientes específicos.
61
I: Interface segregation principle
Rectangulo es usada por diferentes sistemas Cada sistema usa un subconjunto de métodos de Rectangulo Clases como Pantalla o Impresora deben de incluirse en todos los sistemas aunque nunca vayan a ser usadas
62
I: Interface segregation principle
Las clases de cada sistema quedan contenidas Rectangulo solo tiene los métodos que son comunes y usados por todos sus clientes Cada cliente solo ve los métodos que le interesan
63
I: Interface segregation principle
En la siguiente versión del programa, se puede actualizar el registro del Producto en el archivo Viola OCP ¿Y si no tuviéramos acceso al código de Alumno? También viola el LSP
64
I: Interface segregation principle
Agregamos funcionalidad sin afectar implementaciones a las que no les interesa el nuevo comportamiento. Cada cliente (Producto, Alumno) ve solo la interfaz que le interesa
65
I: Interface segregation principle
66
D: Dependency inversion principle
DIP - Principio de inversión de dependencias El objetivo de este principio es el uso de abstracciones para conseguir que una clase interactúe con otras clases sin que las conozca directamente. Es decir, las clases de nivel superior no deben conocer las clases de nivel inferior. Dicho de otro modo, no debe conocer los detalles. "A. Los módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambos deben depender de abstracciones. B. Las abstracciones no deben depender de detalles. Los detalles deben depender de abstracciones." — Robert Martin, paper sobre DIP enlazado desde Los Principios del Diseño Orientado a Objetos.
67
D: Dependency inversion principle
DIP explica que un módulo concreto A, no debe depender directamente de otro módulo concreto B, sino de una abstracción de B. Tal abstracción es una interfaz o una clase (que podría ser abstracta) que sirve de base para un conjunto de clases hijas. DIP dice que si una clase depende de otras clases, ésta relación debería ser de dependencia de interfaces en lugar de dependencia de implementaciones concretas. La idea es aislar nuestra clase detrás de un muro de abstracciones de las que depender. Si los detalles tras las abstracciones cambian nuestra clase se encuentra a salvo. Esto ayuda a mantener un acoplamiento bajo y hace que nuestro diseño sea más fácil de cambiar.
68
D: Dependency inversion principle
La clase Logica necesita de un colaborador para guardar el dato Dato en algún lugar persistente. Disponemos de una clase MyBD que es capaz de almacenar Dato en una base de datos MySQL y de una clase FS que es capaz de almacenar Dato en un archivo binario sobre un sistema de archivos. Si en el código de Logica escribimos literalmente el nombre de la clase MyBD como colaborador para persistir datos, ¿Cómo haremos cuando necesitamos cambiar la base de datos por archivos binarios en disco? No quedará otro remedio que modificar el código de Logica.
69
D: Dependency inversion principle
Si las clases MyDB y FS implementasen una misma interfaz IPersistor para guardar Dato, podríamos limitarnos a usar IPersistor (que es una abstracción) en el código de Logica. Cuando los requerimientos exigiesen un cambio de base de datos por archivos en disco o viceversa, sólo tendríamos que preocuparnos de que el atributo myPersistor de la clase Logica, que es de tipo IPersistor contuviese una instancia de MyDB o bien de FS. Incluso si posteriormente se desea almacenar el dato en la nube se podría simplemente crear una clase MyCloud que implemente a IPersistor y pasarla en lugar de MyDB o FS.
70
D: Dependency inversion principle
Donde DIP comienza a ser más útil y un poco más profundo es en un concepto relacionado llamado Inyección de Dependencias. La Inyección de Dependencias consiste en incluir unas clases dentro de otras que las necesitan, de tal forma que no haya que hacer new( ) de instancias concretas. Esta técnica aísla nuestras clases y consigue que los cambios y la reutilización sean mucho más fáciles de conseguir.
71
D: Dependency inversion principle
La otra faceta de DIP se encuentra en las dependencias que hay entre módulos de alto y bajo nivel en aplicaciones que utilizan un diseño basado en capas. Por ejemplo, una clase que acceda a la base de datos no debería depender de un formulario mostrado en la interfaz gráfica con el que mostrar esos datos. En su lugar, la interfaz gráfica debería apoyarse en una abstracción (o abstracciones) sobre la clase que acceda a la base de datos. Las capas tradicionales utilizadas en las aplicaciones (datos, lógia, interfaz gráfico) han sido reemplazadas por el patrón MVC, así que se tiende a pensar en DIP únicamente desde la perspectiva de abstracción de dependencias.
72
D: Dependency inversion principle
Clases de alto nivel (mas alejadas de “fierros”), no deben depender de clases de menor nivel. Solo debe haber dependencias a abstracciones. Las abstracciones de conceptos no deben de depender de clases mas específicas (con detalles de implementación).
73
D: Dependency inversion principle
Teclado e Impresora son clases de bajo nivel con una funcionalidad específica, por lo que las podemos imaginar usadas fácilmente en otras clases. Copiar también tiene una funcionalidad específica que seria deseable usar en otros casos similares, como copiar del teclado a un archivo, o de archivo a archivo...
74
D: Dependency inversion principle
Hubo que modificar Copiar para agregar los distintos tipos de origen que queremos manejar (viola OCP) El problema radica en que Copiar usa clases muy específicas, lo que impide su reutilización.
75
D: Dependency inversion principle
Copiar depende solo de abstracciones, sin implementaciones específicas. Se pueden construir múltiples implementaciones de Reader y Writer para nuevos dispositivos y usar Copiar con todas ellas
76
D: Dependency inversion principle
77
S.O.L.I.D. El uso de los principios puede servir de guía cuando se está realizando el diseño. No hay diseños perfectos, únicamente compromisos y los principios SOLID pueden ayudar a evaluarlos y a alcanzar un balance equilibrado. Sin embargo, ninguno de los principios debe ser seguido al pie de la letra ni dogmáticamente, se debe analizar por ejemplo: ¿Realmente se necesita hacer una gran descomposición en las interfaces para cumplir OCP y DIP? Quizá sí, quizá no, pero considerar los principios SOLID puede ayudar a decidir.
78
S.O.L.I.D. Los principios SOLID encajan naturalmente con la práctica de TDD (Test Driven Design). Por ejemplo, escribir un test o prueba unitaria realmente efectiva para una clase es mucho más fácil si cumples con DIP y se aísla a la clase de sus dependencias. Si se está escribiendo el test primero para así dirigir su diseño, entonces las clases tenderán a usar DIP naturalmente. Si se están haciendo test de clases que ya están escritas, probablemente se encontrará que es más difícil hacer y puede que se acabe haciendo test que requieran interacción del usuario, que se reescriban clases para seguir DIP o, peor todavía, que se tiren a la basura de las clases-demasiado-dificil-de-probar.
79
S.O.L.I.D.
Presentaciones similares
© 2025 SlidePlayer.es Inc.
All rights reserved.