¿Por qué C # no proporciona la palabra clave 'amigo' de estilo C ++? [cerrado]


208

La palabra clave amigo de C ++ permite que class Aa se designe class Bcomo amigo. Esto permite Class Bacceder a los private/ protectedmiembros de class A.

Nunca he leído nada sobre por qué esto se dejó fuera de C # (y VB.NET). La mayoría de las respuestas a esta pregunta anterior de StackOverflow parecen estar diciendo que es una parte útil de C ++ y que hay buenas razones para usarla. En mi experiencia, tendría que estar de acuerdo.

Me parece que otra pregunta es realmente preguntar cómo hacer algo similar frienden una aplicación C #. Si bien las respuestas generalmente giran en torno a clases anidadas, no parece tan elegante como usar la friendpalabra clave.

El libro original de Patrones de diseño lo usa regularmente en todos sus ejemplos.

En resumen, ¿por qué friendfalta en C # y cuál es la forma (o formas) de "mejores prácticas" de simularlo en C #?

(Por cierto, la internalpalabra clave no es la misma cosa, permite que todas las clases dentro de todo el ensamblado accedan a los internalmiembros, mientras friendque le permite otorgar a cierta clase acceso completo a exactamente otra clase)


55
Me siento protegido interno es un buen compromiso ..
Gulzar Nazim

3
No es demasiado confuso, ¿verdad? Como dije anteriormente, el libro GOF lo usa con bastante frecuencia en ejemplos. Para mí no es más confuso que interno.
Ash el

77
Ciertamente puedo ver escenarios en los que pueden ser muy útiles, como ya se mencionó en Pruebas unitarias. ¿Pero realmente quieres que tus amigos accedan a tus partes privadas?
danswain

2
Es fácil de responder: C # ofrece "interno" como modificador de acceso que otorga acceso a todo el código en el mismo módulo / ensamblaje. Esto elimina la necesidad de algo como amigo. En Java, la palabra clave "protegido" se comporta de manera similar al acceso wrt desde el mismo paquete.
sellibitze

2
@sellibitze, menciono las diferencias con interno en la pregunta. El problema con Interanl es que todas las clases en el ensamblado pueden acceder a los miembros internos. Esto rompe la encapsulación ya que muchas de estas clases pueden no necesitar acceso.
Ash

Respuestas:


67

Tener amigos en la programación se considera más o menos "sucio" y fácil de abusar. Rompe las relaciones entre clases y socava algunos atributos fundamentales de un lenguaje OO.

Dicho esto, es una buena característica y la he usado muchas veces en C ++; y me gustaría usarlo en C # también. Pero apuesto a causa de la OOness "pura" de C # (en comparación con la pseudo OOness de C ++) MS decidió que debido a que Java no tiene una palabra clave amiga, C # tampoco debería (es broma;))

En una nota seria: interno no es tan bueno como amigo, pero hace el trabajo. Recuerde que es raro que distribuya su código a desarrolladores de terceros no a través de una DLL; así que mientras usted y su equipo conozcan las clases internas y su uso, estarán bien.

EDITAR Permítanme aclarar cómo la palabra clave amigo socava la POO.

Las variables y métodos privados y protegidos son quizás una de las partes más importantes de OOP. La idea de que los objetos pueden contener datos o lógica que solo ellos pueden usar le permite escribir su implementación de funcionalidad independientemente de su entorno, y que su entorno no puede alterar la información de estado que no es adecuada para manejar. Al usar friend, está acoplando las implementaciones de dos clases juntas, lo cual es mucho peor que si solo hubiera acoplado su interfaz.


167
C # 'interno' en realidad está afectando la encapsulación mucho más que C ++ 'amigo'. Con "amistad", el propietario otorga explícitamente el permiso a quien quiera. "Interno" es un concepto abierto.
Nemanja Trifunovic

21
Anders debe ser elogiado por las características que dejó de lado, no por las que incluyó.
Mehrdad Afshari

21
Sin embargo, no estoy de acuerdo en que la encapsulación interna de C # duele. Lo contrario en mi opinión. Puede usarlo para crear un ecosistema encapsulado dentro de un ensamblaje. Varios objetos pueden compartir tipos internos dentro de este ecosistema que no están expuestos al resto de la aplicación. Utilizo los trucos de ensamblaje "amigo" para crear ensambles de prueba unitaria para estos objetos.
Molesto el

26
Esto no tiene sentido. friendno permite que clases o funciones arbitrarias accedan a miembros privados. Permite que las funciones o clases específicas que se marcaron como amigo lo hagan. La clase propietaria del miembro privado aún controla el acceso al 100%. También podría argumentar que los métodos de miembros públicos. Ambos aseguran que los métodos enumerados específicamente en la clase tengan acceso a miembros privados de la clase.
JALF

