¿Debería usar alguna vez variables miembro protegidas?

97

¿Debería usar alguna vez variables miembro protegidas? ¿Cuáles son las ventajas y qué problemas puede causar esto?

John Channing
fuente

Respuestas:

73

¿Debería usar alguna vez variables miembro protegidas?

Depende de lo quisquilloso que seas para esconderte.

  • Si no desea ninguna filtración del estado interno, entonces declarar todas sus variables miembro como privadas es el camino a seguir.
  • Si realmente no le importa que las subclases puedan acceder al estado interno, entonces protegido es suficientemente bueno.

Si aparece un desarrollador y subclasifica su clase, puede estropearlo porque no lo entiende completamente. Con los miembros privados, además de la interfaz pública, no pueden ver los detalles específicos de la implementación de cómo se están haciendo las cosas, lo que le brinda la flexibilidad de cambiarlo más tarde.

Allain Lalonde
fuente
1
¿Puede comentar sobre el rendimiento de las variables protegidas frente a una variable privada con un método get / set?
Jake
3
Yo diría que no es algo de lo que valga la pena preocuparse a menos que descubra a través del perfil que el cuello de la botella termina siendo el acceso (lo que casi nunca es). Hay trucos que se pueden hacer para que el JIT sea más inteligente sobre las cosas si termina siendo un problema. En Java, por ejemplo, puede insinuar que el descriptor de acceso se puede insertar marcándolo como final. Aunque honestamente, el desempeño de los captadores y definidores es mucho menos importante que lidiar con la organización del sistema y con los problemas de desempeño reales determinados por un perfilador.
Allain Lalonde
23
@Jake: Nunca debe tomar decisiones de diseño basadas en supuestos de rendimiento. Usted toma decisiones de diseño basadas en lo que cree que es el mejor diseño y solo si su perfil de la vida real muestra un cuello de botella en su diseño, lo arregla. Por lo general, si el diseño es sólido, el rendimiento también es bueno.
Mecki
Con los miembros privados, además de la interfaz pública, no pueden ver los detalles específicos de la implementación. Pueden simplemente abrir la clase y buscarla, ¡¿eso no tiene sentido ?!
Negro
2
@Black Claramente, Allain quiso decir 'no pueden acceder ' a esos miembros y, por lo tanto, no pueden crear código contra ellos, dejando al autor de la clase libre para eliminar / cambiar los miembros protegidos más tarde. (Por supuesto, el idioma pimpl permitiría ocultarlos visualmente y también de las unidades de traducción, incluido el encabezado).
underscore_d
31

El sentimiento generalizado hoy en día es que provocan un acoplamiento indebido entre las clases derivadas y sus bases.

No tienen ninguna ventaja particular sobre los métodos / propiedades protegidos (alguna vez pudieron tener una ligera ventaja de rendimiento), y también se usaron más en una era en la que la herencia muy profunda estaba de moda, lo que no está de moda en este momento.

Will Dean
fuente
2
¿No debería no particular advantage over protected methods/propertiesser no particular advantage over *private* methods/properties?
Penghe Geng
No, porque estoy / estaba hablando de las ventajas / desventajas de varias formas de comunicación entre las clases derivadas y sus bases; todas estas técnicas estarían 'protegidas'; la diferencia es si son variables miembro (campos) o propiedades / métodos ( es decir, subrutinas de algún tipo).
Will Dean
1
Gracias por la rápida aclaración. Me alegra tener la respuesta del póster original en una hora para mi pregunta a una publicación de 6 años. No crees que eso pueda suceder en la mayoría de los foros en línea :)
Penghe Geng
9
Más notable aún es que en realidad de acuerdo conmigo mismo a través de esa cantidad de tiempo ...
Will Dean
Una orden del día de un constructor es asegurarse de que todas las variables de estado se inicialicen explícitamente. Si se adhiere a esta convención, puede utilizar la superconstrucción para llamar al constructor padre; luego se encargará de inicializar las variables de estado privadas en la clase principal.
ncmathsadist
31

Generalmente, si algo no se concibe deliberadamente como público, lo hago privado.

Si surge una situación en la que necesito acceso a esa variable privada o método de una clase derivada, lo cambio de privado a protegido.

Esto casi nunca sucede; realmente no soy un fanático de la herencia, ya que no es una forma particularmente buena de modelar la mayoría de las situaciones. De todos modos, continúa, no te preocupes.

Yo diría que esto está bien (y probablemente la mejor manera de hacerlo) para la mayoría de los desarrolladores.

