Entiendo el concepto de un objeto, y como programador de Java siento que el paradigma OO me resulta bastante natural en la práctica.
Sin embargo, recientemente me encontré pensando:
Espere un segundo, ¿cuáles son realmente los beneficios prácticos de usar un objeto sobre el uso de una clase estática (con las prácticas adecuadas de encapsulación y OO)?
Podría pensar en dos beneficios de usar un objeto (ambos son significativos y poderosos):
Polimorfismo: le permite intercambiar funcionalidades de forma dinámica y flexible durante el tiempo de ejecución. También permite agregar nuevas funciones 'partes' y alternativas al sistema fácilmente. Por ejemplo, si hay una
Car
clase diseñada para trabajar conEngine
objetos, y desea agregar un nuevo motor al sistema que el automóvil puede usar, puede crear una nuevaEngine
subclase y simplemente pasar un objeto de esta clase alCar
objeto, sin tener que cambiar nada acercaCar
. Y puede decidir hacerlo durante el tiempo de ejecución.Ser capaz de 'pasar funcionalidad': puede pasar un objeto alrededor del sistema de forma dinámica.
¿Pero hay más ventajas para los objetos sobre las clases estáticas?
A menudo, cuando agrego nuevas 'partes' a un sistema, lo hago creando una nueva clase e instanciando objetos a partir de él.
Pero recientemente, cuando me detuve y lo pensé, me di cuenta de que una clase estática haría lo mismo que un objeto, en muchos de los lugares donde normalmente uso un objeto.
Por ejemplo, estoy trabajando para agregar un mecanismo de guardar / cargar archivo a mi aplicación.
Con un objeto, la línea de código de llamada se verá así: Thing thing = fileLoader.load(file);
Con una clase estática, se vería así: Thing thing = FileLoader.load(file);
¿Cual es la diferencia?
Con bastante frecuencia, no puedo pensar en una razón para crear una instancia de un objeto cuando una clase estática simple y antigua actuaría de la misma manera. Pero en los sistemas OO, las clases estáticas son bastante raras. Entonces debo estar perdiendo algo.
¿Hay más ventajas para los objetos que no sean los dos que enumeré? Por favor explique.
EDITAR: Para aclarar. Sí encuentro objetos muy útiles al intercambiar funcionalidad o al pasar datos. Por ejemplo, escribí una aplicación que inventa melodías. MelodyGenerator
tenían varias subclases que crean melodías de manera diferente, y los objetos de estas clases eran intercambiables (patrón de estrategia).
Las melodías también eran objetos, ya que es útil pasarlas. También los acordes y escalas.
Pero, ¿qué pasa con las partes 'estáticas' del sistema, que no se van a pasar? Por ejemplo, un mecanismo de 'guardar archivo'. ¿Por qué debería implementarlo en un objeto y no en una clase estática?
fuente
Thing
?FileLoader
por uno que lee desde un socket? ¿O un simulacro para probar? ¿O uno que abra un archivo zip?System.Math
en .NET es un ejemplo de algo que tiene mucho más sentido como clase estática: nunca necesitará cambiarlo o burlarse de él y ninguna de las operaciones podría formar parte de una instancia. Realmente no creo que su ejemplo de "ahorro" se ajuste a esa factura.Respuestas:
jajaja suenas como un equipo en el que solía trabajar;)
Java (y probablemente C #) ciertamente admite ese estilo de programación. Y trabajo con personas cuyo primer instinto es: "¡Puedo hacer que sea un método estático!" Pero hay algunos costos sutiles que lo alcanzan con el tiempo.
1) Java es un lenguaje orientado a objetos. Y enloquece a los chicos funcionales, pero realmente se sostiene bastante bien. La idea detrás de OO es agrupar la funcionalidad con el estado para tener pequeñas unidades de datos y funcionalidades que preserven su semántica al ocultar el estado y exponer solo las funciones que tienen sentido en ese contexto.
Al pasar a una clase con solo métodos estáticos, está rompiendo la parte de "estado" de la ecuación. Pero el estado todavía tiene que vivir en alguna parte. Entonces, lo que he visto con el tiempo es que una clase con todos los métodos estáticos comienza a tener listas de parámetros cada vez más complicadas, porque el estado se está moviendo desde la clase hacia las llamadas a funciones.
Después de crear una clase con todos los métodos estáticos, revise y solo examine cuántos de esos métodos tienen un único parámetro común. Es una pista de que ese parámetro debería ser la clase que contiene esas funciones, o ese parámetro debería ser un atributo de una instancia.
2) Las reglas para OO se entienden bastante bien. Después de un tiempo, puede mirar un diseño de clase y ver si cumple con criterios como SOLID. Y después de muchas pruebas de unidad de práctica, desarrolla un buen sentido de lo que hace que una clase sea "adecuada" y "coherente". Pero no hay buenas reglas para una clase con todos los métodos estáticos, y no hay una razón real por la que no debas agrupar todo allí. La clase está abierta en su editor, entonces, ¿qué diablos? Simplemente agregue su nuevo método allí. Después de un tiempo, su aplicación se convierte en una serie de "Objetos de Dios" en competencia, cada uno tratando de dominar el mundo. Nuevamente, refactorizarlos en unidades más pequeñas es muy subjetivo y difícil de determinar si lo hizo bien.
3) Las interfaces son una de las características más poderosas de Java. La herencia de clases ha demostrado ser problemática, pero la programación con interfaces sigue siendo uno de los trucos más poderosos del lenguaje. (ídem C #) Las clases totalmente estáticas no pueden colocarse en ese modelo.
4) Cierra la puerta a importantes técnicas de OO que no puedes aprovechar. Por lo tanto, puede trabajar durante años con solo un martillo en su caja de herramientas, sin darse cuenta de lo mucho más fácil que hubiera sido si hubiera tenido también un destornillador.
4.5) Crea las dependencias de tiempo de compilación más difíciles e irrompibles posibles. Entonces, por ejemplo, si tiene,
FileSystem.saveFile()
entonces no hay forma de cambiar eso, salvo falsificar su JVM en tiempo de ejecución. Lo que significa que cada clase que hace referencia a su clase de función estática tiene una dependencia dura en tiempo de compilación de esa implementación específica, lo que hace que la extensión sea casi imposible y complica enormemente las pruebas. Puede probar la clase estática de forma aislada, pero se vuelve muy difícil probar las clases que se refieren a esa clase de forma aislada.5) Enloquecerás a tus compañeros de trabajo. La mayoría de los profesionales con los que trabajo toman en serio su código y prestan atención al menos a algún nivel de principios de diseño. Dejando a un lado la intención central de un lenguaje, hará que se quiten el pelo porque refactorizarán constantemente el código.
Cuando estoy en un idioma, siempre trato de usar bien un idioma. Entonces, por ejemplo, cuando estoy en Java, utilizo un buen diseño de OO, porque realmente estoy aprovechando el lenguaje para lo que hace. Cuando estoy en Python, mezclo funciones de nivel de módulo con clases ocasionales: solo podía escribir clases en Python, pero luego creo que no estaría usando el lenguaje para lo que es bueno.
Otra táctica es usar mal un lenguaje, y se quejan de todos los problemas que está causando. Pero eso se aplica a casi cualquier tecnología.
La característica clave de Java es administrar la complejidad en unidades pequeñas y comprobables que se unen para que sean fáciles de entender. Java enfatiza definiciones claras de interfaz independientes de la implementación, lo cual es un gran beneficio. Es por eso que (y otros lenguajes OO similares) siguen siendo tan ampliamente utilizados. A pesar de toda la verbosidad y el ritualismo, cuando termino con una gran aplicación Java, siempre siento que las ideas están más separadas en código que mis proyectos en un lenguaje más dinámico.
Sin embargo, es difícil. He visto a personas obtener el error "todo estático", y es un poco difícil disuadirlos. Pero los he visto sentir un gran alivio cuando lo superan.
fuente
Tu preguntaste:
Antes de esta pregunta, enumeró el polimorfismo y se lo pasó como dos beneficios del uso de objetos. Quiero decir que esas son las características del paradigma OO. La encapsulación es otra característica del paradigma OO.
Sin embargo, esos no son los beneficios. Los beneficios son:
Tu dijiste:
Creo que tienes un punto válido allí. En esencia, la programación no es más que la transformación de datos y la creación de efectos secundarios basados en datos. Algunas veces la transformación de datos requiere datos auxiliares. Otras veces, no lo hace.
Cuando se trata de la primera categoría de transformaciones, los datos auxiliares deben pasarse como entrada o almacenarse en algún lugar. Un objeto es el mejor enfoque que las clases estáticas para tales transformaciones. Un objeto puede almacenar los datos auxiliares y usarlos en el momento adecuado.
Para la segunda categoría de transformaciones, una clase estática es tan buena como un Objeto, si no mejor. Las funciones matemáticas son ejemplos clásicos de esta categoría. La mayoría de las funciones estándar de la biblioteca C también se incluyen en esta categoría.
Tu preguntaste:
Si
FileLoader
no lo hace, cada vez , la necesidad de almacenar todos los datos, me gustaría ir con el segundo enfoque. Si existe una pequeña posibilidad de que necesite datos auxiliares para realizar la operación, el primer enfoque es una apuesta más segura.Tu preguntaste:
Enumeré los beneficios (ventajas) de usar el paradigma OO. Espero que se expliquen por sí mismos. Si no, estaré encantado de dar más detalles.
Tu preguntaste:
Este es un ejemplo donde una clase estática simplemente no funcionará. Hay muchas formas de guardar los datos de su aplicación en un archivo:
La única forma de proporcionar tales opciones es crear una interfaz y crear objetos que implementen la interfaz.
En conclusión
Use el enfoque correcto para el problema en cuestión. Es mejor no ser religioso acerca de un enfoque frente al otro.
fuente
Melody
. EjChord
.Improvisations
,SoundSample
Y otros), será económico simplificar la implementación mediante abstracciones.Depende del idioma y el contexto, a veces. Por ejemplo, los
PHP
scripts utilizados para atender solicitudes siempre tienen una solicitud de servicio y una respuesta para generar durante toda la vida útil del script, por lo que los métodos estáticos para actuar sobre la solicitud y generar una respuesta pueden ser apropiados. Pero en un servidor escritoNode.js
, puede haber muchas solicitudes y respuestas diferentes, todo al mismo tiempo. Entonces, la primera pregunta es: ¿está seguro de que la clase estática realmente corresponde a un objeto singleton?En segundo lugar, incluso si tiene singletons, el uso de objetos le permite aprovechar el polimorfismo utilizando técnicas como Dependency_injection y Factory_method_pattern . Esto a menudo se usa en varios patrones de Inversion_of_control , y es útil para crear objetos simulados para pruebas, registros, etc.
Mencionó las ventajas anteriores, así que aquí hay una que no hizo: herencia. Muchos idiomas no tienen la capacidad de anular los métodos estáticos de la misma manera que los métodos de instancia pueden anularse. En general, es mucho más difícil heredar y anular los métodos estáticos.
fuente
Además de la publicación de Rob Y
Siempre que la funcionalidad de tu
load(File file)
pueda separarse claramente de todas las demás funciones que utilices, está bien usar un método / clase estático. Usted externaliza el estado (lo que no es malo) y tampoco obtiene redundancia, ya que puede, por ejemplo, aplicar parcialmente o modificar su función para que no tenga que repetirlo. (eso es lo mismo o similar a usar unfactory
patrón)Sin embargo, tan pronto como dos de estas funciones comiencen a tener un uso común, querrá poder agruparlas de alguna manera. Imagine que no solo tiene una
load
función sino también unahasValidSyntax
función. ¿Qué harás?¿Ves las dos referencias
myfile
aquí? Comienza a repetirse porque su estado externalizado debe pasarse para cada llamada. Rob Y ha descrito cómo internalizas el estado (aquí elfile
) para que puedas hacerlo así:fuente
hasValidSyntax()
yload()
no se les permite la reutilización del estado (el resultado de un archivo parcialmente analizada).Un problema importante al usar clases estáticas es que te obligan a ocultar tus dependencias y te obligan a depender de las implementaciones. En la siguiente firma del constructor, cuáles son sus dependencias:
Bueno, a juzgar por la firma, una persona solo necesita un nombre, ¿verdad? Bueno, no si la implementación es:
Entonces una persona no es solo instanciada. De hecho, extraemos datos de una base de datos, lo que nos da una ruta a un archivo, que luego debe analizarse, y luego los datos reales que queremos deben ser extraídos. Este ejemplo está claramente por encima, pero prueba el punto. Pero espera, ¡puede haber mucho más! Escribo la siguiente prueba:
Sin embargo, esto debería pasar, ¿verdad? Quiero decir, encapsulamos todas esas otras cosas, ¿verdad? Bueno, en realidad explota con una excepción. ¿Por qué? Ah, sí, las dependencias ocultas que no conocíamos tienen un estado global. Las
DBConnection
necesidades inicializadas y conectadas. LasFileLoader
necesidades inicializadas con unFileFormat
objeto (comoXMLFileFormat
oCSVFileFormat
). Nunca he oído hablar de esos? Bueno, ese es el punto. Su código (y su compilador) no pueden decirle que necesita estas cosas porque las llamadas estáticas ocultan esas dependencias. ¿Dije prueba? Quise decir que el nuevo desarrollador junior acaba de enviar algo como esto en su versión más reciente. Después de todo, compilado = funciona, ¿verdad?Además, supongamos que está en un sistema que no tiene una instancia de MySQL en ejecución. O, digamos que está en un sistema Windows pero su
DBConnection
clase solo va a su servidor MySQL en una caja Linux (con rutas de Linux). O bien, supongamos que está en un sistema en el que la ruta devuelta por elDBConnection
no es de lectura / escritura para usted. Eso significa que intentar ejecutar o probar este sistema en cualquiera de estas condiciones fallará, no debido a un error de código, sino debido a un error de diseño que limita la flexibilidad del código y lo vincula a una implementación.Ahora, digamos, queremos registrar cada llamada a la base de datos para una
Person
instancia específica , una que atraviesa una ruta determinada y problemática. Podríamos iniciar sesiónDBConnection
, pero esto registrará todo , poniendo mucho desorden y dificultando distinguir la ruta de código particular que queremos rastrear. Sin embargo, si estamos utilizando inyección de dependencia con unaDBConnection
instancia, podríamos simplemente implementar la interfaz en un decorador (o extender la clase, ya que tenemos ambas opciones disponibles con un objeto). Con una clase estática, no podemos inyectar la dependencia, no podemos implementar una interfaz, no podemos envolverla en un decorador, y no podemos extender la clase. Solo podemos llamarlo directamente, en algún lugar oculto en nuestro código. Por lo tanto, estamos obligados tener una dependencia oculta de una implementación.¿Esto es siempre malo? No necesariamente, pero podría ser mejor invertir su punto de vista y decir "¿Hay alguna buena razón para que esto no sea una instancia?" en lugar de "¿Hay una buena razón de que esto debería ser un ejemplo?" Si realmente puede decir que su código será tan inquebrantable (y sin estado) como
Math.abs()
, tanto en su implementación como en la forma en que se utilizará, entonces puede considerar hacerlo estático. Sin embargo, tener instancias sobre clases estáticas le brinda un mundo de flexibilidad que no siempre es fácil de recuperar después del hecho. También puede darle más claridad sobre la verdadera naturaleza de las dependencias de su código.fuente
new
levantar un montón de objetos en el constructor (lo que generalmente no es aconsejable), pero también puedo inyectarlos. Con las clases estáticas, no hay forma de inyectarlas (no es fácil en Java de todos modos, y esa sería una discusión completamente diferente para los lenguajes que lo permiten), por lo que no hay otra opción. Es por eso que destaco la idea de ser forzado a ocultar dependencias tan fuertemente en mi respuesta.Class
, luego nos aseguramos de que sea la clase correcta) o si está escribiendo pato (nos aseguramos de que responda al método correcto). Si desea hacer lo último, debe volver a evaluar su elección de idioma. Si desea hacer lo primero, las instancias funcionan bien y están integradas.Solo mis dos centavos.
Para su pregunta:
Puede preguntarse si el mecanismo de la parte del sistema que reproduce sonidos o la parte del sistema que guarda los datos en un archivo CAMBIARÁ . Si su respuesta es sí, eso indica que debe abstraerlos usando
abstract class
/interface
. Usted puede preguntar, ¿cómo puedo saber las cosas futuras? Definitivamente, no podemos. Entonces, si la cosa no tiene estado, puede usar 'clase estática' comojava.lang.Math
, de lo contrario, usar un enfoque orientado a objetos.fuente