¿Por qué los campos privados son privados para el tipo, no para la instancia?

153

En C # (y muchos otros lenguajes) es perfectamente legítimo acceder a campos privados de otras instancias del mismo tipo. Por ejemplo:

public class Foo
{
    private bool aBool;

    public void DoBar(Foo anotherFoo)
    {
        if (anotherFoo.aBool) ...
    }
}

Como la especificación C # (secciones 3.5.1, 3.5.2) establece que el acceso a los campos privados es en un tipo, no en una instancia. He estado discutiendo esto con un colega y estamos tratando de encontrar una razón por la que funcione así (en lugar de restringir el acceso a la misma instancia).

El mejor argumento que podríamos proponer es para las verificaciones de igualdad donde la clase puede querer acceder a campos privados para determinar la igualdad con otra instancia. ¿Hay alguna otra razón? ¿O alguna razón de oro que absolutamente significa que debe funcionar así o que algo sería completamente imposible?

RichK
fuente
18
¿Cuáles son los beneficios de la alternativa?
Jon Skeet
19
Es cierto que no modela muy bien el mundo real. Después de todo, el hecho de que mi automóvil pueda informar la cantidad de gasolina que le queda no significa que mi automóvil pueda saber cuánto combustible le queda a CADA automóvil. Entonces, sí, teóricamente puedo ver tener un acceso de "instancia privada". Sin embargo, siento que realmente nunca lo usaría porque me preocuparía que en el 99% de los casos, realmente DESEA que otra instancia acceda a esa variable.
Aquino
2
@ Jon La posición que se tomó para los llamados 'beneficios' es que las clases no deberían conocer el estado interno de otra instancia, independientemente del hecho de que sea del mismo tipo. No es un beneficio por decir, pero probablemente haya una razón bastante buena para elegir uno sobre el otro y eso es lo que hemos estado tratando de descubrir
RichK,
44
Siempre puede convertir la variable en un par de cierres getter / setter, inicializados en el constructor, que fallará si esto no es lo mismo que fue durante la inicialización ...
Martin Sojka
8
Scala proporciona miembros privados de instancia , junto con muchos otros niveles de acceso que no se encuentran en Java, C # y muchos otros lenguajes. Esta es una pregunta perfectamente legítima.
Bruno Reis

Respuestas:

73

Creo que una razón por la que funciona de esta manera es porque los modificadores de acceso funcionan en tiempo de compilación . Como tal, determinar si un objeto dado es también el objeto actual no es fácil de hacer. Por ejemplo, considere este código:

public class Foo
{
    private int bar;

    public void Baz(Foo other)
    {
        other.bar = 2;
    }

    public void Boo()
    {
        Baz(this);
    }
}

¿Puede el compilador descubrir necesariamente que otheres realmente this? No en todos los casos. Se podría argumentar que esto simplemente no debería compilarse entonces, pero eso significa que tenemos una ruta de código donde no se puede acceder a un miembro de la instancia privada de la instancia correcta , lo que creo que es aún peor.

Solo requerir visibilidad a nivel de tipo en lugar de a nivel de objeto asegura que el problema sea manejable, así como hacer que una situación que parece que debería funcionar realmente funcione.

EDITAR : El punto de Danilel Hilgarth de que este razonamiento es al revés tiene mérito. Los diseñadores de idiomas pueden crear el idioma que quieran, y los escritores de compiladores deben cumplir con él. Dicho esto, los diseñadores de idiomas tienen algún incentivo para facilitar que los escritores de compiladores hagan su trabajo. (Aunque en este caso, es bastante fácil argumentar que solo se puede acceder a los miembros privados a través de this(implícita o explícitamente)).

Sin embargo, creo que eso hace que el problema sea más confuso de lo que debe ser. La mayoría de los usuarios (incluido yo mismo) lo encontrarían innecesariamente limitante si el código anterior no funcionara: después de todo, ¡esos son mis datos a los que intento acceder! ¿Por qué debería tener que pasar this?

En resumen, creo que podría haber exagerado el caso por ser "difícil" para el compilador. Lo que realmente quise transmitir es que la situación anterior parece una situación en la que a los diseñadores les gustaría trabajar.

