Post on 19-Sep-2018
Principios de Diseño Orientado
a Objetos
PRINCIPIOS DE DISEÑO DE CLASES • PRINCIPIO ABIERTO-‐CERRADO (OCP): Un módulo debe ser abierto para extensión pero cerrado para modificación.
• Wikipidia: La noción de que las “en?dades de so@ware … deben estar abiertas para su extensión, pero cerradas para su modificación”.
• Principio Abierto-‐Cerrado: Abierto a extensiones Cerrado a modificaciones de código existente debido a extensiones
PRINCIPIOS DE DISEÑO DE CLASES • El Principio Open/Closed (Open/Closed Principle, OCP) fue acuñado por
el Dr. Bertrand Meyer en su libro "Object Oriented So@ware Construc?on" y básicamente lo que nos dice este principio es que "las en?dades de so@ware (clases, módulos, funciones…) deben estar abiertas para extenderse, pero cerradas para modificación".
• Las clases que cumplen con OCP ?enen dos caracterís?cas: – Son abiertas para la extensión; es decir, que la lógica o el comportamiento
de esas clases puede ser extendida en nuevas clases, usando herencia. – Son cerradas para la modificación, y por tanto el código fuente de dichas
clases debería permanecer inalterado.
• ¿Qué significa esto?: Que una en?dad ?ene que ser extendida sin necesidad de ser modificada internamente. Por ejemplo, deberíamos poder ampliar la funcionalidad de una clase vía herencia sin necesidad de tener que modificar su comportamiento interno.
Principio abierto/cerrado
Las en7dades so8ware deben estar abiertas para su extensión, pero cerradas para su modificación
Bertran Meyer
Diseño cerrado/cerrado
• Finalidad – Sistema funcionando (cerrado), pero
ampliable (abierto) – Conseguir cambios añadiendo nuevo
código sin afectar al resto de elementos del diseño
Clase A Clase B
• Ambigüedad – La dependencia “uno a uno” se
transforma en una dependencia de “uno a muchos”
• Ventajas • Evita que cambios en un módulo
afecten a otros módulos
Diseño abierto/cerrado
Principio abierto/cerrado
Análisis
Diseño cerrado/cerrado
Clase A Clase B
Clase A Clase Abstracta B
Clase B1 Clase B2
Están abiertos para su extensión • Eso implica que el
comportamiento de los módulos puede ser extendido. Se puede hacer que un módulo se comporte de formas nuevas cuando los requisitos de la aplicación cambien.
Están cerrados para su modificación • El código fuente del módulo es
inalterable. No se permite realizar cambios al código fuente.
Diseño abierto/cerrado
Principio abierto/cerrado
Análisis
Diseño cerrado/cerrado
Clase A Clase B
Clase A Clase Abstracta B
Clase B1 Clase B2
• Se ?ene una aplicación que es capaz de dibujar círculos y cuadrados. Los círculos y los cuadrados deben ser dibujados en un determinado orden, por lo tanto se creará una lista en el orden apropiado de círculos y de cuadrados para que la aplicación recorra la lista en ese orden y dibuje cada círculo o cada cuadrado.
• U?lizando una versión estructurada que NO se ajusta al principio abierto/cerrado, u?lizando lenguaje C, se puede resolver este problema como se muestra en el ejemplo EPDiseno0. Se puede observar que tanto la estructura Cuadrado como la estructura Círculo ?enen el primer elemento en común, que no es más que un iden?ficador del ?po de figura.
EJEMPLO: NO cumple el Principio abierto/cerrado
• La función DibujaFiguras recorre la matriz de punteros a elementos de ?po Figura, examinando el ?po de figura y llamando a la función apropiada.
• Sin embargo, la función DibujaFiguras no cumple el principio
abierto/cerrado porque no está cerrada a nuevos ?pos de figuras. Si se quiere extender esta función para dibujar rectángulos, se ?ene que modificar la función DibujaFiguras.
VER <EPD0.CPP>
EJEMPLO: NO cumple el Principio abierto/cerrado
• En el Ejemplo EPDiseno0-‐1 se presenta la solución al problema planteado, pero de forma que cumpla el principio Abierto/cerrado. Aprovechando las ventajas Programación Orientada a Objetos en C++. En este caso, se ha definido la clase Figura como clase abstracta. Esta clase cuenta con un método virtual puro denominado Dibuja.
• Las clases Círculo y Cuadrado son clases derivadas de la clase Figura. Por su parte, la función DibujaFiguras no necesita modificarse en el caso de que se añadan nuevas figuras a la jerarquía. De acuerdo con esto se puede extender el comportamiento de la función DibujaFiguras sin modificar nada en absoluto de su código, cumpliendo así el principio abierto/cerrado. VER <EPD0-‐1.CPP>
EJEMPLO: SÍ cumple el Principio abierto/cerrado
Principio de sus?tución de Liskov
“Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas”
• El Principio de Sus?tución de Liskov fue acuñado por Bárbara Liskov (de ahí el nombre del principio) en el año 1987 durante una conferencia sobre Jerarquía y Abstracción de datos.
Principio de sus?tución de Liskov
“Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas”
• Su principal come?do es la de asegurar en la herencia entre clases de la Programación Orientada a Objetos que una clase derivada debe comportarse como la clase base. La definición es:
• “Debe ser posible u?lizar cualquier objeto instancia de una subclase en lugar de cualquier objeto instancia de su superclase sin que la semán?ca del programa escrito en los términos de la superclase se vea afectado”
Principio de sus?tución de Liskov Otras definiciones de este principio son: • Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer
las diferencias entre ellas • Toda superclase debe soportar ser sus?tuida por una subclase, lo que en términos de
contratos significa que las precondiciones de los métodos de la subclase no serán más fuertes que las de la clase base, y sus pos condiciones no serán más débiles, es decir, los métodos derivados no deben esperar más ni proveer menos que los originales.
• Los métodos que usan punteros o referencias a clases base deben ser capaces de u?lizar objetos de las clases derivadas sin tener conocimiento de ello (Mar?n96b), también: “Las clases derivadas deben ser u?lizables a través de la interfaz de la clase base, sin necesidad de que el usuario conozca la diferencia”
• Si por cada objeto O1 del ?po S existe un objeto O2 del ?po T tal que para todos los programas P definidos en términos de T y el comportamiento de P permanece invariable cuando O1 es sus?tuido por O2, entonces S es un sub?po de T.
Rectángulo Cuadrado
Propiedades alto ancho
lado
Operaciones SetAlto(x) SetAncho(y) GetAlto() GetAncho()
SetLado(z) GetLado()
Rectángulo
Cuadrado
ES – UN ?
¿Cuadrado ES-‐UN Rectángulo? ¿Cómo programarlo para Respetar Principio de sus?tución de Liskov?
Propiedades y métodos
• Ambigüedad: – Los programas no saben si
trabajan con objetos de super?pos o de sub?pos
• Ventajas • El enunciado de Mar?n es
confuso: – “Los sub7pos deben ser sus7tuibles
por los super7pos”, pero la definición de sub?po se basa en la sus?tución
S es sub?po de T
Obj-‐1 es un objeto de S
Obj-‐2 es un objeto de T
Para todo programa P ( T ) comportamiento P(Obj-‐1) = comportamiento P(Obj-‐2) Cuando Obj-‐1 es sus?tuido por Obj-‐2
T
S
Principio de sus?tución de Liskov
Análisis
Un ejemplo de violación del principio de sus?tución de Liskov
Un ejemplo de violación del principio de sus?tución de Liskov
Un ejemplo de violación del principio de sus?tución de Liskov
Un ejemplo de violación del principio de sus?tución de Liskov
• Para ello, supongamos que a una determinado “cliente” (otro programa), implementa la siguiente función que espera un puntero o una referencia a un objeto de ?po Rectángulo
• ¿Qué sucede si se le pasa se le pasa una instancia de la clase Cuadrado?
Un ejemplo de violación del principio de sus?tución de Liskov
ERROR
Versión que SÍ respeta el principio de sus?tución de Liskov
Versión que SÍ respeta el principio de sus?tución de Liskov
Principio de responsabilidad única
Principio de Única Responsabilidad (Single responsibility principle)
La noción de que un objeto solo debería tener una única responsabilidad
• El Principio de responsabilidad única (Single Responsability Principle -‐ SRP) fue acuñado por Robert C. Mar7n en un arvculo del mismo vtulo y popularizado a través de su conocido libro [patrones Gof]
• "Cada objeto en el sistema deben tener una simple responsabilidad, y todos los servicios de los objetos deben cumplir con esa simple responsabilidad"
• En términos prác?cos, este principio establece que: • "Una clase debe tener una y solo una única causa por la cual puede ser
modificada.“ • "Cada clase debe ser responsable de realizar una ac?vidad del sistema
Principio de responsabilidad única
Principio de Única Responsabilidad (Single responsibility principle)
La noción de que un objeto solo debería tener una única responsabilidad
Diseño con una sola clase (incorrecto)
Cliente P
Cliente Q
Clase X Elementos asociados a la responsabilidad A
Elementos asociados a la responsabilidad B
• Finalidad – Evitar que el cambio de una
responsabilidad en una clase pueda provocar fallos en las demás responsabilidades de la clase
– Evitar que los clientes de una clase carguen con elementos que no u?lizan
Diseño con una sola clase
(no adecuado)
Diseño con dos clases
Cliente P
Cliente Q
Clase X Elementos asociados a la responsabilidad A
Elementos asociados a la responsabilidad B
Cliente P
Cliente Q
Clase XA Elementos asociados a la responsabilidad A
Clase XB Elementos asociados a la responsabilidad B
Principio de responsabilidad única
Diseño con una clase
• Realidad del principio: – División salomónica puntual
• Ambigüedad: – Aumenta entre los elementos de
responsabilidades separadas – Aumenta entre la clase cliente hacia las
clases separadas que no u?lizan – Disminuye entre la clase cliente hacia las
clases separadas que u?lizan
Cliente P
Cliente Q
Clase X Responsabilidad A
Responsabilidad B
Diseño con dos clases
Cliente P
Cliente Q
Clase XA Responsabilidad A
Clase XB Responsabilidad B
Principio de responsabilidad única
Análisis
Principio de inversión de dependencias
Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de las abstracciones Las abstracciones no deben depender de los detalles. Los detalles deben depender de las abstracciones.
Robert C. Mar?n
Principio de inversión de dependencias
• Un software que cumple sus requisitos pero que presenta alguna, o todas, de las características siguientes tiene un mal diseño:
1. Rigidez: Es difícil de cambiar porque cada cambio tiene demasiados efectos en otras partes del sistema.
2. Fragilidad: Cuando se realiza un cambio, partes inesperadas del sistema dejan de funcionar.
3. Inmovilidad: Es difícil de reutilizar en otras aplicaciones porque no puede separarse de la aplicación actual.
• El Principio de Inversión de dependencia ayuda a mantener nuestro código totalmente desacoplado, asegurándonos que dependemos de abstracciones en vez de implementaciones concretas. La Inyección de dependencias es una implementación de este principio, aunque ambos términos se usan de forma intercambiable.
Principio de inversión de dependencias
• Finalidad: – Conseguir que los cambios en los módulos de bajo nivel no afecten a los módulos de alto nivel
– Facilitar la reu?lización de los módulos de alto nivel
Diseño tradicional
Nivel Polí?ca
Nivel Mecanismo
Nivel U?lidad
Diseño tradicional
Nivel Polí?ca
Nivel Mecanismo
Nivel U?lidad
Principio de inversión de dependencias
Diseño con inversión de dependencias
Nivel Polí?ca
Nivel Mecanismo
Nivel U?lidad
Interfaz Polí?ca
Interfaz Mecanismo
Polí?ca
Mecanismo
U?lidad
Ejemplo que NO cumple Principio de inversión de dependencias
• Como ejemplo se va a tomar un programa sumamente simple, el cual va a tener la misión de mandar los caracteres que se introduzcan por el teclado a un archivo en disco. El diagrama de estructura que se correspondería con dicho programa se muestra en la Figura siguiente.
Ejemplo que NO cumple Principio de inversión de dependencias
Ver <EPDiseno2.CPP> • En este ejemplo las dos funciones de más bajo nivel
LeerTeclado y GuardarArchivo son reutilizables, se pueden utilizar en otros programas para acceder al teclado y para guardar caracteres en un fichero de texto.
• Sin embargo, la función Copiar, que es la que encierra la
política del proceso y sería deseable reutilizar, no se puede reutilizar en ningún otro proceso que no haga referencia al teclado y a un fichero. Ya que, se tiene que la función Copiar es dependiente del disco y no puede reutilizarse en otro contexto diferente.
Ejemplo que NO cumple Principio de inversión de dependencias
• Así, si se quisiese añadir una nueva funcionalidad al programa Copiar, por ejemplo que pudiese mandar los datos a un fichero de texto o a la impresora, habría que modificar el módulo Copiar añadiendo una condición que seleccione el dispositivo en función de una bandera.
• Esto añade cada vez más interdependencias en el
sistema, de forma que cuantos más dispositivos se introduzcan mayor será el grado de dependencia del módulo Copiar con varios módulos de bajo nivel. Como consecuencia se habrá obtenido un código rígido y frágil.
Ejemplo que SÍ cumple Principio de inversión de dependencias
Ver <EPDiseno3.CPP>
• Para solventar estas dependencias de la función de alto nivel (Copiar) de las funciones de bajo nivel (Guardar Archivo, EscribeImpresora) se debe buscar la forma de independizar la función Copiar de los detalles que él controla, para que de esta forma pueda ser reutilizado sin problemas, esto es, se debe buscar un módulo que copie caracteres de cualquier dispositivo de entrada a cualquier dispositivo de salida, para lo cual se debe tener presente el principio de inversión de dependencias.
Ejemplo que SÍ cumple Principio de inversión de dependencias
Ver <EPDiseno3.CPP>
Ejemplo que SÍ cumple Principio de inversión de dependencias
• El diagrama de clases anterior muestra una clase Copiar que contiene dos clases abstractas, una clase abstracta Lector y otra clase abstracta Escritor. De esta forma se tiene un ciclo trivial en el que la clase Copiar obtiene un carácter del Lector y se la manda al Escritor, pero de forma totalmente independiente de los módulos de bajo nivel. De esta forma se han invertido las dependencias, la clase Copiar depende de abstracciones, y los lectores y escritores especializados dependen de las mismas abstracciones.
• De esta forma ahora se puede reutilizar la clase Copiar de forma independiente de los dispositivos físicos. Se pueden añadir nuevos tipos de lectores y de escritores sin que la clase Copiar dependa en absoluto de ellos.
Principio de separación de la interfaz
Los clientes no deben ser forzados a depender de interfaces que no u7lizan
Robert C. Mar?n
• Básicamente lo que nos quiere decir este principio es que las clases que implementen una interfaz o una clase abstracta no deberían estar obligadas a tener partes que no van a u?lizar.
• Una interfaz es un contrato que debe cumplir una clase, y tales contratos deben ser específicos, no genéricos; esto nos proporcionará una forma más ágil de definir una única responsabilidad por interfaz -‐ de otra forma, violaríamos además el Principio de Responsabilidad Única
Principio de separación de la interfaz
Los clientes no deben ser forzados a depender de interfaces que no u7lizan
Robert C. Mar?n
• Otras definiciones de este principio son: • La implementación de las abstracciones debe estar en la
medida de lo posible en interfaces y no en clases. • Los clientes no deben estar obligados a implementar y/o a
depender de una interface que luego no usarán. • Es mejor tener interfaces específicos para cada cliente que
uno de propósito general, o lo que es lo mismo, no deberíamos obligar a los clientes a depender de métodos que no u?lizan.
Diseño con una interfaz
(incorrecto)
Diseño con dos interfaces
Cliente C
Cliente D
Interfaz Z Métodos que u?liza
el cliente C
Métodos que u?liza el cliente D
Cliente C
Cliente D
interfaz ZC Métodos que u?liza
el cliente C
Interfaz ZD Métodos que u?liza
el cliente D
Principio de separación de la interfaz
Diseño con una interfaz
• Extensión del principio de responsabilidad única
• Ambigüedad – Aumenta entre los métodos de
interfaces separadas – Aumenta entre la clase cliente hacia los
métodos de las interfaces no u?liza
Cliente C
Cliente D
Interfaz Z Métodos cliente C
Métodos cliente D
Diseño con dos interfaces
Cliente C
Cliente D
Interfaz ZC Métodos cliente C
Interfaz ZD Métodos cliente D
Principio de separación de la interfaz
Análisis
Ejemplo que NO cumple Principio de separación de la interfaz
• Supóngase que se está desarrollando un sistema de seguridad, en el que existe una clase que es Puerta.
• Considérese ahora una puerta que cuando lleve abierta un determinado ?empo haga sonar una alarma, y cuya clase va a denominarse PuertaConAlarma.
• Para conseguir este obje?vo, los objetos de la clase PuertaConAlarma deben comunicarse con los objetos de la clase Temporizador que lleva el control del ?empo.
Ejemplo que NO cumple Principio de separación de la interfaz
Ejemplo que NO cumple Principio de separación de la interfaz
• Se ha forzado a la clase Puerta, y por tanto a la clase PuertaConAlarma, a heredar de ClienteTemporizador. Esta solución es problemá?ca, debido a que ahora Puerta depende de ClienteTemporizador, y no todas las puertas necesitan de este control de ?empo, de hecho la abstracción original de Puerta no contemplaba en absoluto el ?empo. Según este diseño, todas las puertas que se deriven de Puerta heredan innecesariamente de la clase ClienteTemporizador.
Ejemplo que SÍ cumple Principio de separación de la interfaz
• La solución a este problema es aplicar el principio de separación de la interfaz en el diseño, como se puede apreciar en la.
• Aquí se ha hecho uso de la herencia múl?ple, de forma que la clase PuertaConAlarma herede de la clase Puerta y de la clase ClienteTemporizador, de esta forma los clientes de las dos clases bases podrán recibir objetos de PuertaConAlarma, u?lizando el mismo objeto a través de interfaces separadas.
Ejemplo que SÍ cumple Principio de separación de la interfaz
Ley de Deméter
La Ley de Deméter (LoD por sus siglas en ingles Law of Demeter) o Principio de Menos Conocimiento
• En su forma general, la LoD es un caso específico de loose coupling. Esta direc?va fue inventada en la Universidad Northeastern (Boston, Massachuse|s) a finales del año 1987, y puede ser sustancialmente resumida de las siguientes maneras: • Cada unidad debe tener un limitado conocimiento sobre otras unidades y solo
conocer aquellas unidades estrechamente relacionadas a la unidad actual. • Cada unidad debe hablar solo a sus amigos y no hablar con extraños. • Solo hablar con sus amigos inmediatos.
Ley de Deméter
“Habla sólo con tus amigos”
Detallando un poco más, quiere decir que para un método M de una clase O sólo deberían invocarse métodos de los siguientes ?pos de objetos:
• del propio objeto O • de los parámetros que recibe el propio método M • de cualquier objeto que instancie el propio método M • de cualquier atributo de O
Gracias….