¿Por qué es tan malo usar 'final' en una clase?

34

Estoy refactorizando un sitio web PHP OOP heredado.

Estoy tan tentado de comenzar a usar 'final' en las clases para " make it explicit that the class is currently not extended by anything". Esto podría ahorrar mucho tiempo si vengo a una clase y me pregunto si puedo cambiar el nombre / eliminar / modificar una protectedpropiedad o método. Si realmente quiero extender una clase, puedo eliminar la palabra clave final para desbloquearla y extenderla.

Es decir, si vengo a una clase que no tiene clases para niños, puedo registrar ese conocimiento marcando la clase como final. La próxima vez que vaya, no tendría que volver a buscar la base de código para ver si tiene hijos. De este modo se ahorra tiempo durante las refactorizaciones.

Todo parece una idea sensata para ahorrar tiempo ... pero a menudo he leído que las clases solo deben hacerse 'finales' en ocasiones raras / especiales.

Tal vez arruina la creación de objetos simulados o tiene otros efectos secundarios en los que no estoy pensando.

¿Qué me estoy perdiendo?

JW01
fuente
3
Si tienes el control total de tu código, entonces no es terrible, supongo, sino un poco en el lado del TOC. ¿Qué pasa si se pierde una clase (por ejemplo, no tiene hijos pero no es final)? Es mejor dejar que una herramienta escanee el código y le diga si alguna clase tiene hijos. Tan pronto como empaque esto como una biblioteca y se lo dé a otra persona, lidiar con 'final' se convierte en un dolor.
Trabajo
Por ejemplo, MbUnit es un marco abierto para pruebas de unidades .Net, y MsTest no lo es (sorpresa sorpresa). Como resultado, DEBE desembolsar 5k a MSFT solo porque quiere ejecutar algunas pruebas de la manera MSFT. Otros corredores de prueba no pueden ejecutar el dll de prueba creado con MsTest, todo debido a las clases finales que utilizó MSFT.
Trabajo
3
Algunas publicaciones de blog sobre el tema: Clases finales: abierto para extensión, cerrado por herencia (mayo de 2014; por Mathias Verraes)
hakre
Buen articulo. Dice mis pensamientos sobre esto. No sabía sobre esas anotaciones, así que fue útil. Aclamaciones.
JW01
"PHP 5 introduce la palabra clave final, que evita que las clases secundarias anulen un método al prefijar la definición con final. Si la clase en sí misma se está definiendo final, entonces no se puede extender". php.net/manual/en/language.oop5.final.php No está nada mal.
Negro

Respuestas:

54

A menudo he leído que las clases solo deben hacerse 'finales' en raras ocasiones especiales.

Quien escribió eso está mal. Use finalgenerosamente, no hay nada de malo en eso. Documenta que una clase no se diseñó teniendo en cuenta la herencia, y esto suele ser cierto para todas las clases de forma predeterminada: diseñar una clase que se pueda heredar de manera significativa toma más que simplemente eliminar un finalespecificador; Se necesita mucho cuidado.

Por lo tanto, el uso finalpor defecto no es malo. De hecho, muchas personas proponen que este sea el valor predeterminado, por ejemplo, Jon Skeet .

Tal vez arruina la creación de objetos simulados ...

De hecho, esto es una advertencia, pero siempre puede recurrir a las interfaces si necesita burlarse de sus clases. Esto es ciertamente superior a hacer que todas las clases estén abiertas a la herencia solo con el propósito de burlarse.

Konrad Rudolph
fuente
1
1+: Ya que arruina la burla; considere crear interfaces o clases abstractas para cada clase "final".
Spoike
Bueno, eso pone una llave en las obras. Esta llegada tardía contradice todas las otras respuestas. Ahora no sé qué creer: o. (Desmarcó mi respuesta aceptada y retrasé la decisión durante el nuevo juicio)
JW01
1
Puede considerar la API de Java en sí y con qué frecuencia encontrará el uso de la palabra clave 'final'. De hecho, no se usa con mucha frecuencia. Se utiliza en casos en los que el rendimiento puede verse afectado debido a la vinculación dinámica que tiene lugar cuando se llaman métodos "virtuales" (en Java todos los métodos son virtuales si no se declaran como 'finales' o 'privados'), o cuando se introducen valores personalizados Las subclases de una clase API de Java pueden generar problemas de coherencia, como la subclase 'java.lang.String'. Una cadena de Java es un objeto de valor por definición y no debe cambiar su valor más adelante. Una subclase podría romper esta regla.
Jonny Dee
55
@ Jonny La mayoría de la API de Java está mal diseñada. Recuerde que la mayor parte tiene más de diez años y, mientras tanto, se han desarrollado muchas mejores prácticas. Pero no confíe en mi palabra: los propios ingenieros de Java lo dicen, en primer lugar Josh Bloch, que ha escrito en detalle sobre esto, por ejemplo, en Effective Java . Tenga la seguridad de que si la API de Java se desarrollara hoy, se vería muy diferente y finaldesempeñaría un papel mucho más importante.
Konrad Rudolph
1
Todavía no estoy convencido de que usar 'final' con más frecuencia se haya convertido en una práctica recomendada. En mi opinión, el 'final' realmente debería usarse solo si los requisitos de rendimiento lo dictan, o si debe asegurarse de que las subclases no puedan romper la consistencia. En todos los demás casos, la documentación necesaria debe incluirse, bueno, en la documentación (que debe escribirse de todos modos), y no en el código fuente como palabras clave que imponen ciertos patrones de uso. Para mí, eso es como jugar a ser dios. Usar anotaciones sería una mejor solución. Se podría agregar una anotación '@final' y el compilador podría emitir una advertencia.
Jonny Dee
8

