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?
Respuestas:
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:
¿Puede el compilador descubrir necesariamente que
other
es realmentethis
? 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.
fuente
this.bar = 2
pero noother.bar = 2
, porqueother
podría ser una instancia diferente.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.
fuente
get/set
que 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 unaComplex
clase, 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).private
. Puede consultar mi respuesta para ver mi opinión sobre las cosas si lo desea;).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:
En el caso de
private
que 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, six
es 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,
private
significa: "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 # .
fuente
Foo
efectivamente 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.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?
fuente
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.
fuente
readonly
y 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?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
const
s.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:Si no puede acceder a los miembros privados de otras instancias de su propia clase, debe implementar de
ToString
esta manera:Esto funcionará, pero es O (n ^ 2), no muy eficiente. De hecho, eso probablemente derrota el propósito de tener una
StringBuilder
clase en primer lugar. Si puede acceder a los miembros privados de otras instancias de su propia clase, puede implementarToString
creando una cadena de la longitud adecuada y luego haciendo una copia insegura de cada fragmento en su lugar apropiado en la cadena: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 .
fuente
internal
que lo hace menos privado! Terminarías exponiendo los elementos internos de tu clase a todo en su conjunto.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í
fuente
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/set
mé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.fuente
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.
fuente
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'.
fuente