dlev
fuente
33
En mi humilde opinión, este razonamiento es al revés. El compilador hace cumplir las reglas del lenguaje. No los HACE. En otras palabras, si los miembros privados fueran "instancia privada" y no "tipo privado", el compilador se habría implementado de otras maneras, por ejemplo, solo permitiendo this.bar = 2pero no other.bar = 2, porque otherpodría ser una instancia diferente.
Daniel Hilgarth
@Daniel Ese es un buen punto, pero como mencioné, creo que tener esa restricción sería peor que tener visibilidad a nivel de clase.
dlev
3
@dlev: No dije nada sobre si creo que los miembros de "instancia privada" son buenos o no. :-) Solo quería señalar que está argumentando que una implementación técnica dicta las reglas del lenguaje y que, en mi opinión, esta argumentación no es correcta.
Daniel Hilgarth
2
@Daniel Point tomado. Todavía creo que esta es una consideración válida, aunque, por supuesto, tiene razón en que, en última instancia, los diseñadores de idiomas descubren lo que quieren y los escritores de compiladores se ajustan a eso.
dlev
2
No creo que el argumento del compilador se mantenga. Hay lenguajes alrededor que solo permiten instancia privada (como Ruby) y que permiten instancia privada y clase privada por elección (como Eiffel). La generación del compilador no era necesariamente más difícil o más fácil para estos idiomas. Para obtener más información, consulte también mi respuesta en el hilo.
Abel
61

Porque el propósito del tipo de encapsulación utilizado en C # y lenguajes similares * es reducir la dependencia mutua de diferentes piezas de código (clases en C # y Java), no diferentes objetos en la memoria.

Por ejemplo, si escribe código en una clase que usa algunos campos en otra clase, entonces estas clases están muy estrechamente acopladas. Sin embargo, si se trata de código en el que tiene dos objetos de la misma clase, entonces no hay dependencia adicional. Una clase siempre depende de sí misma.

Sin embargo, toda esta teoría sobre la encapsulación falla tan pronto como alguien crea propiedades (o obtiene / establece pares en Java) y expone todos los campos directamente, lo que hace que las clases estén tan acopladas como si de todos modos estuvieran accediendo a los campos.

* Para aclaraciones sobre los tipos de encapsulación, consulte la excelente respuesta de Abel.

Goran Jovic
fuente
3
No creo que falle una vez get/setque se proporcionan los métodos. Los métodos de acceso aún mantienen una abstracción (el usuario no necesita saber que hay un campo detrás de él, o qué campo (s) pueden estar detrás de la propiedad). Por ejemplo, si estamos escribiendo una Complexclase, podemos exponer propiedades para obtener / establecer las coordenadas polares, y otro obtener / establecer para coordenadas cartesianas. El usuario utiliza la clase que se encuentra debajo (incluso podría ser algo completamente diferente).
Ken Wayne VanderLinde
55
@Ken: Falla si proporciona una propiedad para cada campo que tiene sin siquiera considerar si debe exponerse.
Goran Jovic
@Goran Aunque estoy de acuerdo en que no sería lo ideal, todavía no falla por completo, ya que los accesos get / set le permiten agregar lógica adicional si los campos subyacentes cambian, por ejemplo.
ForbesLindesay
1
"Porque el propósito de la encapsulación es disminuir la dependencia mutua de diferentes partes del código" >> no. La encapsulación también puede significar encapsulación de instancia, depende del idioma o de la escuela de pensamiento. Una de las voces más definitivas en OO, Bertrand Meyer, considera que esto es incluso el predeterminado private. Puede consultar mi respuesta para ver mi opinión sobre las cosas si lo desea;).
Abel
@Abel: basé mi respuesta en idiomas con encapsulación de clase porque OP preguntó por uno de ellos, y francamente porque los conozco. ¡Gracias por la corrección y por la nueva información interesante!
Goran Jovic
49

Ya se han agregado algunas respuestas a este interesante hilo, sin embargo, no encontré la razón real de por qué este comportamiento es como es. Déjame intentarlo:

Tiempo atrás

En algún lugar entre Smalltalk en los años 80 y Java a mediados de los 90, el concepto de orientación a objetos maduró. La ocultación de información, que originalmente no se consideraba un concepto solo disponible para OO (mencionado por primera vez en 1978), se introdujo en Smalltalk ya que todos los datos (campos) de una clase son privados, todos los métodos son públicos. Durante los muchos desarrollos nuevos de OO en los años 90, Bertrand Meyer intentó formalizar gran parte de los conceptos de OO en su libro histórico. Object Oriented Software Construction (OOSC), que desde entonces se ha considerado una referencia (casi) definitiva sobre conceptos de OO y diseño de lenguaje. .

En el caso de visibilidad privada