8
Pienso todo lo contrario. Si un ensamblaje tiene 50 clases, si 3 de esas clases usan alguna clase de utilidad, entonces hoy, su única opción es marcar esa clase de utilidad como "interna". Al hacerlo, no solo lo pone a disposición de las 3 clases, sino también para el resto de las clases del ensamblado. Lo que si todo lo que quisieras hacer es proporcionar un acceso más detallado de modo que solo las tres clases en cuestión tuvieran acceso a la clase de utilidad. Realmente lo hace más orientado a objetos porque mejora la encapsulación.
zumalifeguard

104

En otros comentarios. Usar friend no se trata de violar la encapsulación, sino de hacerla cumplir. Al igual que los accesores + mutadores, la sobrecarga de operadores, la herencia pública, el downcasting, etc. , a menudo se usa incorrectamente, pero no significa que la palabra clave no tenga, o peor, un mal propósito.

Vea el mensaje de Konrad Rudolph en el otro hilo, o si lo prefiere, vea la entrada relevante en las preguntas frecuentes de C ++.


... y la entrada correspondiente en la FQA: yosefk.com/c++fqa/friend.html#fqa-14.2
Josh Lee

28
+1 para " Usar amigo no se trata de violar la encapsulación, sino de lo contrario se trata de hacerla cumplir "
Nawaz,

2
Una cuestión de opinión semántica, creo; y quizás relacionado con la situación exacta en cuestión. Hacer una clase a friendle da acceso a TODOS sus miembros privados, incluso si solo necesita dejar que establezca uno de ellos. Hay un fuerte argumento para afirmar que hacer que los campos estén disponibles para otra clase que no los necesita cuenta como "violar la encapsulación". Hay lugares en C ++ donde es necesario (sobrecarga de operadores), pero hay una compensación de encapsulación que debe considerarse cuidadosamente. Parece que los diseñadores de C # sintieron que la compensación no valía la pena.
GrandOpener

3
La encapsulación se trata de hacer cumplir invariantes. Cuando definimos una clase como amiga de otra, decimos explícitamente que las dos clases están fuertemente vinculadas con respecto al manejo de los invariantes de la primera. Hacer que la clase se haga amiga es aún mejor que exponer todos los atributos directamente (como campo público) o mediante setters.
Luc Hermitte

1
Exactamente. Cuando quieres usar amigo pero no puedes, estás obligado a usar público o público, que están mucho más abiertos a ser molestados por cosas que no deberían. Especialmente en un ambiente de equipo.
Prole

50

Para obtener información, hay otra cosa relacionada, pero no exactamente la misma en .NET [InternalsVisibleTo], que permite que un ensamblaje designe otro ensamblaje (como un ensamblaje de prueba de unidad) que (efectivamente) tenga acceso "interno" a tipos / miembros en El montaje original.


3
buena sugerencia, ya que probablemente las personas que buscan un amigo quieren encontrar una forma de prueba unitaria con la capacidad de tener más "conocimiento" interno.
SwissCoder

14

Debería poder lograr el mismo tipo de cosas para las que se usa "amigo" en C ++ mediante el uso de interfaces en C #. Requiere que defina explícitamente qué miembros se pasan entre las dos clases, lo cual es un trabajo adicional pero también puede hacer que el código sea más fácil de entender.

Si alguien tiene un ejemplo de uso razonable de "amigo" que no se puede simular usando interfaces, ¡compártelo! Me gustaría entender mejor las diferencias entre C ++ y C #.