El simple hecho del asunto es que , si algún otro desarrollador aparece un año después y decide que necesita acceso a su variable de miembro privada, simplemente editará el código, lo cambiará a protegido y continuará con su negocio.

Las únicas excepciones reales a esto son si está en el negocio de enviar archivos DLL binarios en forma de caja negra a terceros. Esto consiste básicamente en Microsoft, los proveedores de 'Custom DataGrid Control' y tal vez algunas otras aplicaciones grandes que se envían con bibliotecas de extensibilidad. A menos que esté en esa categoría, no vale la pena gastar tiempo / esfuerzo en preocuparse por este tipo de cosas.

Orion Edwards
fuente
8

El problema clave para mí es que una vez que crea una variable protegida, no puede permitir que ningún método de su clase dependa de que su valor esté dentro de un rango, porque una subclase siempre puede colocarlo fuera de rango.

Por ejemplo, si tengo una clase que define el ancho y el alto de un objeto renderizable, y protejo esas variables, no puedo hacer suposiciones sobre (por ejemplo), la relación de aspecto.

Críticamente, nunca puedo hacer esas suposiciones en ningún momento desde el momento en que el código se lanza como biblioteca, ya que incluso si actualizo mis configuradores para mantener la relación de aspecto, no tengo ninguna garantía de que las variables se establezcan a través de los configuradores o que se acceda a ellas a través del getters en código existente.

Tampoco puede ninguna subclase de mi clase optar por hacer esa garantía, ya que tampoco pueden hacer cumplir los valores de las variables, incluso si ese es el punto completo de su subclase .

Como ejemplo:

  • Tengo una clase rectangular con ancho y alto almacenados como variables protegidas.
  • Una subclase obvia (dentro de mi contexto) es una clase "DisplayedRectangle", donde la única diferencia es que restrinjo los anchos y alturas a valores válidos para una pantalla gráfica.
  • Pero eso es imposible ahora , ya que mi clase DisplayedRectangle no puede restringir realmente esos valores, ya que cualquier subclase de ella podría anular los valores directamente, sin dejar de ser tratado como un DisplayedRectangle.

Al restringir las variables para que sean privadas, puedo aplicar el comportamiento que quiero a través de setters o getters.

desparasitar
fuente
7

En general, mantendría sus variables de miembro protegidas en el raro caso en el que también tenga control total sobre el código que las usa. Si está creando una API pública, diría que nunca. A continuación, nos referiremos a la variable miembro como una "propiedad" del objeto.

Esto es lo que su superclase no puede hacer después de hacer que una variable miembro esté protegida en lugar de privada con accesores:

  1. Cree perezosamente un valor sobre la marcha cuando se lea la propiedad. Si agrega un método getter protegido, puede crear el valor de manera perezosa y devolverlo.

  2. saber cuándo la propiedad ha sido modificada o eliminada. Esto puede introducir errores cuando la superclase hace suposiciones sobre el estado de esa variable. Hacer un método de establecimiento protegido para la variable mantiene ese control.

  3. Establezca un punto de interrupción o agregue una salida de depuración cuando se lea o se escriba en la variable.

  4. Cambie el nombre de esa variable miembro sin buscar en todo el código que podría usarla.

En general, creo que sería raro que recomiende crear una variable miembro protegida. Es mejor dedicar unos minutos a exponer la propiedad a través de captadores / definidores que horas después rastreando un error en algún otro código que modificó la variable protegida. No solo eso, sino que está asegurado contra la adición de funciones futuras (como la carga diferida) sin romper el código dependiente. Es más difícil hacerlo más tarde que hacerlo ahora.

Michael Bishop
fuente
7

A nivel de diseño, podría ser apropiado usar una propiedad protegida, pero para la implementación no veo ninguna ventaja en mapear esto a una variable miembro protegida en lugar de métodos de acceso / mutador.

Las variables miembro protegidas tienen desventajas significativas porque permiten de manera efectiva el acceso del código de cliente (la subclase) al estado interno de la clase base. Esto evita que la clase base mantenga efectivamente sus invariantes.

Por la misma razón, las variables de miembro protegidas también hacen que escribir código multiproceso seguro sea significativamente más difícil a menos que se garantice constante o se limite a un solo hilo.

Los métodos de acceso / mutador ofrecen considerablemente más estabilidad de API y flexibilidad de implementación bajo mantenimiento.

Además, si eres un purista de OO, los objetos colaboran / comunican enviando mensajes, no leyendo / configurando el estado.

A cambio ofrecen muy pocas ventajas. No los eliminaría necesariamente del código de otra persona, pero no los uso yo mismo.

richj
fuente
4