Según Meyer, un método debería estar disponible para un conjunto definido de clases (página 192-193). Obviamente, esto proporciona una granularidad muy alta de la ocultación de información, la siguiente característica está disponible para la clase A y la clase B y todos sus descendientes:

feature {classA, classB}
   methodName

En el caso de privateque diga lo siguiente: sin declarar explícitamente un tipo como visible para su propia clase, no puede acceder a esa característica (método / campo) en una llamada calificada. Es decir, si xes una variable, x.doSomething()no está permitido. El acceso no calificado está permitido, por supuesto, dentro de la clase misma.

En otras palabras: para permitir el acceso de una instancia de la misma clase, debe permitir el acceso del método de esa clase explícitamente. Esto a veces se llama instancia-privado versus clase-privado.

Instancia privada en lenguajes de programación

Sé de al menos dos idiomas actualmente en uso que utilizan la ocultación de información privada de instancia en lugar de la ocultación de información privada de clase. Uno es Eiffel, un lenguaje diseñado por Meyer, que lleva a OO a sus extremos. El otro es Ruby, un lenguaje mucho más común hoy en día. En Ruby, privatesignifica: "privado para esta instancia" .

Elecciones para el diseño del lenguaje.

Se ha sugerido que permitir instancia-privada sería difícil para el compilador. No lo creo, ya que es relativamente simple permitir o rechazar llamadas calificadas a métodos. Si es por un método privado, doSomething()está permitido yx.doSomething() no , un diseñador de lenguaje ha definido efectivamente la accesibilidad de solo instancia para métodos y campos privados.

Desde un punto de vista técnico, no hay razón para elegir de una forma u otra (especialmente si se considera que Eiffel.NET puede hacer esto con IL, incluso con herencia múltiple, no hay una razón inherente para no proporcionar esta función).

Por supuesto, es una cuestión de gustos y, como ya se mencionó, algunos métodos podrían ser más difíciles de escribir sin la característica de visibilidad a nivel de clase de los métodos y campos privados.

Por qué C # solo permite la encapsulación de clases y no la encapsulación de instancias

Si observa los hilos de Internet en la encapsulación de instancias (un término que a veces se usa para referirse al hecho de que un lenguaje define los modificadores de acceso a nivel de instancia, en oposición al nivel de clase), el concepto a menudo está mal visto. Sin embargo, teniendo en cuenta que algunos lenguajes modernos usan la encapsulación de instancias, al menos para el modificador de acceso privado, te hace pensar que puede ser y es útil en el mundo de la programación moderna.

Sin embargo, C # ciertamente ha mirado más duro en C ++ y Java por su diseño de lenguaje. Si bien Eiffel y Modula-3 también estaban en la imagen, teniendo en cuenta las muchas características de Eiffel que faltan (herencia múltiple), creo que eligieron la misma ruta que Java y C ++ cuando se trataba del modificador de acceso privado.

Si realmente quiere saber por qué debería tratar de contactar a Eric Lippert, Krzysztof Cwalina, Anders Hejlsberg o cualquier otra persona que trabajó en el estándar de C #. Desafortunadamente, no pude encontrar una nota definitiva en el lenguaje de programación anotado The C # .

Abel
fuente
1
Esa es una respuesta encantadora con muchos antecedentes, muy interesante, gracias por tomarse el tiempo para responder a una pregunta (relativamente) antigua
RichK
1
@RichK: de nada, solo capté la pregunta demasiado tarde, supongo, pero encontré el tema lo suficientemente intrigante para responder con cierta profundidad :)
Abel
En COM, "privado" significa "instancia privada". Creo que esto fue en cierta medida porque una definición de clase Fooefectivamente representaba una interfaz y una clase implementadora; Como COM no tenía un concepto de referencia de objeto de clase, solo una referencia de interfaz, no había forma de que una clase pudiera contener una referencia a algo que se garantizaba que era otra instancia de esa clase. Este diseño basado en la interfaz hizo que algunas cosas fueran engorrosas, pero significaba que uno podía diseñar una clase que fuera sustituible por otra sin tener que compartir ninguna de las mismas partes internas.
supercat
2
Gran respuesta. OP debería considerar marcar esta pregunta como la respuesta.
Gavin Osborn
18

Esta es solo mi opinión, pero pragmáticamente, creo que si un programador tiene acceso a la fuente de una clase, puede confiar razonablemente en que acceda a los miembros privados de la instancia de la clase. ¿Por qué atar a los programadores con la mano derecha cuando a la izquierda ya les has dado las llaves del reino?

