2011-12-28 17 views
5

Wikipedia define virtual methods como:Los métodos en paradigmas orientados a objetos pueden ser reemplazados por los métodos con la misma firma en las clases heredadas. Las variables sin embargo no pueden. ¿Por qué?

En la programación orientada a objetos, una función virtual o un método virtual es una función o método cuyo comportamiento puede ser anulado dentro de una clase que hereda por una función con la misma firma [para proporcionar un comportamiento polimórfico].

De acuerdo con la definición, todos los métodos no estáticos en Java es por defecto, excepto virtuales métodos finales y privadas. El método que no puede heredarse para el comportamiento polimórfico es , no, un método virtual.

Los métodos estáticos en Java nunca pueden anularse; por lo tanto, no tiene sentido declarar un método estático como definitivo en Java porque los métodos estáticos en sí mismos se comportan como los métodos finales. Simplemente pueden ser ocultos en las subclases por los métodos con la misma firma. Evidentemente, esto se debe a que los métodos estáticos nunca pueden tener un comportamiento polimórfico: un método que se anula debe lograr el polimorfismo, que no es el caso con los métodos estáticos.

Del párrafo anterior, se puede llegar a una conclusión importante. Todos los métodos en C++ son por defecto estáticos porque ningún método en C++ puede comportarse polimórficamente hasta que se declaren explícitamente como virtuales en la superclase. Por el contrario, todos los métodos en Java excepto los métodos finales, estáticos y privados son por defecto virtuales porque tienen comportamiento polimórfico por defecto (no es necesario declarar explícitamente métodos como virtuales en Java y, en consecuencia, Java no tiene palabras clave como "virtual")

Ahora, demostremos que las variables de instancia (también estáticas) no pueden comportarse polimórficamente a partir del siguiente ejemplo simple en Java.

class Super 
{ 
    public int a=5; 
    public int show() 
    { 
     System.out.print("Super method called a = "); 
     return a; 
    } 
} 

final class Child extends Super 
{ 
    public int a=6; 

    @Override 
    public int show() 
    { 
     System.out.print("Child method called a = "); 
     return a; 
    } 
} 

final public class Main 
{ 
    public static void main(String...args) 
    { 
     Super s = new Child(); 
     Child c = new Child(); 

     System.out.println("s.a = "+s.a); 
     System.out.println("c.a = "+c.a);   

     System.out.println(s.show()); 
     System.out.println(c.show()); 
    } 
} 

La salida producida por el fragmento de código anterior es como sigue.

 
s.a = 5 
c.a = 6 
Child method called a = 6 
Child method called a = 6 

En este ejemplo, ambas llamadas s.show() y c.show() hecho con el método show() a través de las variables de tipo Super y de tipo Child, respectivamente, invoque el método show() en la clase Child. Esto significa que el método show() en la clase Child anula el método show() en la clase Super porque ambos tienen la firma idéntica.

Esto, sin embargo, no se puede aplicar a la variable de instancia a declarada en ambas clases.En este caso, s.a se referiría a a en la clase Super y visualización 5 y c.a se referiría a a en la clase Child y mostrar 6 significa que a en los Child clase sólo cueros (y no altera temporalmente como le ocurrió a no estático métodos) a en la clase Super.

Después de esta larga discusión, solo hay una pregunta. ¿Por qué las variables de instancia (y el resto también) no se anulan? ¿Cuáles fueron las razones especiales para implementar tal mecanismo? ¿Habría habido ventajas o desventajas si hubieran sido anuladas?

+1

Los métodos no virtuales de C++ no son estáticos. son métodos de instancia que no pueden ser anulados. Son similares a los métodos finales en Java: métodos de instancia que no se pueden anular. –

+0

'De acuerdo con el párrafo anterior, se puede llegar a una conclusión importante. Todos los métodos en C++ (también en C) son por defecto estáticos porque ningún método en C++ puede comportarse polimórficamente hasta que se declaren explícitamente como virtuales en la base': un método estático no puede acceder al objeto [no 'this' para estos métodos ] mientras que una función no virtual [predeterminada] en C++ puede – amit

+1

Creo que la palabra "anulada" no es aplicable a las variables. Usted anula el comportamiento, pero no el estado. Se puede acceder a las variables desde la subclase, por lo que no es necesario "anular" (puede cambiar su valor y tiene el mismo nombre). También hay algo llamado sombreado, por lo que si redeclara otra variable con el mismo nombre, es otra variable diferente en tiempo de ejecución. –

Respuesta

3

Creo que el objetivo de la anulación está cambiando la funcionalidad sin cambiar la firma. Esto es relevante solo para los métodos: el método puede tener la misma firma pero tener un comportamiento diferente. Los campos solo tienen "firma" que también está limitada a su tipo y nombre. No tienen comportamiento, por lo que no se puede anular nada.

Otra razón para no poder "anular" los campos es que los campos son típicamente privados (o deben ser privados), por lo que en realidad son los detalles sangrientos de la implementación de la clase. Sin embargo, los métodos representan la interfaz externa de la clase. Esta interfaz externa debe admitir polimorfo para facilitar el cambio de la funcionalidad de los módulos sin cambiar las relaciones entre los módulos.

+0

De acuerdo con el Principio de sustitución de Liskov, las firmas no tienen que ser las mismas: el tipo de retorno debe ser covariante (el mismo tipo o derivado) y los argumentos deben ser contravariantes (mismo o basal) en la firma de los métodos primordiales. Del mismo modo, el tipo de campo con el mismo nombre en un descendiente debe ser covariante, por lo que puede tener sentido "anular" los campos. – outis