La mayoría de las veces, es peligroso usar protected porque rompe un poco la encapsulación de su clase, que bien podría desglosarse por una clase derivada mal diseñada.

Pero tengo un buen ejemplo: digamos que puede algún tipo de contenedor genérico. Tiene una implementación interna y accesos internos. Pero debe ofrecer al menos 3 accesos públicos a sus datos: mapa, hash_map, similar a un vector. Entonces tienes algo como:

template <typename T, typename TContainer>
class Base
{
   // etc.
   protected
   TContainer container ;
}

template <typename Key, typename T>
class DerivedMap     : public Base<T, std::map<Key, T> >      { /* etc. */ }

template <typename Key, typename T>
class DerivedHashMap : public Base<T, std::hash_map<Key, T> > { /* etc. */ }

template <typename T>
class DerivedVector  : public Base<T, std::vector<T> >        { /* etc. */ }

Usé este tipo de código hace menos de un mes (por lo que el código es de la memoria). Después de pensarlo un poco, creo que si bien el contenedor Base genérico debería ser una clase abstracta, incluso si puede vivir bastante bien, porque usar Base directamente sería una molestia, debería estar prohibido.

Resumen Por lo tanto, ha protegido los datos utilizados por la clase derivada. Aún así, debemos tener en cuenta el hecho de que la clase Base debe ser abstracta.

paercebal
fuente
no rompe la encapsulación más que los miembros públicos. Es una configuración para decir que las clases derivadas pueden usar el estado de la clase que no está expuesto a los usuarios de la clase.
gbjbaanb
@gbjbaanb: Se contradice a sí mismo: "no rompe la encapsulación más de lo que lo hacen los miembros públicos" es diferente de "[solo] las clases derivadas pueden usar el estado de la clase". "protegido" es el medio entre lo público y lo privado. Así que "protegido rompe [...] algo la encapsulación" sigue siendo cierto ...
paercebal
de hecho, en el lenguaje c ++, los adaptadores de contenedor como std :: stack expondrán el objeto contenedor subyacente con una variable protegida llamada "c".
Johannes Schaub - litb
Sé que esta publicación es bastante antigua, pero siento la necesidad de intervenir. No rompes "un poco" la encapsulación, la rompes por completo. protectedno está más encapsulado que public. Estoy dispuesto a que me demuestren que estoy equivocado. Todo lo que tienes que hacer es escribir una clase con un miembro protegido y prohibirme modificarlo. Obviamente, la clase tiene que ser no final, ya que el objetivo de usar protected es la herencia. O algo está encapsulado o no lo está. No hay un estado intermedio.
Taekahn
3

En resumen, sí.

Las variables miembro protegidas permiten el acceso a la variable desde cualquier subclases así como desde cualquier clase en el mismo paquete. Esto puede resultar muy útil, especialmente para datos de solo lectura. Sin embargo, no creo que sean necesarios, porque cualquier uso de una variable miembro protegida se puede replicar utilizando una variable miembro privada y un par de getters y setters.

Jay Stramel
fuente
1
Por el contrario, las variables de miembros privados tampoco son necesarias; public es suficiente para cualquier uso.
Alice
3

Solo para que conste, en el artículo 24 de "C ++ excepcional", en una de las notas al pie, Sutter dice "nunca escribirías una clase que tenga una variable miembro pública o protegida, ¿verdad? (Independientemente del ejemplo deficiente establecido por algunas bibliotecas .) "

hAcKnRoCk
fuente
2

Para obtener información detallada sobre los modificadores de acceso .Net, vaya aquí

No hay ventajas o desventajas reales para las variables de miembro protegidas, es una cuestión de lo que necesita en su situación específica. En general, es una práctica aceptada declarar las variables miembro como privadas y habilitar el acceso externo a través de propiedades. Además, algunas herramientas (por ejemplo, algunos mapeadores O / R) esperan que los datos del objeto estén representados por propiedades y no reconocen las variables miembro públicas o protegidas. Pero si sabe que desea que sus subclases (y SOLO sus subclases) accedan a una determinada variable, no hay razón para no declararla protegida.

Manu
fuente
Querer que las subclases accedan a una variable es muy diferente de querer que puedan mutarla libremente . Ese es uno de los principales argumentos en contra de las variables protegidas: ahora su clase base no puede asumir que ninguna de sus invariantes se cumple, porque cualquier clase derivada puede hacer absolutamente cualquier cosa con los miembros protegidos. Ese es el principal argumento en su contra. Si solo necesitan acceder a los datos, entonces ... escriba un descriptor de acceso. : P (uso variables protegidas, aunque probablemente más de lo que debería, ¡y trataré de reducirlas!)
underscore_d