Buen punto. ¿Tuviste la oportunidad de mirar la pregunta anterior sobre SO (formulada por monóxido y vinculada anteriormente? Me interesaría cómo resolverla mejor en C #?)
Ash

No estoy seguro exactamente cómo hacerlo en C #, pero creo que la respuesta de Jon Skeet a las preguntas de monóxido apunta en la dirección correcta. C # permite clases anidadas. Sin embargo, no he tratado de codificar y compilar una solución. :)
Parappa

Siento que el patrón de mediador es un buen ejemplo de dónde sería agradable un amigo. Refactorizo ​​mis formularios para tener un mediador. Quiero que mi formulario pueda llamar al "evento" como métodos en el mediador, pero realmente no quiero que otras clases llamen a esos "eventos" como métodos. La función "Amigo" le daría al formulario acceso a los métodos sin abrirlo a todo lo demás en el ensamblaje.
Vaccano

3
El problema con el enfoque de la interfaz de reemplazar 'Friend' es que un objeto exponga una interfaz, ¡eso significa que cualquiera puede lanzar el objeto a esa interfaz y tener acceso a los métodos! El alcance de la interfaz a interno, protegido o privado tampoco funciona como si eso funcionara, ¡simplemente podría analizar el método en cuestión! Piensa en una madre y un niño en un centro comercial. Público es todos en el mundo. Interna es solo la gente en el centro comercial. Privado es solo la madre o el niño. 'Amigo' es algo entre la madre y la hija solamente.
Mark A. Donohoe

1
Aquí hay uno: stackoverflow.com/questions/5921612/… Como vengo de C ++, la falta de amigos me vuelve loco casi a diario. En mis pocos años con C #, he hecho literalmente cientos de métodos públicos donde podrían ser privados y más seguros, todo por falta de amigos. Personalmente, creo que amigo es lo más importante que se debe agregar a .NET
kaalus

14

Con friendun diseñador de C ++ tiene un control preciso sobre a quién están expuestos los miembros privados *. Pero, se ve obligado a exponer a cada uno de los miembros privados.

Con internalun diseñador de C # tiene un control preciso sobre el conjunto de miembros privados que está exponiendo. Obviamente, puede exponer a un solo miembro privado. Pero, quedará expuesto a todas las clases en la asamblea.

Por lo general, un diseñador desea exponer solo unos pocos métodos privados a algunas otras clases seleccionadas. Por ejemplo, en un patrón de clase de fábrica, puede desearse que la clase C1 sea instanciada solo por la clase de fábrica CF1. Por lo tanto, la clase C1 puede tener un constructor protegido y una fábrica amiga de la clase CF1.

Como puede ver, tenemos 2 dimensiones a lo largo de las cuales se puede romper la encapsulación. friendlo rompe a lo largo de una dimensión, lo internalhace a lo largo de la otra. ¿Cuál es una violación peor en el concepto de encapsulación? Difícil de decir. Pero sería bueno tener ambos friendy internaldisponibles. Además, una buena adición a estos dos sería el tercer tipo de palabra clave, que se utilizaría miembro por miembro (like internal) y especifica la clase de destino (like friend).

* Por brevedad, usaré "privado" en lugar de "privado y / o protegido".

- Nick


13

De hecho, C # ofrece la posibilidad de obtener el mismo comportamiento en modo POO puro sin palabras especiales: son interfaces privadas.

En cuanto a la pregunta ¿Cuál es el equivalente de C # de amigo? se marcó como duplicado de este artículo y nadie propone una realización realmente buena. Aquí mostraré la respuesta a ambas preguntas.

La idea principal fue tomar desde aquí: ¿Qué es una interfaz privada?

Digamos que necesitamos alguna clase que pueda administrar instancias de otras clases y llamar a algunos métodos especiales sobre ellas. No queremos dar la posibilidad de llamar a estos métodos a ninguna otra clase. Esto es exactamente lo mismo que hace la palabra clave friend c ++ en el mundo c ++.

Creo que un buen ejemplo en la práctica real podría ser el patrón Full State Machine donde algunos controladores actualizan el objeto de estado actual y cambian a otro objeto de estado cuando es necesario.

Tú podrías:

  • La forma más fácil y peor de hacer público el método Update (): espero que todos entiendan por qué es malo.
  • La siguiente forma es marcarlo como interno. Es lo suficientemente bueno si coloca sus clases en otra asamblea, pero incluso entonces cada clase en esa asamblea podría llamar a cada método interno.
  • Use una interfaz privada / protegida, y lo seguí de esta manera.

Controller.cs

public class Controller
{
    private interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() {  }
    }

    public Controller()
    {
        //it's only way call Update is to cast obj to IState
        IState obj = new StateBase();
        obj.Update();
    }
}

Program.cs

class Program
{
    static void Main(string[] args)
    {
        //it's impossible to write Controller.IState p = new Controller.StateBase();
        //Controller.IState is hidden
        var p = new Controller.StateBase();
        //p.Update(); //is not accessible
    }
}

Bueno, ¿qué hay de la herencia?

Necesitamos usar la técnica descrita en Dado que las implementaciones explícitas de miembros de interfaz no pueden declararse virtuales y marcar IState como protegido para dar la posibilidad de derivar también del Controlador.

Controller.cs

public class Controller
{
    protected interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() { OnUpdate(); }
        protected virtual void OnUpdate()
        {
            Console.WriteLine("StateBase.OnUpdate()");
        }
    }

    public Controller()
    {
        IState obj = new PlayerIdleState();
        obj.Update();
    }
}

PlayerIdleState.cs

public class PlayerIdleState: Controller.StateBase
{
    protected override void OnUpdate()
    {
        base.OnUpdate();
        Console.WriteLine("PlayerIdleState.OnUpdate()");
    }
}

Y, por último, un ejemplo de cómo probar la herencia de lanzamiento de controlador de clase: ControllerTest.cs