PescadoCanasta
fuente
13
No entiendo este argumento. Supongamos que está trabajando en un programa en el que usted es el único desarrollador y el único que utilizaría sus bibliotecas. ¿Está diciendo en ese caso que hace público a CADA miembro porque tiene acceso al código fuente?
aquinas
@aquinas: eso es bastante diferente. Hacer público todo conducirá a prácticas de programación horribles. Permitir que un tipo vea campos privados de otras instancias de sí mismo es un caso muy limitado.
Igby Largeman
2
No, no estoy abogando por hacer públicos todos los miembros. ¡Cielos no! Mi argumento es que, si ya está en la fuente, puede ver los miembros privados, cómo se usan, las convenciones empleadas, etc., ¿por qué no puede usar ese conocimiento?
FishBasketGordo
77
Creo que la forma de pensar todavía está en los términos del tipo. Si no se puede confiar en el tipo para tratar con los miembros del tipo correctamente, ¿qué se puede hacer? ( Normalmente , sería un programador que realmente controla las interacciones, pero en esta era de herramientas de generación de código, ese no siempre es el caso.)
Anthony Pegram
3
Mi pregunta no es realmente de confianza, más de necesidad. La confianza es totalmente irrelevante cuando puedes usar la reflexión para cosas atroces para los privados. Me pregunto por qué, conceptualmente, puedes acceder a los privados. Asumí que había una razón lógica en lugar de una situación de mejores prácticas.
RichK
13

De hecho, la razón es la verificación de igualdad, la comparación, la clonación, la sobrecarga del operador ... Sería muy complicado implementar operator + en números complejos, por ejemplo.

Lorenzo Dematté
fuente
3
Pero todo lo que mencionas requiere un tipo que OBTIENE el valor de otro tipo. Estoy de acuerdo con decir: ¿quieres inspeccionar mi estado privado? Bien, adelante. ¿Quieres establecer mi estado privado? De ninguna manera, amigo, cambia tu propio maldito estado. :)
aquinas
2
@aquinas No funciona de esa manera. Los campos son campos. Solo son de solo lectura si se declaran como readonlyy eso también se aplica a la instancia de declaración. Evidentemente, usted está afirmando que debería haber una regla de compilación específica para prohibir esto, pero cada una de esas reglas es una característica que el equipo de C # tiene que especificar, documentar, localizar, probar, mantener y respaldar. Si no hay un beneficio convincente , ¿por qué lo harían?
Aaronaught
Estoy de acuerdo con @Aaronaught: hay un costo asociado con la implementación de cada nueva especificación y cambio en el compilador. Para un escenario establecido, considere un método de clonación. Claro, es posible que desee realizarlo con un constructor privado, pero tal vez en algunos escenarios no sea posible / aconsejable.
Lorenzo Dematté
1
Equipo C #? Solo estoy debatiendo posibles cambios teóricos del lenguaje en CUALQUIER idioma. "Si no hay un beneficio convincente ..." Creo que puede haber un beneficio. Al igual que con cualquier garantía del compilador, alguien puede encontrar útil garantizar que una clase solo modifique su propio estado.
Tomás de Aquino
@aquinas: Las características se implementan porque son útiles para muchos o la mayoría de los usuarios, no porque puedan ser útiles en algunos casos aislados.
Aaronaught
9

En primer lugar, ¿qué pasaría con los miembros estáticos privados? ¿Solo se puede acceder a ellos mediante métodos estáticos? Ciertamente no querrás eso, porque entonces no podrás acceder a tu consts.

En cuanto a su pregunta explícita, considere el caso de a StringBuilder, que se implementa como una lista vinculada de instancias de sí mismo:

public class StringBuilder
{
    private string chunk;
    private StringBuilder nextChunk;
}

Si no puede acceder a los miembros privados de otras instancias de su propia clase, debe implementar de ToStringesta manera:

public override string ToString()
{
    return chunk + nextChunk.ToString();
}

Esto funcionará, pero es O (n ^ 2), no muy eficiente. De hecho, eso probablemente derrota el propósito de tener una StringBuilderclase en primer lugar. Si puede acceder a los miembros privados de otras instancias de su propia clase, puede implementar ToStringcreando una cadena de la longitud adecuada y luego haciendo una copia insegura de cada fragmento en su lugar apropiado en la cadena:

