¿Por qué los métodos estáticos no deberían ser reemplazables?

13

En las respuestas a esta pregunta, el consenso general fue que los métodos estáticos no están destinados a ser anulados (y, por lo tanto, las funciones estáticas en C # no pueden ser virtuales o abstractas). Sin embargo, este no es solo el caso en C #; Java también prohíbe esto y a C ++ tampoco parece gustarle. Sin embargo, puedo pensar en muchos ejemplos de funciones estáticas que me gustaría anular en una clase secundaria (por ejemplo, métodos de fábrica). Si bien, en teoría, hay formas de evitarlos, ninguno de ellos es limpio o simple.

¿Por qué no deberían anularse las funciones estáticas?

PixelArtDragon
fuente
44
Delphi admite métodos de clase , que son bastante similares a los métodos estáticos y pueden anularse. Se les pasa un selfpuntero que apunta a la clase y no a una instancia de la clase.
CodesInChaos
Una definición de estática es: falta de cambio. Tengo curiosidad por qué hizo una función estática que desea anular.
JeffO
44
@JeffO: Esa definición en inglés de "estático" no tiene absolutamente nada que ver con la semántica única de las funciones miembro estáticas.
Lightness compite con Monica el
55
Su pregunta es "¿por qué los métodos estáticos deben usar un despacho de tipo estático en lugar del despacho virtual único ?" Cuando formula la pregunta de esa manera, creo que se responde a sí misma.
Eric Lippert
1
@EricLippert La pregunta podría expresarse mejor como "¿Por qué C # ofrece métodos estáticos en lugar de métodos de clase?", Pero sigue siendo una pregunta válida.
CodesInChaos

Respuestas:

13

Con los métodos estáticos, no hay ningún objeto para proporcionar un control adecuado del mecanismo de anulación.

El mecanismo de método virtual de clase / instancia normal permite un control finamente ajustado de las anulaciones de la siguiente manera: cada objeto real es una instancia de exactamente una clase. Esa clase determina el comportamiento de las anulaciones; siempre tiene la primera grieta en los métodos virtuales. Luego puede elegir llamar al método padre en el momento adecuado para su implementación. Cada método padre también tiene su turno para invocar su método padre. Esto da como resultado una buena cascada de invocaciones de padres, que cumple una de las nociones de reutilización de código por la que se conoce la orientación a objetos. (Aquí el código de las clases base / súper se está reutilizando de una manera relativamente compleja; otra noción ortogonal de reutilización de código en OOP es simplemente tener múltiples objetos de la misma clase).

Varias subclases pueden reutilizar las clases base, y cada una puede coexistir cómodamente. Cada clase que se usa para instanciar objetos dictando su propio comportamiento, coexistiendo pacíficamente y simultáneamente con los demás. El cliente tiene control sobre qué comportamientos quiere y cuándo elige qué clase usar para crear una instancia de un objeto y pasarlo a otros según lo desee.

(Este no es un mecanismo perfecto, ya que uno siempre puede identificar capacidades que no son compatibles, por supuesto, por eso los patrones como el método de fábrica y la inyección de dependencia se superponen en capas).

Entonces, si tuviéramos que hacer una capacidad de anulación para las estadísticas sin cambiar nada más, tendríamos dificultades para ordenar las anulaciones. Sería difícil definir un contexto limitado para la aplicabilidad de la anulación, por lo que obtendría la anulación globalmente en lugar de más localmente como con los objetos. No hay objeto de instancia para cambiar el comportamiento. Entonces, si alguien invocó el método estático que fue reemplazado por otra clase, ¿la anulación debería tener control o no? Si hay varias anulaciones de este tipo, ¿quién obtiene el control primero? ¿segundo? Con la anulación de objetos de instancia, todas estas preguntas tienen respuestas significativas y bien razonadas, pero con estática no.

Las anulaciones para la estática serían sustancialmente caóticas, y cosas como esta se han hecho antes.

Por ejemplo, Mac OS System 7 y anteriores usaban un mecanismo de parcheo de trampa para extender el sistema al obtener el control de las llamadas al sistema hechas por la aplicación antes que el sistema operativo. Podría pensar en la tabla de parches de llamadas del sistema como una matriz de punteros de función, muy parecida a una vtable para objetos de ejemplo, excepto que era una sola tabla global.

Esto causó un dolor incalculable para los programadores debido a la naturaleza desordenada de los parches de trampa. El que parche la trampa al final básicamente ganó, incluso si no quisieron. Cada parcheador de la captura capturaría el valor de la trampa anterior para una especie de capacidad de llamada principal, que era extremadamente frágil. Eliminar un parche de trampa, por ejemplo, cuando ya no necesita saber sobre una llamada al sistema se consideró una mala forma, ya que realmente no tenía la información necesaria para eliminar su parche (si lo hiciera, también quitaría el parche de cualquier otro parche que hubiera seguido tú).

Esto no quiere decir que sería imposible crear un mecanismo para anular las estadísticas, pero lo que probablemente preferiría hacer es convertir los campos estáticos y los métodos estáticos en campos de instancias y métodos de instancias de metaclases, de modo que el objeto normal entonces se aplicarían técnicas de orientación. Tenga en cuenta que también hay sistemas que hacen esto: CSE 341: clases y metaclases de Smalltalk ; Ver también: ¿Cuál es el equivalente de Smalltalk de la estática de Java?


Estoy tratando de decir que tendrías que hacer un diseño de características de lenguaje serio para que funcione incluso razonablemente bien. Por ejemplo, se hizo un enfoque ingenuo, cojeó, pero fue muy problemático y podría decirse (es decir, argumentaría) que tenía fallas arquitectónicas al proporcionar una abstracción incompleta y difícil de usar.

Cuando haya terminado de diseñar la función de anulaciones estáticas para que funcione bien, es posible que haya inventado alguna forma de metaclases, que es una extensión natural de OOP en / para métodos basados ​​en clases. Entonces, no hay razón para no hacer esto, y algunos idiomas realmente lo hacen. Tal vez es solo un requisito adicional que varios idiomas eligen no hacer.

Erik Eidt
fuente
Si estoy entendiendo correctamente: no se trata de si teóricamente querrías o deberías hacer algo como esto, sino más que programáticamente, ¿su implementación sería difícil y no necesariamente útil?
PixelArtDragon
@ Garan, vea mi apéndice.
Erik Eidt
1
No compro el argumento de pedido; obtienes el método proporcionado por la clase en la que llamaste el método (o la primera superclase que proporciona un método de acuerdo con la linealización elegida por tu idioma).
hobbs
1
@PierreArlaud: Pero this.instanceMethod()tiene una resolución dinámica, por lo que si self.staticMethod()tiene la misma resolución, es decir, dinámica, ya no sería un método estático.
Jörg W Mittag
1
Se debe agregar una semántica superior para los métodos estáticos. ¡Una hipotética metaclases de Java + no necesita agregar nada! Simplemente crea objetos de clases, y los métodos estáticos se convierten en métodos de instancia regular. No tiene que agregar algo al lenguaje, porque solo usa conceptos que ya están allí: objetos, clases y métodos de instancia. Como beneficio adicional, ¡en realidad puedes eliminar los métodos estáticos del lenguaje! ¡Puede agregar una función eliminando un concepto y, por lo tanto, la complejidad del lenguaje!
Jörg W Mittag
9

La anulación depende del despacho virtual: utiliza el tipo de tiempo de ejecución del thisparámetro para decidir a qué método llamar. Un método estático no tiene thisparámetros, por lo que no hay nada que enviar.

Algunos lenguajes, especialmente Delphi y Python, tienen un alcance "intermedio" que permite esto: métodos de clase. Un método de clase no es un método de instancia ordinario, pero tampoco es estático; recibe un selfparámetro (de forma divertida, ambos idiomas llaman thisparámetro self) que es una referencia al tipo de objeto en sí mismo, en lugar de una instancia de ese tipo. Con ese valor, ahora tiene un tipo disponible para hacer despacho virtual.

Desafortunadamente, ni la JVM ni el CLR tienen nada comparable.

Mason Wheeler
fuente
¿Son los lenguajes que usan los selfque tienen clases como objetos reales e instanciados con métodos reemplazables: Smalltalk, por supuesto, Ruby, Dart, Objective C, Self, Python? En comparación con los lenguajes que toman después de C ++, donde las clases no son objetos de primera clase, incluso si hay algún acceso de reflexión.
Jerry101
Para ser claros, ¿estás diciendo que en Python o Delphi puedes anular los métodos de clase?
Erik Eidt
@ErikEidt En Python, casi todo es anulable. En Delphi, un método de clase puede declararse explícitamente virtualy luego anularse en una clase descendiente, al igual que los métodos de instancia.
Mason Wheeler
Entonces, estos lenguajes proporcionan algún tipo de noción de metaclases, ¿no?
Erik Eidt
1
@ErikEidt Python tiene metaclases "verdaderas". En Delphi, una referencia de clase es un tipo de datos especial, no una clase en sí misma.
Mason Wheeler
3

Usted pregunta

¿Por qué no deberían anularse las funciones estáticas?

Pregunto

¿Por qué las funciones que desea anular deben ser estáticas?

Ciertos idiomas te obligan a iniciar el espectáculo en un método estático. Pero después de eso, realmente puede resolver una gran cantidad de problemas sin más métodos estáticos.

A algunas personas les gusta usar métodos estáticos cada vez que no hay dependencia del estado en un objeto. A algunas personas les gusta usar métodos estáticos para la construcción de otros objetos. A algunas personas les gusta evitar los métodos estáticos tanto como sea posible.

Ninguna de estas personas está equivocada.

Si necesita que se pueda anular, simplemente deje de etiquetarlo como estático. Nada se romperá porque tienes un objeto apátrida volando.

naranja confitada
fuente
Si el lenguaje admite estructuras, entonces un tipo de unidad puede ser una estructura de tamaño cero, que se pasa al método de entrada lógica. La creación de ese objeto es un detalle de implementación. Y si empezamos a hablar de los detalles de implementación como la verdad real, entonces creo que también hay que considerar que en una aplicación en el mundo real, un tipo de tamaño cero no tiene por qué ser en realidad una instancia (porque no hay instrucciones son necesarias para la carga o almacenarlo).
Theodoros Chatzigiannakis
Y si está hablando aún más "detrás de escena" (por ejemplo, en el nivel ejecutable), entonces los argumentos del entorno podrían definirse como un tipo en el lenguaje y el punto de entrada "real" del programa podría ser un método de instancia de ese tipo, actuando en cualquier instancia que el cargador del sistema operativo haya pasado al programa.
Theodoros Chatzigiannakis
Creo que depende de cómo lo mires. Por ejemplo, cuando se inicia un programa, el sistema operativo lo carga en la memoria y luego va a la dirección donde reside la implementación individual del programa del punto de entrada binario (por ejemplo, el _start()símbolo en Linux). En ese ejemplo, el ejecutable es el objeto (tiene su propia "tabla de despacho" y todo) y el punto de entrada es la operación despachada dinámicamente. Por lo tanto, el punto de entrada de un programa es inherentemente virtual cuando se ve desde el exterior y también se puede hacer que parezca virtual cuando se ve desde el interior.
Theodoros Chatzigiannakis
1
"Nada se romperá porque tienes un objeto apátrida volando". ++++++
RubberDuck
@TheodorosChatzigiannakis haces una fuerte discusión. si nada más me has convencido, la línea no inspira la línea de pensamiento que pretendía. Realmente estaba tratando de dar permiso a la gente para que ignorara algunas de las prácticas más habituales que asocian ciegamente con métodos estáticos. Entonces lo he actualizado. Pensamientos?
candied_orange
2

¿Por qué los métodos estáticos no deberían ser reemplazables?

No es una cuestión de "debería".

"Anular" significa "despachar dinámicamente ". "Método estático" significa "despacho estático". Si algo es estático, no se puede anular. Si algo se puede anular, no es estático.

Su pregunta es bastante similar a preguntar: "¿Por qué los triciclos no deberían tener cuatro ruedas?" La definición de "triciclo" es que tiene tres ruedas. Si es un triciclo, no puede tener cuatro ruedas, si tiene cuatro ruedas, no puede ser un triciclo. Del mismo modo, la definición de "método estático" es que se envía estáticamente. Si es un método estático, no puede enviarse dinámicamente, si puede enviarse dinámicamente, no puede ser un método estático.

Por supuesto, es perfectamente posible tener métodos de clase que puedan ser anulados. O bien, podría tener un lenguaje como Ruby, donde las clases son objetos como cualquier otro objeto y, por lo tanto, pueden tener métodos de instancia, lo que elimina por completo la necesidad de métodos de clase. (Ruby solo tiene un tipo de métodos: métodos de instancia. No tiene métodos de clase, métodos estáticos, constructores, funciones o procedimientos).

Jörg W Mittag
fuente
1
"Por definición" Bien dicho.
candied_orange
1

así, las funciones estáticas en C # no pueden ser virtuales o abstractas

En C #, siempre llama a miembros estáticos utilizando la clase, por ejemplo BaseClass.StaticMethod(), no baseObject.StaticMethod(). Finalmente, si tiene una ChildClassherencia BaseClassy childObjectuna instancia de ChildClass, no podrá llamar a su método estático desde childObject. Siempre tendrá que usar explícitamente la clase real, por static virtuallo que no tiene sentido.

Lo que puede hacer es redefinir el mismo staticmétodo en su clase secundaria y usar la newpalabra clave.

class BaseClass {
    public static int StaticMethod() { return 1; }
}

class ChildClass {
    public static new int StaticMethod() { return BaseClass.StaticMethod() + 2; }
}

int result;    
var baseObj = new BaseClass();
var childObj = new ChildClass();

result = BaseClass.StaticMethod();
result = baseObj.StaticMethod(); // DOES NOT COMPILE

result = ChildClass.StaticMethod();
result = childObj.StaticMethod(); // DOES NOT COMPILE

Si fuera posible llamar baseObject.StaticMethod(), entonces su pregunta tendría sentido.

usuario276648
fuente