class ControllerTest: Controller
{
    public ControllerTest()
    {
        IState testObj = new PlayerIdleState();
        testObj.Update();
    }
}

Espero cubrir todos los casos y mi respuesta fue útil.


Esta es una respuesta increíble que necesita más votos a favor.
KthProg

@max_cn Esta es una solución realmente buena, pero no puedo ver una manera de hacer que esto funcione con marcos de prueba para pruebas unitarias. ¿Alguna sugerencia?
Steve Land

Estoy un poco confundido. Si quisiera que una clase externa pudiera llamar a Update en su primer ejemplo (interfaz privada), ¿cómo modificaría el Controller para permitir que eso suceda?
AnthonyMonterrosa

@ Steve Land, puede usar la herencia como se muestra en ControllerTest.cs. Agregue los métodos públicos necesarios en una clase de prueba derivada y tendrá acceso para todo el personal protegido que tenga en la clase base.
max_cn

@AnthonyMonterrosa, estamos ocultando la Actualización de TODOS los recursos externos, entonces ¿por qué quieres compartirla con alguien? Claro que puede agregar algún método público para acceder a miembros privados, pero será como una puerta trasera para cualquier otra clase. De todos modos, si lo necesita para fines de prueba, use la herencia para hacer algo como la clase ControllerTest para hacer cualquier caso de prueba allí.
max_cn

9

Friend es extremadamente útil al escribir pruebas unitarias.

Si bien eso tiene un costo de contaminar ligeramente la declaración de su clase, también es un recordatorio forzado por el compilador de qué pruebas realmente podrían interesarle en el estado interno de la clase.

Un idioma muy útil y limpio que he encontrado es cuando tengo clases de fábrica, haciéndolos amigos de los artículos que crean que tienen un constructor protegido. Más específicamente, esto fue cuando tenía una única fábrica responsable de crear objetos de representación coincidentes para los objetos del escritor de informes, que representaban en un entorno determinado. En este caso, tiene un único punto de conocimiento sobre la relación entre las clases de redactor de informes (cosas como bloques de imágenes, bandas de diseño, encabezados de página, etc.) y sus objetos de representación coincidentes.


Iba a hacer un comentario sobre que "amigo" era útil para las clases de fábrica, pero me alegra ver que alguien ya lo ha hecho.
Edward Robertson

9

A C # le falta la palabra clave "amigo" por la misma razón que le falta la destrucción determinista. Cambiar las convenciones hace que las personas se sientan inteligentes, como si sus nuevas formas fueran superiores a las viejas. Se trata de orgullo.

Decir que "las clases de amigos son malas" es tan miope como otras declaraciones no calificadas como "no use gotos" o "Linux es mejor que Windows".

La palabra clave "amigo" combinada con una clase proxy es una excelente manera de exponer ciertas partes de una clase a otras clases específicas. Una clase proxy puede actuar como una barrera confiable contra todas las demás clases. "público" no permite ninguna focalización de este tipo, y usar "protegido" para obtener el efecto con la herencia es incómodo si realmente no existe una relación conceptual "es una".


A C # le falta destrucción determinista porque es más lento que la recolección de basura. La destrucción determinista lleva tiempo para cada objeto creado, mientras que la recolección de basura solo toma tiempo para los objetos actualmente activos. En la mayoría de los casos, esto es más rápido, y cuanto más objetos de corta duración hay en el sistema, la recolección de basura es relativamente más rápida.
Edward Robertson

1
@Edward Robertson La destrucción determinista no toma tiempo a menos que haya un destructor definido. Pero en este caso, la recolección de basura es mucho peor, porque entonces necesita usar un finalizador, que es más lento y no determinista.
kaalus

1
... y la memoria no es el único tipo de recurso. La gente a menudo actúa como si fuera verdad.
cubuspl42

8

Puede acercarse a C ++ "amigo" con la palabra clave C # "interno" .


3
Cerca, pero no del todo. Supongo que depende de cómo estructures tus conjuntos / clases, pero sería más elegante minimizar el número de clases a las que se les da acceso usando friend. Por ejemplo, en un conjunto de utilidad / ayuda, no todas las clases deberían tener acceso a miembros internos.
Ash el

3
@Ash: necesita acostumbrarse a él, pero una vez que ve a los ensamblajes como el componente fundamental de sus aplicaciones, internaltiene mucho más sentido y proporciona una excelente compensación entre la encapsulación y la utilidad. Sin embargo, el acceso predeterminado de Java es aún mejor.
Konrad Rudolph

7

Esto en realidad no es un problema con C #. Es una limitación fundamental en IL. C # está limitado por esto, al igual que cualquier otro lenguaje .Net que busca ser verificable. Esta limitación también incluye clases administradas definidas en C ++ / CLI ( Sección de especificaciones 20.5 ).

