Estoy un poco confundido acerca de cómo se puede aplicar el principio de Open Closed en la vida real. Requisito en cualquier negocio cambia con el tiempo. De acuerdo con el principio Open-Closed, debe extender la clase modificando la clase existente. Para mí, cada vez que extender una clase no parece ser práctico para cumplir con el requisito. Permítanme dar un ejemplo con el sistema de reserva de trenes.
En el sistema de reserva de trenes habrá un objeto Boleto. Podría haber diferentes tipos de boletos Boleto regular, Boleto de concesión, etc. Boleto es clase abstracta y Boletos regulares y Boletos de concesión son clases concretas. Como todos los tickets tendrán el método PrintTicket que es común, por lo tanto, está escrito en Ticket, que es la clase abstracta base. Digamos que esto funcionó bien durante unos meses. Ahora entra un nuevo requisito, que establece cambiar el formato del ticket. Es posible que se agreguen algunos campos más en el ticket impreso o que se cambie el formato. Para cumplir este requisito, tengo las siguientes opciones
- Modifique el método PrintTicket () en la clase abstracta Ticket. Pero esto violará el principio de Abierto-Cerrado.
- Anule el método PrintTicket () en las clases secundarias, pero esto duplicará la lógica de impresión que es una violación del Principio DRY (No se repita).
Entonces las preguntas son
- ¿Cómo puedo satisfacer los requisitos comerciales anteriores sin violar el principio Abierto / Cerrado?
- ¿Cuándo se supone que la clase está cerrada por modificación? ¿Cuáles son los criterios para considerar que la clase está cerrada por modificación? Es después de la implementación inicial de la clase o puede ser después de la primera implementación en producción o puede ser otra cosa.
fuente
Respuestas:
Esto depende de la forma en que
PrintTicket
se implementa. Si el método necesita considerar la información de las subclases, debe proporcionar una forma para que proporcionen información adicional.Además, puede anular sin repetir su código también. Por ejemplo, si llama a su método de clase base desde la implementación, evita la repetición:
El patrón de Método de plantilla proporciona una tercera opción: implementar
PrintTicket
en la clase base y confiar en clases derivadas para proporcionar detalles adicionales según sea necesario.Aquí hay un ejemplo usando su jerarquía de clases:
No es la clase la que debe cerrarse a la modificación, sino más bien la interfaz de esa clase (me refiero a "interfaz" en sentido amplio, es decir, una colección de métodos y propiedades y su comportamiento, no la construcción del lenguaje). La clase oculta su implementación, por lo que los propietarios de la clase pueden modificarla en cualquier momento, siempre y cuando su comportamiento externo visible permanezca sin cambios.
La interfaz de la clase debe permanecer cerrada después de la primera vez que la publica para uso externo. Las clases de uso interno permanecen abiertas para refactorizar para siempre, porque puede encontrar y corregir todos sus usos.
Además de los casos más simples, no es práctico esperar que su jerarquía de clases cubra todos los escenarios de uso posibles después de un cierto número de iteraciones de refactorización. Los requisitos adicionales que requieren métodos completamente nuevos en la clase base aparecen regularmente, por lo que su clase permanece abierta a modificaciones por siempre.
fuente
TicketPrinter
clase que se pasa al constructor.Comencemos con una línea simple de principio abierto-cerrado
"Open for extension Closed for modification"
. Considere su problema. Yo diseñaría las clases para que el Ticket base sea responsable de imprimir una información de Ticket común y otros tipos de Ticket sean responsables de imprimir su propio formato sobre la impresión base.De esta manera, si aplica cualquier tipo de boleto nuevo que se introduciría en el sistema, implementará su propia función de impresión además de la información básica de impresión del boleto. Base Ticket ahora está cerrado para modificación pero abierto para extensión al proporcionar un método adicional para sus tipos derivados.
fuente
El problema no es que esté violando el principio Abierto / Cerrado, sino que está violando el Principio de responsabilidad única.
Este es literalmente un ejemplo de un libro escolar de un problema de SRP, o como dice Wikipedia :
Las diferentes clases de Ticket pueden cambiar porque les agrega nueva información. La impresión del ticket puede cambiar porque necesita un nuevo diseño, por lo tanto, debe tener una clase TicketPrinter que acepte tickets como entrada.
Sus clases Ticket pueden implementar diferentes interfaces para proporcionar diferentes tipos de datos o proporcionar algún tipo de plantilla de datos.
fuente
Esto no viola el principal abierto-cerrado. El código cambia todo el tiempo, por eso SOLID es importante, por lo que el código sigue siendo mantenible, flexible y cambiante. No hay nada de malo en cambiar el código.
OCP trata más sobre las clases externas que no pueden alterar la funcionalidad prevista de una clase.
Por ejemplo, tengo una clase
A
que toma una Cadena en el constructor y verifica que esta Cadena tenga un cierto formato llamando a un método para verificar la cadena. Este método de verificación también debe ser utilizado por los clientes deA
, por lo que en la implementación ingenua, está hechopublic
.Ahora puedo 'romper' esta clase heredando de ella y anulando el método de verificación para que siempre regrese
true
. Debido al polimorfismo, todavía puedo usar mi subclase en cualquier lugar donde pueda usarA
, posiblemente creando un comportamiento no deseado en mi programa.Esto parece silencioso, algo malicioso que hacer, pero en una base de código más compleja, podría hacerse como un "error honesto". Para cumplir con OCP, la clase
A
debe diseñarse de tal manera que no sea posible cometer este error.Podría hacer esto, por ejemplo, haciendo el método de verificación
final
.fuente
La herencia no es el único mecanismo que se puede utilizar para cumplir con los requisitos de OCP, y en la mayoría de los casos diría que rara vez es el mejor, por lo general, hay mejores formas, siempre que planifique con anticipación un poco para considerar el tipo de cambios que probablemente necesite.
En este caso, argumentaría que si espera obtener cambios frecuentes de formato en su sistema de impresión de tickets (y eso me parece una apuesta bastante segura), usar un sistema de plantillas tendría mucho sentido. Ahora, no necesitamos tocar el código en absoluto: todo lo que tenemos que hacer para cumplir con esta solicitud es cambiar una plantilla (siempre que su sistema de plantillas pueda acceder a todos los datos que se requieren, de todos modos).
En realidad, un sistema nunca puede cerrarse por completo contra modificaciones, e incluso cerrarlo requeriría una gran cantidad de esfuerzo desperdiciado, por lo que debe hacer juicios sobre qué tipo de cambios son probables y estructurar su código en consecuencia.
fuente