Si desea dejarse una nota a sí mismo de que una clase no tiene subclases, hágalo y utilice un comentario, para eso están. La palabra clave "final" no es un comentario, y usar palabras clave de idioma solo para indicarle algo (y solo usted sabrá lo que significa) es una mala idea.

Jeremy
fuente
17
¡Esto es completamente falso! “Por medio de las palabras reservadas sólo para señal de algo que usted [...] es una mala idea.” - Por el contrario, esto es exactamente lo que las palabras reservadas están ahí para. Su advertencia tentativa, "y solo usted sabría lo que significa", por supuesto, es correcta pero no se aplica aquí; el OP está usando finalcomo se esperaba. No hay nada de malo en eso. Y usar una función de lenguaje para imponer una restricción siempre es superior a usar un comentario.
Konrad Rudolph
8
@Konrad: Excepto que finaldebe indicar "No se debe crear ninguna subclase de esta clase" (por razones legales o algo así), no "esta clase actualmente no tiene hijos, por lo que todavía estoy seguro de meterme con sus miembros protegidos". La intención finales la antítesis misma de "libremente editable", ¡y una finalclase ni siquiera debería tener protectedmiembros!
SF.
3
@Konrad Rudolph No es minúsculo en absoluto. Si otras personas luego desean extender sus clases, hay una gran diferencia entre un comentario que dice "no extendido" y una palabra clave que dice "no se puede extender".
Jeremy
2
@Konrad Rudolph Eso suena como una gran opinión para hacer una pregunta sobre el diseño del lenguaje OOP. Pero sabemos cómo funciona PHP, y toma el otro enfoque de permitir la extensión a menos que esté sellado. Pretender que está bien combinar palabras clave porque "así es como debe ser" es solo defensa.
Jeremy
3
@ Jeremy, no estoy combinando palabras clave. La palabra clave finalsignifica, "esta clase no se extenderá [por ahora]". Nada más, nada menos. Si PHP fue diseñado con esta filosofía en mente es irrelevante: después de todo, tiene la finalpalabra clave. En segundo lugar, argumentar desde el diseño de PHP está destinado a fallar, dado lo patchworky y en general mal diseñado PHP.
Konrad Rudolph
5

Hay un buen artículo sobre "Cuándo declarar las clases finales" . Algunas citas de ella:

TL; DR: haga sus clases siempre final, si implementan una interfaz, y no se definen otros métodos públicos

¿Por qué tengo que usar final?

  1. Prevenir la herencia masiva de la cadena de la fatalidad
  2. Composición alentadora
  3. Forzar al desarrollador a pensar en la API pública del usuario
  4. Forzar al desarrollador a reducir la API pública de un objeto
  5. Una finalclase siempre puede hacerse extensible
  6. extends rompe la encapsulación
  7. No necesitas esa flexibilidad
  8. Eres libre de cambiar el código

Cuando evitar final:

Las clases finales solo funcionan de manera efectiva bajo los siguientes supuestos:

  1. Hay una abstracción (interfaz) que implementa la clase final
  2. Toda la API pública de la clase final es parte de esa interfaz

Si falta una de estas dos condiciones previas, es probable que llegue a un punto en el tiempo en que hará que la clase sea extensible, ya que su código no se basa realmente en abstracciones.

PD: ¡Gracias a @ocramius por la excelente lectura!

Nikita U.
fuente
5

"final" para una clase significa: ¿Quieres una subclase? Adelante, borra la subclase "final" tanto como quieras, pero no te quejes si no funciona. Estás sólo en esto.