Dicho esto, creo que Nelson tiene una buena explicación de por qué esto es algo malo.


77
-1. friendno es algo malo por el contrario, es mucho mejor que internal. Y la explicación de Nelson es mala e incorrecta.
Nawaz

4

Deja de poner excusas para esta limitación. amigo es malo, pero interno es bueno? son lo mismo, solo que ese amigo te da un control más preciso sobre a quién se le permite acceder y quién no.

Esto es para hacer cumplir el paradigma de encapsulación? entonces tienes que escribir métodos de acceso y ahora ¿qué? ¿Cómo se supone que debes evitar que todos (excepto los métodos de la clase B) llamen a estos métodos? no puedes, porque tampoco puedes controlar esto, porque te falta "amigo".

Ningún lenguaje de programación es perfecto. C # es uno de los mejores lenguajes que he visto, pero poner excusas tontas para las funciones faltantes no ayuda a nadie. En C ++, extraño el sistema fácil de eventos / delegados, reflexión (+ des / serialización automática) y foreach, pero en C # extraño la sobrecarga del operador (sí, sigue diciéndome que no lo necesitas), parámetros predeterminados, una constante eso no se puede eludir, herencia múltiple (sí, sigue diciéndome que no lo necesitabas y las interfaces eran un reemplazo suficiente) y la capacidad de decidir eliminar una instancia de la memoria (no, esto no es terriblemente malo a menos que seas un tinkerer)


En C # puede sobrecargar la mayoría de los operadores. No puede sobrecargar los operadores de asignación, pero por otro lado puede anular ||/ &&mientras los mantiene en cortocircuito. Los parámetros predeterminados se agregaron en C # 4.
CodesInChaos

2

Existe el InternalsVisibleToAttribute desde .Net 3, pero sospecho que solo lo agregaron para atender a los ensambles de prueba después del aumento de las pruebas unitarias. No veo muchas otras razones para usarlo.

Funciona a nivel de ensamblaje pero hace el trabajo donde no lo hace el interno; es decir, donde desea distribuir un ensamblado pero desea que otro ensamblado no distribuido tenga acceso privilegiado a él.

Con toda razón, requieren que el conjunto de amigos tenga una clave fuerte para evitar que alguien cree un amigo simulado junto con su conjunto protegido.


2

He leído muchos comentarios inteligentes sobre la palabra clave "amigo" y estoy de acuerdo en lo que es útil, pero creo que la palabra clave "interna" es menos útil, y ambos siguen siendo malos para la programación pura de OO.

¿Que tenemos? (diciendo sobre "amigo" También digo sobre "interno")

  • está usando "amigo" hace que el código sea menos puro con respecto a oo?
  • si;

  • no está usando "amigo" hace que el código sea mejor?

  • no, todavía tenemos que establecer algunas relaciones privadas entre clases, y podemos hacerlo solo si rompemos nuestra hermosa encapsulación, por lo que tampoco es bueno, puedo decir que es aún más malo que usar "amigo".

Usar friend crea algunos problemas locales, no usarlo crea problemas para los usuarios de la biblioteca de códigos.

La buena solución común para el lenguaje de programación que veo así:

// c++ style
class Foo {
  public_for Bar:
    void addBar(Bar *bar) { }
  public:
  private:
  protected:
};

// c#
class Foo {
    public_for Bar void addBar(Bar bar) { }
}

¿Qué piensa usted al respecto? Creo que es la solución orientada a objetos más común y pura. Puede abrir el acceso a cualquier método que elija a la clase que desee.


No soy un centinela de OOP, pero parece que resuelve las quejas de todos contra amigos e internos. Pero sugeriría usar un atributo para evitar la extensión de la sintaxis de C #: [PublicFor Bar] void addBar (Bar bar) {} ... No estoy seguro de que tenga sentido; No sé cómo funcionan los atributos o si pueden jugar con la accesibilidad (hacen muchas otras cosas "mágicas").
Grault

2

Solo responderé a la pregunta "Cómo".

Hay tantas respuestas aquí, sin embargo, me gustaría proponer una especie de "patrón de diseño" para lograr esa característica. Usaré un mecanismo de lenguaje simple, que incluye:

  • Interfaces
  • Clase anidada

Por ejemplo, tenemos 2 clases principales: estudiante y universidad. El estudiante tiene un GPA al cual solo la universidad tiene acceso. Aquí está el código:

public interface IStudentFriend
{
    Student Stu { get; set; }
    double GetGPS();
}

public class Student
{
    // this is private member that I expose to friend only
    double GPS { get; set; }
    public string Name { get; set; }

    PrivateData privateData;

