Al leer varias preguntas de Stack Overflow y el código de otros, se cierra el consenso general de cómo diseñar clases. Esto significa que, de forma predeterminada, en Java y C # todo es privado, los campos son finales, algunos métodos son finales y, a veces, las clases son incluso finales .
La idea detrás de esto es ocultar los detalles de implementación, lo cual es una muy buena razón. Sin embargo, con la existencia de la protected
mayoría de los lenguajes OOP y el polimorfismo, esto no funciona.
Cada vez que deseo agregar o cambiar la funcionalidad a una clase, generalmente me veo obstaculizado por el privado y el final colocado en todas partes. Aquí los detalles de implementación son importantes: está tomando la implementación y extendiéndola, sabiendo perfectamente cuáles son las consecuencias. Sin embargo, debido a que no puedo acceder a métodos y campos privados y finales, tengo tres opciones:
- No amplíe la clase, solo evite el problema que conduce a un código que es más complejo
- Copie y pegue toda la clase, eliminando la reutilización del código
- Bifurca el proyecto
Esas no son buenas opciones. ¿Por qué no se protected
usa en proyectos escritos en idiomas que lo admiten? ¿Por qué algunos proyectos prohíben explícitamente heredar de sus clases?
fuente
Respuestas:
Diseñar clases para que funcionen correctamente cuando se extiende, especialmente cuando el programador que hace la extensión no comprende completamente cómo se supone que funciona la clase, requiere un esfuerzo adicional considerable. No puede simplemente tomar todo lo que es privado y hacerlo público (o protegido) y llamarlo "abierto". Si permite que otra persona cambie el valor de una variable, debe considerar cómo afectarán todos los valores posibles a la clase. (¿Qué pasa si establecen la variable en nulo? ¿Una matriz vacía? ¿Un número negativo?) Lo mismo se aplica cuando se permite que otras personas llamen a un método. Se necesita un pensamiento cuidadoso.
Entonces, no es tanto que las clases no deberían estar abiertas, sino que a veces no vale la pena hacerlas abiertas.
Por supuesto, también es posible que los autores de la biblioteca solo fueran flojos. Depende de la biblioteca de la que estés hablando. :-)
fuente
Hacer que todo sea privado por defecto suena duro, pero míralo desde el otro lado: cuando todo es privado por defecto, se supone que hacer algo público (o protegido, que es casi lo mismo) es una elección consciente; es el contrato del autor de la clase con usted, el consumidor, sobre cómo usar la clase. Esto es una conveniencia para ambos: el autor es libre de modificar el funcionamiento interno de la clase, siempre que la interfaz permanezca sin cambios; y usted sabe exactamente en qué partes de la clase puede confiar y cuáles están sujetas a cambios.
La idea subyacente es 'acoplamiento suelto' (también conocido como 'interfaces estrechas'); su valor radica en mantener baja la complejidad. Al reducir la cantidad de formas en que los componentes pueden interactuar, también se reduce la cantidad de dependencia cruzada entre ellos; y la dependencia cruzada es uno de los peores tipos de complejidad cuando se trata de mantenimiento y gestión de cambios.
En bibliotecas bien diseñadas, las clases que valen la pena extender a través de la herencia tienen miembros públicos y protegidos en los lugares adecuados, y ocultan todo lo demás.
fuente
Se supone que todo lo que no es privado existe más o menos con un comportamiento sin cambios en cada versión futura de la clase. De hecho, puede considerarse parte de la API, documentada o no. Por lo tanto, exponer demasiados detalles probablemente esté causando problemas de compatibilidad más adelante.
En cuanto a "final" resp. clases "selladas", un caso típico son las clases inmutables. Muchas partes del marco dependen de que las cadenas sean inmutables. Si la clase String no fuera final, sería fácil (incluso tentador) crear una subclase String mutable que causara todo tipo de errores en muchas otras clases cuando se usa en lugar de la clase String inmutable.
fuente
final
y / oprivate
está bloqueado en su lugar y nunca se puede cambiar .public
miembros;protected
los miembros forman un contrato solo entre la clase que los expone y sus clases derivadas inmediatas; Por el contrario, lospublic
miembros de contratos con todos los consumidores en nombre de todas las posibles clases heredadas presentes y futuras.En OO hay dos formas de agregar funcionalidad al código existente.
El primero es por herencia: tomas una clase y derivas de ella. Sin embargo, la herencia debe usarse con cuidado. Debe usar la herencia pública principalmente cuando tiene una relación isA entre la base y la clase derivada (por ejemplo, un Rectángulo es una Forma). En su lugar, debe evitar la herencia pública para reutilizar una implementación existente. Básicamente, la herencia pública se utiliza para asegurarse de que la clase derivada tenga la misma interfaz que la clase base (o una más grande), para que pueda aplicar el principio de sustitución de Liskov .
El otro método para agregar funcionalidad es usar delegación o composición. Usted escribe su propia clase que usa la clase existente como un objeto interno y le delega parte del trabajo de implementación.
No estoy seguro de cuál fue la idea detrás de todas esas finales en la biblioteca que está tratando de usar. Probablemente los programadores querían asegurarse de que no heredarías una nueva clase de las suyas, porque esa no es la mejor manera de extender su código.
fuente
La mayoría de las respuestas tienen razón: las clases deben estar selladas (final, no superable, etc.) cuando el objeto no está diseñado o destinado a extenderse.
Sin embargo, no consideraría simplemente cerrar todo el diseño de código adecuado. La "O" en SOLID es para "Principio Abierto-Cerrado", indicando que una clase debe estar "cerrada" a la modificación, pero "abierta" a la extensión. La idea es que tienes código en un objeto. Funciona bien Agregar funcionalidad no debería requerir abrir ese código y realizar cambios quirúrgicos que pueden romper el comportamiento de trabajo anterior. En cambio, uno debería poder usar la inyección de herencia o dependencia para "extender" la clase para hacer cosas adicionales.
Por ejemplo, una clase "ConsoleWriter" puede obtener texto y enviarlo a la consola. Lo hace bien. Sin embargo, en cierto caso, TAMBIÉN necesita la misma salida escrita en un archivo. Abrir el código de ConsoleWriter, cambiar su interfaz externa para agregar un parámetro "WriteToFile" a las funciones principales, y colocar el código de escritura de archivo adicional al lado del código de escritura de la consola generalmente se consideraría algo malo.
En cambio, podría hacer una de dos cosas: podría derivar de ConsoleWriter para formar ConsoleAndFileWriter, y extender el método Write () para llamar primero a la implementación base, y luego también escribir en un archivo. O bien, puede extraer una interfaz IWriter de ConsoleWriter y volver a implementar esa interfaz para crear dos nuevas clases; un FileWriter y un "MultiWriter" que puede recibir otros IWriters y "transmitirá" cualquier llamada realizada a sus métodos a todos los escritores dados. El que elijas depende de lo que vas a necesitar; la derivación simple es, bueno, más simple, pero si tiene una previsión tenue de eventualmente necesitar enviar el mensaje a un cliente de red, tres archivos, dos canales con nombre y la consola, continúe y tome la molestia de extraer la interfaz y crear el "Adaptador en Y"; eso'
Ahora, si la función Write () nunca se declaró virtual (o se selló), ahora tiene problemas si no controla el código fuente. A veces incluso si lo haces. En general, esta es una mala posición para estar, y frustra a los usuarios de API de código cerrado o de código limitado cuando esto sucede. Pero, hay una razón legítima por la que Write (), o toda la clase ConsoleWriter, están sellados; puede haber información confidencial en un campo protegido (anulado a su vez desde una clase base para proporcionar dicha información). Puede haber validación insuficiente; cuando escribe una API, debe asumir que los programadores que consumen su código no son más inteligentes o benevolentes que el "usuario final" promedio del sistema final; de esa manera nunca te decepcionarás.
fuente
Creo que probablemente sea de todas las personas que usan un lenguaje oo para la programación en C. Te estoy mirando, Java. Si su martillo tiene la forma de un objeto, pero desea un sistema de procedimiento y / o no entiende realmente las clases, entonces su proyecto deberá bloquearse.
Básicamente, si vas a escribir en un idioma oo, tu proyecto debe seguir el paradigma oo, que incluye la extensión. Por supuesto, si alguien escribió una biblioteca en un paradigma diferente, tal vez no debas extenderla de todos modos.
fuente
Porque no saben nada mejor.
Los autores originales probablemente se están aferrando a un malentendido de principios SÓLIDOS que se originó en un mundo C ++ confuso y complicado.
Espero que noten que los mundos rubí, pitón y perl no tienen los problemas que las respuestas aquí dicen ser la razón del sellado. Tenga en cuenta que es ortogonal a la escritura dinámica. Los modificadores de acceso son fáciles de trabajar en la mayoría de los idiomas (¿todos?). Los campos de C ++ se pueden modificar mediante la conversión a otro tipo (C ++ es más débil). Java y C # pueden usar la reflexión. Los modificadores de acceso hacen las cosas lo suficientemente difíciles como para evitar que lo hagas a menos que REALMENTE lo desees.
Sellar clases y marcar a cualquier miembro como privado viola explícitamente el principio de que las cosas simples deberían ser simples y las cosas difíciles deberían ser posibles. De repente, las cosas que deberían ser simples, no lo son.
Te animo a que intentes comprender el punto de vista de los autores originales. Gran parte de esto proviene de una idea académica de encapsulación que nunca ha demostrado un éxito absoluto en el mundo real. Nunca he visto un marco o biblioteca en la que algunos desarrolladores en algún lugar no quisieran que funcionara de manera ligeramente diferente y no tuvieran buenas razones para cambiarlo. Hay dos posibilidades que pueden haber afectado a los desarrolladores de software originales que sellaron y convirtieron a los miembros en privados.
Creo que en el marco corporativo mundial, # 2 es probablemente el caso. Esos marcos de trabajo de C ++, Java y .NET deben "terminarse" y deben seguir ciertas pautas. Esas pautas generalmente significan tipos sellados a menos que el tipo se haya diseñado explícitamente como parte de una jerarquía de tipos y miembros privados para muchas cosas que podrían ser útiles para el uso de otros ... pero como no se relacionan directamente con esa clase, no están expuestos . Extraer un nuevo tipo sería demasiado costoso para soportar, documentar, etc.
Toda la idea detrás de los modificadores de acceso es que los programadores deben protegerse de sí mismos. "La programación en C es mala porque te permite dispararte en el pie". No es una filosofía con la que estoy de acuerdo como programador.
Prefiero el enfoque de destrozar el nombre de Python. Puede reemplazar fácilmente los elementos privados (mucho más fácil que la reflexión) si lo necesita. Una gran reseña sobre ella está disponible aquí: http://bytebaker.com/2009/03/31/python-properties-vs-java-access-modifiers/
El modificador privado de Ruby en realidad es más como protegido en C # y no tiene un modificador privado como C #. Protegido es un poco diferente. Aquí hay una gran explicación: http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/
Recuerde, su lenguaje estático no tiene que ajustarse a los estilos anticuados del código escrito en ese idioma pasado.
fuente
EDITAR: Creo que las clases deben estar diseñadas para ser abiertas. Con algunas restricciones, pero no debe cerrarse por herencia.
Por qué: "Clases finales" o "clases selladas" me parecen extrañas. Nunca tengo que marcar una de mis propias clases como "final" ("sellada"), porque es posible que tenga que inherente esas clases, más adelante.
Compré / descargué bibliotecas de terceros con clases (la mayoría de los controles visuales), y estoy realmente agradecido de que ninguna de esas bibliotecas usara "final", incluso si es compatible con el lenguaje de programación, en el que estaban codificadas, porque terminé extendiendo esas clases, incluso si he compilado bibliotecas sin el código fuente.
A veces, tengo que lidiar con algunas clases "selladas" ocasionales, y terminé haciendo una nueva "envoltura" de clase contactando a la clase dada, que tiene miembros similares.
Los clasificadores de alcance permiten "sellar" algunas características sin restringir la herencia.
Cuando cambié de Structured Progr. de programación orientada a objetos, que empecé a usar privados miembros de la clase, pero, después de muchos proyectos, terminé usando protegido , y, finalmente, la promoción de esas propiedades o métodos a pública , si necesariamente.
PD No me gusta "final" como palabra clave en C #, prefiero usar "sellado" como Java, o "estéril", siguiendo la metáfora de la herencia. Además, "final" es una palabra ambigua utilizada en varios contextos.
fuente