Cuando una clase puede subclasificarse, el comportamiento en el que otros confían debe describirse en términos abstractos que las subclases obedecen. Las personas que llaman deben escribirse para esperar alguna variabilidad. La documentación debe ser escrita cuidadosamente; no puede decirle a la gente que "mire el código fuente" porque el código fuente aún no está allí. Eso es todo esfuerzo. Si no espero que una clase se subclasifique, es un esfuerzo innecesario. "final" dice claramente que este esfuerzo no se ha hecho y da una advertencia justa.

gnasher729
fuente
-1

Una cosa que quizás no haya pensado es el hecho de que CUALQUIER cambio de una clase significa que debe someterse a nuevas pruebas de control de calidad.

No marques las cosas como finales a menos que realmente lo digas en serio.


fuente
Gracias. ¿Podría explicar eso más? ¿Quiere decir que si marco una clase como final(un cambio), entonces solo tengo que volver a probarla?
JW01
44
Yo diría esto con más fuerza. No marque las cosas como finales a menos que no se pueda hacer que una extensión funcione debido al diseño de la clase.
S.Lott
Gracias S.Lott: estoy tratando de entender cómo el mal uso de final afecta el código / proyecto. ¿Has experimentado alguna mala historia de terror que te haya llevado a ser tan dogmático sobre el uso moderado de final? ¿Es esta experiencia de primera mano?
JW01
1
@ JW01: una finalclase tiene un caso de uso primario. Tiene clases polimórficas que no desea ampliar porque una subclase puede romper el polimorfismo. No lo use a finalmenos que deba evitar la creación de subclases. Aparte de eso, es inútil.
S.Lott
@ JW01, en un entorno de producción es probable que no se PERMITE para eliminar una final, porque este no es el código probado el cliente y aprobado. Sin embargo, es posible que se le permita agregar código adicional (para las pruebas), pero es posible que no pueda hacer lo que quiera porque no puede extender su clase.
-1

El uso de 'final' le quita la libertad a otros que desean usar su código.

Si el código que escribe es solo para usted y nunca se dará a conocer al público ni a un cliente, puede hacer con su código lo que desee, por supuesto. De lo contrario, evita que otros construyan sobre su código. Demasiado a menudo tuve que trabajar con una API que hubiera sido fácil de ampliar para mis necesidades, pero luego me vi obstaculizado por 'final'.

Además, a menudo hay un código que mejor no se debe hacer private, pero protected. Claro, privatesignifica "encapsulación" y ocultar cosas consideradas como detalles de implementación. Pero como programador de API, también podría documentar el hecho de que el método xyzse considera un detalle de implementación y, por lo tanto, puede modificarse / eliminarse en una versión futura. Por lo tanto, todos los que confíen en dicho código a pesar de la advertencia lo hacen bajo su propio riesgo. Pero en realidad puede hacerlo y reutilizar el código (con suerte ya probado) y encontrar una solución más rápido.

Por supuesto, si la implementación de la API es de código abierto, uno solo puede eliminar el 'final' o hacer que los métodos estén 'protegidos', pero luego ha cambiado el código y necesita rastrear sus cambios en forma de parches.

Sin embargo, si la implementación es de código cerrado, se queda atrás para encontrar una solución o, en el peor de los casos, cambiar a otra API con menos restricciones con respecto a las posibilidades de personalización / extensión.

Tenga en cuenta que no encuentro que 'final' o 'privado' sean malos, pero creo que se usan con demasiada frecuencia porque el programador no pensó en su código en términos de reutilización y extensión de código.

Jonny Dee
fuente
2
"La herencia no es reutilización de código".
quant_dev
2
Ok, si insistes en una definición perfecta tienes razón. La herencia expresa una relación 'es-a' y es este hecho el que permite el polimorfismo y proporciona una forma de extender una API. Pero en la práctica, la herencia se usa muy a menudo para reutilizar el código. Este es especialmente el caso de la herencia múltiple. Y tan pronto como llame al código de una superclase, en realidad está reutilizando el código.
Jonny Dee
@quant_dev: La reutilización en OOP significa, en primer lugar, la polimorfia. Y la herencia es un punto crítico para el comportamiento polimórfico (compartir la interfaz).
Falcon
@Falcon Sharing interface! = Código compartido. Al menos no en el sentido habitual.
quant_dev
3
@quant_dev: obtienes la reutilización en el sentido OOP incorrecto. Significa que un único método puede funcionar en muchos tipos de datos, gracias al uso compartido de la interfaz. Esa es una parte importante de la reutilización del código OOP. Y la herencia automáticamente nos permite compartir interfaces, lo que mejora enormemente la reutilización del código. Es por eso que hablamos de reutilización en OOP. Solo crear bibliotecas con rutinas es casi tan antiguo como la programación misma y se puede hacer con casi todos los idiomas, es común. La reutilización de código en OOP es más que eso.
Falcon