    public Student(string name, double gps) => (GPS, Name, privateData) = (gps, name, new PrivateData(this);

    // No one can instantiate this class, but Student
    // Calling it is possible via the IStudentFriend interface
    class PrivateData : IStudentFriend
    {
        public Student Stu { get; set; }

        public PrivateData(Student stu) => Stu = stu;
        public double GetGPS() => Stu.GPS;
    }

    // This is how I "mark" who is Students "friend"
    public void RegisterFriend(University friend) => friend.Register(privateData);
}

public class University
{
    var studentsFriends = new List<IStudentFriend>();

    public void Register(IStudentFriend friendMethod) => studentsFriends.Add(friendMethod);

    public void PrintAllStudentsGPS()
    {
        foreach (var stu in studentsFriends)
            Console.WriteLine($"{stu.Stu.Name}: stu.GetGPS()");
    }
}

public static void Main(string[] args)
{
    var Technion = new University();
    var Alex     = new Student("Alex", 98);
    var Jo       = new Student("Jo", 91);

    Alex.RegisterFriend(Technion);
    Jo.RegisterFriend(Technion);
    Technion.PrintAllStudentsGPS();

    Console.ReadLine();
}

1

Sospecho que tiene algo que ver con el modelo de compilación C #: construir IL el JIT compilando eso en tiempo de ejecución. es decir: la misma razón por la que los genéricos de C # son fundamentalmente diferentes a los genéricos de C ++.


Creo que el compilador podría soportarlo razonablemente fácilmente al menos entre clases dentro de un ensamblado. Solo es cuestión de relajar la comprobación de acceso para la clase de amigo en la clase objetivo. Tal vez un amigo entre clases en ensamblajes externos necesite cambios más fundamentales en el CLR.
Ash el

1

puede mantenerlo privado y usar la reflexión para llamar a las funciones. Test Framework puede hacer esto si le pide que pruebe una función privada


A menos que esté trabajando en una aplicación Silverlight.
kaalus

1

Solía ​​usar regularmente friend, y no creo que sea una violación de OOP o una señal de algún defecto de diseño. Hay varios lugares donde es el medio más eficiente para el fin adecuado con la menor cantidad de código.

Un ejemplo concreto es cuando se crean conjuntos de interfaz que proporcionan una interfaz de comunicaciones a otro software. En general, hay algunas clases de peso pesado que manejan la complejidad del protocolo y las peculiaridades de los pares, y proporcionan un modelo de conexión / lectura / escritura / reenvío / desconexión relativamente simple que implica pasar mensajes y notificaciones entre la aplicación del cliente y el ensamblado. Esos mensajes / notificaciones deben incluirse en clases. Los atributos generalmente deben ser manipulados por el software del protocolo, ya que es su creador, pero muchas cosas tienen que permanecer en modo de solo lectura para el mundo exterior.

Es simplemente una tontería declarar que es una violación de OOP que la clase de protocolo / "creador" tenga acceso íntimo a todas las clases creadas: la clase de creador ha tenido que manipular cada bit de datos en el camino. Lo que he encontrado más importante es minimizar todas las líneas adicionales de código BS al que suele conducir el modelo "OOP for OOP's Sake". Los espaguetis adicionales solo hacen más errores.

¿Sabe la gente que puede aplicar la palabra clave interna a nivel de atributo, propiedad y método? No es solo para la declaración de clase de nivel superior (aunque la mayoría de los ejemplos parecen mostrar eso).

Si tiene una clase C ++ que usa la palabra clave friend y desea emularla en una clase C #: 1. declare pública la clase C # 2. declare todos los atributos / propiedades / métodos que están protegidos en C ++ y, por lo tanto, accesibles para amigos como interno en C # 3. crea propiedades de solo lectura para el acceso público a todos los atributos y propiedades internos

Estoy de acuerdo en que no es 100% lo mismo que amigo, y la prueba unitaria es un ejemplo muy valioso de la necesidad de algo como amigo (como es el código de registro del analizador de protocolo). Sin embargo, interno proporciona la exposición a las clases que desea tener exposición, y [InternalVisibleTo ()] maneja el resto, parece que nació específicamente para la prueba unitaria.

En cuanto a amigo "ser mejor porque puedes controlar explícitamente qué clases tienen acceso", ¿qué diablos están haciendo un montón de clases malvadas sospechosas en la misma asamblea en primer lugar? ¡Particione sus ensamblajes!


1

La amistad se puede simular separando interfaces e implementaciones. La idea es: " Requerir una instancia concreta pero restringir el acceso de construcción de esa instancia ".

Por ejemplo

interface IFriend { }

class Friend : IFriend
{
    public static IFriend New() { return new Friend(); }
    private Friend() { }