+0

@outis: El tipo de campo es análogo * both * al tipo de devolución de un getter ('Number x = this.x' es análogo a' Number x = this.getX() ') * y * a un parámetro de setter -type ('this.x = (Number) x' es análogo a' this.setX ((Number) x) '). Entonces tiene que ser invariante. (Y esto es cierto incluso si el campo es 'final', ya que el constructor de la superclase es el que lo configurará en ese caso). – ruakh

2

Hay otro uso del término "estático" al referirse a la resolución de nombre: la resolución estática es cuando el tipo declarado de una variable se usa para resolver el nombre, dinámico cuando se usa el tipo de datos almacenados en la variable. La resolución estática se puede hacer en tiempo de compilación, mientras que la resolución dinámica debe hacerse en tiempo de ejecución. La resolución de miembros virtuales (que es una forma de resolución dinámica) requiere un nivel extra de indirección, una búsqueda de tablas. Permitir campos "virtuales" incurriría en un costo de tiempo de ejecución.

Una vez que se elimina la resolución estática para todos los miembros, no tiene mucho sentido tener variables tipadas estáticamente. En ese punto, también puede tener un lenguaje de tipado dinámico, como Python y la multitud de variantes de Lisp. Cuando descarta tipos estáticos, ya no tiene sentido tratar de hablar de campos secundarios que no anulan (ni anulan) los campos principales, porque ya no existe un tipo de variable estática para sugerir lo contrario.

class Super(object): 
    a=5 
    def show(self): 
     print("Super method called a = ", end='') 
     return self.a 

class Child(Super): 
    a=6 
    def show(self): 
     print("Child method called a = ", end='') 
     return self.a 

# there's no indication that 's' is supposed to be a Super... 
s = Child(); 
c = Child(); 

# so it's no surprise that s.a is Child.a 
print("s.a =", s.a) 
# result: "s.a = 6" 
print("c.a =", c.a) 
# result: "c.a = 6" 
print(s.show()) 
# result: "Child method called a = 6" 
print(c.show()) 
# result: "Child method called a = 6" 

Los ceceos comunes, deben tener tanto declaraciones de tipo como resolución dinámica.

(defgeneric show (o)) 

(defclass super() 
    ((a :accessor super-a 
    :initform 5 
    :initarg :a)) 
) 

(defmethod show ((o super)) 
    (list "Super method called a = " 
     (slot-value o 'a)) 
) 

(defclass child (super) 
    ((a :accessor child-a 
    :initform 6 
    :initarg :a)) 
) 

(defmethod show ((o child)) 
    (list "Child method called a = " 
     (slot-value o 'a)) 
) 

(defun test (s c) 
    (declare (type super s)) 
    (declare (type child c)) 
    (list (list "s.a =" (slot-value s 'a)) 
     (list "c.a =" (slot-value c 'a)) 
     (show s) 
     (show c) 
     ) 
) 

(test (make-instance 'child) (make-instance 'child)) 
;; result: '(("s.a =" 6) ("c.a =" 6) ("Child method called a = " 6) ("Child method called a = " 6)) 

En programación orientada a objetos puro (según algunos), los objetos no deberían tener campos públicos, sólo los miembros del público, por lo que los campos se resuelven de forma estática no es un problema.

Todos los métodos en C++ (también en C) son por estática por defecto [...]

No, porque static significa más que "no puede ser anulado" en Java, y don no significa eso en absoluto en C++. En ambos, la propiedad esencial de los métodos estáticos es que son métodos de clase, accedidos a través de una clase, en lugar de una instancia (aunque Java les permite acceder a cualquiera de ellos).

2

Mire el mundo C#. El uso de variables de instancias públicas está estrictamente desaconsejado, vea StyleCop. La única forma recomendada es usar propiedades que pueden ser anuladas.

Creo que este es el enfoque correcto. Simplemente no use variables de instancia públicas y use analogías para las propiedades.

En cuanto a por qué es así en Java, creo que algún tipo de C++ como el estilo que se adoptó en ese momento de punto cuando Java se diseñó como una forma de facilitar la transición.

+0

C# es como Java. los campos públicos también se consideran un anuncio práctico en Java. C# solo tiene azúcar sintáctico para acceder a las propiedades usando una sintaxis de acceso de campo, y Java usa getters y setters, pero son lo mismo. –

+0

Claro.Para ser sincero, espero una mejor sintaxis para getters y setters en Java en la próxima versión. –

+0

@Jiri Pik La sintaxis actual está bien. Si no te gusta, haz que los campos sean públicos. Creo que la sintaxis de C# para las propiedades es horrenda. –

1

Hay poco para anular en un campo de instancia. Solo tendrías que reemplazarlo por lo mismo: el mismo nombre, el mismo tipo. Puede reemplazar una matriz con una de diferente longitud, pero deberá examinar de cerca todas las superclases para asegurarse de que no están haciendo suposiciones sobre la longitud.

Los campos no privados son, como notaron otros, generalmente algo que debe evitarse. A veces, una clase tiene más una estructura C, con campos públicos y sin métodos. Esta es una práctica perfectamente buena, pero aún no tiene sentido anular nada.

Reemplazar solo se refiere a métodos, independientemente del idioma. Java no tiene propiedades como C#, que son clases (limitadas) en sí mismas con los métodos que se deben cambiar. Los campos pueden verse y actuar como propiedades, pero no son lo mismo en absoluto.

Cuestiones relacionadas