public override string ToString()
{
    string ret = string.FastAllocateString(Length);
    StringBuilder next = this;

    unsafe
    {
        fixed (char *dest = ret)
            while (next != null)
            {
                fixed (char *src = next.chunk)
                    string.wstrcpy(dest, src, next.chunk.Length);
                next = next.nextChunk;
            }
    }
    return ret;
}

Esta implementación es O (n), lo que lo hace muy rápido, y solo es posible si tiene acceso a miembros privados de otras instancias de su clase .

Gabe
fuente
Sí, pero ¿no hay otras formas de evitarlo, como exponer algunos campos como internos?
MikeKulls
2
@ Mike: ¡Exponer un campo ya internalque lo hace menos privado! Terminarías exponiendo los elementos internos de tu clase a todo en su conjunto.
Gabe
3

Esto es perfectamente legítimo en muchos lenguajes (C ++ para uno). Los modificadores de acceso provienen del principio de encapsulación en OOP. La idea es restringir el acceso al exterior , en este caso afuera hay otras clases. Cualquier clase anidada en C #, por ejemplo, también puede acceder a los miembros privados de sus padres.

Si bien esta es una opción de diseño para un diseñador de idiomas. La restricción de este acceso puede complicar extremadamente algunos escenarios muy comunes sin contribuir mucho al aislamiento de las entidades.

Hay una discusión similar aquí

Mehran
fuente
1

No creo que haya una razón por la que no podamos agregar otro nivel de privacidad, donde los datos son privados para cada instancia. De hecho, eso podría incluso proporcionar una agradable sensación de integridad al idioma.

Pero en la práctica real, dudo que realmente sea tan útil. Como señaló, nuestra privacidad habitual es útil para cosas como las verificaciones de igualdad, así como para la mayoría de las otras operaciones que involucran múltiples instancias de un Tipo. Aunque, también me gusta su punto sobre el mantenimiento de la abstracción de datos, ya que ese es un punto importante en OOP.

Creo que, en general, proporcionar la capacidad de restringir el acceso de esa manera podría ser una buena característica para agregar a OOP. ¿Es realmente tan útil? Yo diría que no, ya que una clase debería poder confiar en su propio código. Dado que esa clase es lo único que puede acceder a miembros privados, no hay una razón real para necesitar abstracción de datos cuando se trata de una instancia de otra clase.

Por supuesto, siempre puede escribir su código como privado aplicado a instancias. Use los get/setmétodos habituales para acceder / cambiar los datos. Eso probablemente haría que el código sea más manejable si la clase puede estar sujeta a cambios internos.

Ken Wayne VanderLinde
fuente
0

Grandes respuestas dadas anteriormente. Agregaría que parte de este problema es el hecho de que, en primer lugar, incluso se permite instanciar una clase dentro de sí mismo. Desde entonces, en una lógica recursiva "for" loop, por ejemplo, usar ese tipo de truco siempre que tenga lógica para finalizar la recursividad. Pero crear instancias o pasar la misma clase dentro de sí mismo sin crear tales bucles lógicamente crea sus propios peligros, a pesar de que es un paradigma de programación ampliamente aceptado. Por ejemplo, una clase C # puede crear una copia de sí misma en su constructor predeterminado, pero eso no rompe ninguna regla ni crea bucles causales. ¿Por qué?

Por cierto ... este mismo problema se aplica también a los miembros "protegidos". :(

Nunca acepté ese paradigma de programación por completo porque todavía viene con un conjunto completo de problemas y riesgos que la mayoría de los programadores no comprenden completamente hasta que surgen problemas como este y confunden a las personas y desafían toda la razón de tener miembros privados.

Este aspecto "extraño y extraño" de C # es una razón más por la que una buena programación no tiene nada que ver con la experiencia y la habilidad, sino solo conocer los trucos y trampas ... como trabajar en un automóvil. Es el argumento de que las reglas debían romperse, lo cual es un modelo muy malo para cualquier lenguaje informático.

Stokely
fuente
-1

Me parece que si los datos fueran privados para otras instancias del mismo tipo, ya no serían necesariamente del mismo tipo. No parece comportarse ni actuar de la misma manera que otras instancias. El comportamiento podría modificarse fácilmente en función de esos datos internos privados. Eso solo difundiría la confusión en mi opinión.

Hablando en términos generales, personalmente creo que escribir clases derivadas de una clase base ofrece una funcionalidad similar que está describiendo con 'tener datos privados por instancia'. En cambio, solo tiene una nueva definición de clase por tipo 'único'.

C Johnson
fuente