    private void CallTheBody() 
    {  
        var body = new Body();
        body.ItsMeYourFriend(this);
    }
}

class Body
{ 
    public void ItsMeYourFriend(Friend onlyAccess) { }
}

A pesar de que ItsMeYourFriend()es público, solo la Friendclase puede acceder a él, ya que nadie más puede obtener una instancia concreta de la Friendclase. Tiene un constructor privado, mientras que el New()método de fábrica devuelve una interfaz.

Vea mi artículo Amigos y miembros de la interfaz interna sin costo alguno con la codificación de las interfaces para más detalles.


Lamentablemente, la amistad no se puede simular de ninguna manera posible. Vea esta pregunta: stackoverflow.com/questions/5921612/…
kaalus

1

Algunos han sugerido que las cosas pueden salirse de control usando un amigo. Estoy de acuerdo, pero eso no disminuye su utilidad. No estoy seguro de que ese amigo necesariamente dañe el paradigma OO más que hacer públicos a todos los miembros de su clase. Ciertamente, el lenguaje le permitirá hacer públicos todos sus miembros, pero es un programador disciplinado que evita ese tipo de patrón de diseño. Del mismo modo, un programador disciplinado se reservaría el uso de amigo para casos específicos en los que tiene sentido. Siento exposiciones internas demasiado en algunos casos. ¿Por qué exponer una clase o método a todo en el ensamblado?

Tengo una página ASP.NET que hereda mi propia página base, que a su vez hereda System.Web.UI.Page. En esta página, tengo un código que maneja los informes de errores del usuario final para la aplicación en un método protegido

ReportError("Uh Oh!");

Ahora, tengo un control de usuario que está contenido en la página. Quiero que el control del usuario pueda llamar a los métodos de informe de errores en la página.

MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");

No puede hacer eso si el método ReportError está protegido. Puedo hacerlo interno, pero está expuesto a cualquier código en el ensamblado. Solo quiero que esté expuesto a los elementos de la interfaz de usuario que forman parte de la página actual (incluidos los controles secundarios). Más específicamente, quiero que mi clase de control base defina exactamente los mismos métodos de informe de errores y simplemente llame a los métodos en la página base.

protected void ReportError(string str) {
    MyBasePage bp = Page as MyBasePage;
    bp.ReportError(str);
}

Creo que algo como amigo podría ser útil e implementado en el lenguaje sin hacer que el lenguaje sea menos "OO", tal vez como atributos, para que pueda hacer que las clases o métodos sean amigos de clases o métodos específicos, lo que le permite al desarrollador proporcionar acceso específico Quizás algo como ... (pseudocódigo)

[Friend(B)]
class A {

    AMethod() { }

    [Friend(C)]
    ACMethod() { }
}

class B {
    BMethod() { A.AMethod() }
}

class C {
    CMethod() { A.ACMethod() }
}

En el caso de mi ejemplo anterior, tal vez tenga algo como lo siguiente (uno puede discutir la semántica, pero solo estoy tratando de transmitir la idea):

class BasePage {

    [Friend(BaseControl.ReportError(string)]
    protected void ReportError(string str) { }
}

class BaseControl {
    protected void ReportError(string str) {
        MyBasePage bp = Page as MyBasePage;
        bp.ReportError(str);
    }
}

Desde mi punto de vista, el concepto de amigo no tiene más riesgo que hacerlo público o crear métodos o propiedades públicas para acceder a los miembros. Si algo amigo permite otro nivel de granularidad en la accesibilidad de los datos y le permite reducir esa accesibilidad en lugar de ampliarla con información interna o pública.


0

Si está trabajando con C ++ y se encuentra usando la palabra clave amigo, es una indicación muy fuerte, que tiene un problema de diseño, porque ¿por qué diablos una clase necesita acceder a los miembros privados de otra clase?


Estoy de acuerdo. Nunca necesito la palabra clave amigo. Generalmente se usa para resolver problemas complejos de mantenimiento.
Edouard A.

77
¿Sobrecarga del operador, alguien?
We Are All Monica

3
¿Cuántas veces encontraste un buen caso para la sobrecarga del operador en serio?
bashmohandes

8
Le daré un caso muy simple que desacredita por completo su comentario de 'problema de diseño'. Padre-hijo. Un padre es dueño de sus hijos. Un niño en la colección 'Hijos' de los padres es propiedad de ese padre. El setter de la propiedad Parent en Child no debe ser configurable por cualquiera. Debe asignarse cuando y solo cuando el niño se agrega a la colección Hijos de los padres. Si es público, cualquiera puede cambiarlo. Interno: cualquiera en esa asamblea. ¿Privado? Ninguno. Hacer que la acomodadora sea amiga de los padres elimina esos casos y aún mantiene sus clases separadas, pero estrechamente relacionadas. Huzzah !!
Mark A. Donohoe

2
Existen muchos casos de este tipo, como la mayoría de los patrones básicos de administrador / administrador. Hay otra pregunta sobre SO y nadie descubrió cómo hacerlo sin un amigo. No estoy seguro de qué hace que la gente piense que una clase "siempre será suficiente". A veces, más de una clase necesita trabajar juntos.
kaalus

0

Bsd

Se dijo que, los amigos duelen pura OOness. Que estoy de acuerdo.

También se dijo que los amigos ayudan a la encapsulación, lo que también estoy de acuerdo.

Creo que la amistad debería agregarse a la metodología OO, pero no exactamente como en C ++. Me gustaría tener algunos campos / métodos a los que mi clase amiga pueda acceder, pero NO me gustaría que accedan a TODOS mis campos / métodos. Como en la vida real, les permitía a mis amigos acceder a mi refrigerador personal, pero no les permitía acceder a mi cuenta bancaria.

Uno puede implementar eso de la siguiente manera

    class C1
    {
        private void MyMethod(double x, int i)
        {
            // some code
        }
        // the friend class would be able to call myMethod
        public void MyMethod(FriendClass F, double x, int i)
        {
            this.MyMethod(x, i);
        }
        //my friend class wouldn't have access to this method 
        private void MyVeryPrivateMethod(string s)
        {
            // some code
        }
    }
    class FriendClass
    {
        public void SomeMethod()
        {
            C1 c = new C1();
            c.MyMethod(this, 5.5, 3);
        }
    }

Eso, por supuesto, generará una advertencia del compilador y dañará el intellisense. Pero hará el trabajo.

En una nota al margen, creo que un programador seguro debe hacer la unidad de prueba sin acceder a los miembros privados. esto está bastante fuera del alcance, pero intente leer sobre TDD. sin embargo, si aún desea hacerlo (tener a C ++ como amigos) intente algo como

#if UNIT_TESTING
        public
#else
        private
#endif
            double x;

así que escribe todo su código sin definir UNIT_TESTING y cuando desea realizar la prueba de la unidad, agrega #define UNIT_TESTING a la primera línea del archivo (y escribe todo el código que realiza la prueba de la unidad en #if UNIT_TESTING). Eso debe manejarse con cuidado.

Como creo que las pruebas unitarias son un mal ejemplo para el uso de amigos, daría un ejemplo de por qué creo que los amigos pueden ser buenos. Supongamos que tiene un sistema de ruptura (clase). Con el uso, el sistema de ruptura se desgasta y necesita ser renovado. Ahora, desea que solo un mecánico con licencia lo arregle. Para hacer el ejemplo menos trivial, diría que el mecánico usaría su destornillador personal (privado) para arreglarlo. Es por eso que la clase mecánica debería ser amiga de la clase breakingSystem.


2
Ese parámetro FriendClass no sirve como control de acceso: puede llamar c.MyMethod((FriendClass) null, 5.5, 3)desde cualquier clase.
Jesse McGrew

0

La amistad también puede simularse mediante el uso de "agentes", algunas clases internas. Considere el siguiente ejemplo:

public class A // Class that contains private members
{
  private class Accessor : B.BAgent // Implement accessor part of agent.
  {
    private A instance; // A instance for access to non-static members.
    static Accessor() 
    { // Init static accessors.
      B.BAgent.ABuilder = Builder;
      B.BAgent.PrivateStaticAccessor = StaticAccessor;
    }
    // Init non-static accessors.
    internal override void PrivateMethodAccessor() { instance.SomePrivateMethod(); }
    // Agent constructor for non-static members.
    internal Accessor(A instance) { this.instance = instance; }
    private static A Builder() { return new A(); }
    private static void StaticAccessor() { A.PrivateStatic(); }
  }
  public A(B friend) { B.Friendship(new A.Accessor(this)); }
  private A() { } // Private constructor that should be accessed only from B.
  private void SomePrivateMethod() { } // Private method that should be accessible from B.
  private static void PrivateStatic() { } // ... and static private method.
}
public class B
{
  // Agent for accessing A.
  internal abstract class BAgent
  {
    internal static Func<A> ABuilder; // Static members should be accessed only by delegates.
    internal static Action PrivateStaticAccessor;
    internal abstract void PrivateMethodAccessor(); // Non-static members may be accessed by delegates or by overrideable members.
  }
  internal static void Friendship(BAgent agent)
  {
    var a = BAgent.ABuilder(); // Access private constructor.
    BAgent.PrivateStaticAccessor(); // Access private static method.
    agent.PrivateMethodAccessor(); // Access private non-static member.
  }
}

Podría ser mucho más simple cuando se usa para acceder solo a miembros estáticos. Los beneficios de dicha implementación es que todos los tipos se declaran en el ámbito interno de las clases de amistad y, a diferencia de las interfaces, permite el acceso a miembros estáticos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.