¿Cuándo deberías usar una clase privada / interna?

21

Para aclarar, lo que estoy preguntando es

public class A{
    private/*or public*/ B b;
}

vs.

public class A{
    private/*or public*/ class B{
        ....
    }
}

Definitivamente puedo pensar en algunas razones para usar una u otra, pero lo que realmente me gustaría ver son ejemplos convincentes que muestran que los pros y los contras no son solo académicos.

EpsilonVector
fuente
2
La respuesta dependería de las facilidades proporcionadas por el idioma y las bibliotecas principales. Suponiendo el lenguaje C #, intente ejecutarlos con StyleCop. Estoy bastante seguro de que recibirá una advertencia sobre la separación de esta clase en su propio archivo. Eso significa que suficientes personas en MSFT piensan que no necesita usar ninguno de los ejemplos que ha usado.
Trabajo
¿Cuál sería un ejemplo convincente si los ejemplos académicos no son lo suficientemente buenos?
@ Job StyleCop solo dará una advertencia si las clases internas son visibles desde el exterior. Si es privado, está completamente bien y en realidad tiene muchos usos.
julealgon

Respuestas:

20

Por lo general, se usan cuando una clase es un detalle de implementación interna de otra clase en lugar de una parte de su interfaz externa. Principalmente los he visto como clases de solo datos que hacen que las estructuras de datos internas sean más limpias en lenguajes que no tienen una sintaxis separada para estructuras de estilo C. También son útiles a veces para objetos altamente personalizados derivados de un método, como controladores de eventos o subprocesos de trabajo.

Karl Bielefeldt
fuente
2
Así es como los uso. Si una clase es, desde un punto de vista funcional, nada más que un detalle de implementación privada de otra clase, entonces debería declararse de esa manera, tal como lo sería un método o campo privado. No hay razón para contaminar el espacio de nombres con basura que nunca se usará en otro lugar.
Aaronaught
9

Supongamos que está construyendo un árbol, una lista, un gráfico, etc. ¿Por qué debería exponer los detalles internos de un nodo o una célula al mundo externo?

Cualquiera que use un gráfico o una lista solo debe confiar en su interfaz, no en su implementación, ya que es posible que desee cambiarlo algún día en el futuro (por ejemplo, de una implementación basada en matriz a una basada en puntero) y los clientes que usan su estructura de datos ( cada uno de ellos ) tendría que modificar su código para adecuarlo a la nueva implementación.

En cambio, encapsular la implementación de un nodo o una celda en una clase interna privada le da la libertad de modificar la implementación en cualquier momento que lo necesite, sin que los clientes se vean obligados a ajustar su código en consecuencia, siempre que la interfaz de su estructura de datos permanezca intacto

Ocultar los detalles de la implementación de su estructura de datos también conlleva ventajas de seguridad, ya que si desea distribuir su clase, solo pondrá a disposición el archivo de interfaz junto con el archivo de implementación compilado y nadie sabrá si realmente está utilizando matrices o punteros para su implementación, protegiendo así su aplicación de algún tipo de explotación o, al menos, conocimiento debido a la imposibilidad de mal uso o inspección de su código. Además de los problemas prácticos, no subestimes el hecho de que es una solución extremadamente elegante en tales casos.

Nicola Lamonaca
fuente
5

En mi opinión, es un policía justo usar clases definidas internamente para clases que se usan brevemente en una clase para permitir una tarea específica.

Por ejemplo, si necesita vincular una lista de datos que consta de dos clases no relacionadas:

public class UiLayer
{
    public BindLists(List<A> as, List<B> bs)
    {
        var list = as.ZipWith(bs, (x, y) => new LayerListItem { AnA = x, AB = y});
        // do stuff with your new list.
    }

    private class LayerListItem
    {
        public A AnA;
        public B AB;
    }
}

Si su clase interna es utilizada por otra clase, debe ponerla por separado. Si su clase interna contiene alguna lógica, debe ponerla por separado.

Básicamente, creo que son excelentes para tapar agujeros en sus objetos de datos, pero son difíciles de mantener si realmente tienen que contener lógica, ya que tendrá que saber dónde buscarlos si necesita cambiarlos.

Ed James
fuente
2
Suponiendo C #, ¿no puedes usar tuplas aquí?
Trabajo
3
@ Job Seguro, pero a veces tuple.ItemXel código no está claro.
Lasse Espeholt 05 de
2
@ Job, sí, lasseespeholt acertó. Prefiero ver una clase interna de {string Title; Descripción de la cadena; } que una Tupla <cadena, cadena>.
Ed James
en ese punto, ¿no tendría sentido poner esa clase en su propio archivo?
Trabajo
No, no si solo lo estás usando realmente para unir algunos datos brevemente para lograr una tarea que no está fuera del alcance de la clase principal. ¡Al menos no en mi opinión!
Ed James
4
  • Las clases internas en algún lenguaje (Java, por ejemplo) tienen un vínculo con un objeto de su clase que contiene y pueden usar sus miembros sin calificarlos. Cuando está disponible, es más claro que reimplementarlos usando otras facilidades de lenguaje.

  • Las clases anidadas en otro lenguaje (C ++ por ejemplo) no tienen ese vínculo. Simplemente está utilizando el alcance y el control de accesibilidad proporcionados por la clase.

Un programador
fuente
3
En realidad, Java tiene ambos .
Joachim Sauer
3

Evito las clases internas en Java porque el intercambio en caliente no funciona en presencia de clases internas. Odio esto porque realmente odio comprometer el código por tales consideraciones, pero esos reinicios de Tomcat se suman.

Kevin Cline
fuente
¿Está este problema documentado en alguna parte?
mosquito
Downvoter: ¿es incorrecta mi afirmación?
Kevin Cline
1
Voté en contra porque su respuesta carece de detalles, no porque sea incorrecta. La falta de detalles hace que su afirmación sea difícil de verificar. Si agrega más sobre ese tema, probablemente volvería a votar a favor, porque su información parece bastante interesante y puede ser útil
mosquito
1
@gnat: Investigué un poco y, aunque parece ser una verdad bien conocida, no encontré una referencia definitiva sobre las limitaciones de Java HotSwap.
Kevin Cline
no estamos en Skeptics.SE :) solo haría alguna referencia, no tiene por qué ser definitivo
mosquito
2

Uno de los usos de la clase interna estática privada es el patrón Memo. Coloca todos los datos que deben recordarse en una clase privada y los devuelve desde alguna función como Object. Nadie afuera no puede (sin reflexión, deserialización, inspección de memoria ...) mirarlo / modificarlo, pero él puede devolverlo a su clase y puede restaurar su estado.

user470365
fuente
2

Una razón para usar una clase interna privada podría ser porque una API que está utilizando requiere herencia de una clase particular, pero no desea exportar el conocimiento de esa clase a los usuarios de su clase externa. Por ejemplo:

// async_op.h -- someone else's api.
struct callback
{
    virtual ~callback(){}
    virtual void fire() = 0;
};

void do_my_async_op(callback *p);



// caller.cpp -- my api
class caller
{
private :
    struct caller_inner : callback
    {
        caller_inner(caller &self) : self_(self) {}
        void fire() { self_.do_callback(); }
        caller &self_;
    };

    void do_callback()
    {
        // Real callback
    }

    caller_inner inner_;

public :
    caller() : inner_(*this) {}

    void do_op()
    {
        do_my_async_op(&inner_);
    }
};

En esto, do_my_async_op requiere que se le pase un objeto de devolución de llamada de tipo. Esta devolución de llamada tiene una firma de función de miembro público que utiliza la API.

Cuando se llama a do_op () desde la clase externa, utiliza una instancia de la clase interna privada que hereda de la clase de devolución de llamada requerida en lugar de un puntero a sí mismo. La clase externa tiene una referencia a la clase externa con el simple propósito de desviar la devolución de llamada a la función de miembro privado do_callback de la clase externa .

El beneficio de esto es que está seguro de que nadie más puede llamar a la función miembro pública "fire ()".

Dragón Kaz
fuente
2

Según Jon Skeet en C # en profundidad, el uso de una clase anidada era la única forma de implementar un singleton totalmente seguro para subprocesos (ver Quinta versión) (hasta .NET 4).

Scott Whitlock
fuente
1
es el idioma de Initialization On Demand Holder , en Java también requiere una clase interna estática privada. Se recomienda en las preguntas frecuentes de JMM , por Brian Goetz en JCiP 16.4 y por Joshua Bloch en JavaOne 2008 Java más eficaz (pdf) "Para un alto rendimiento en un campo estático ..."
mosquito
1

Las clases privadas e internas se utilizan para aumentar los niveles de encapsulación y ocultar detalles de implementación.

En C ++, además de una clase privada, se puede lograr el mismo concepto implementando el espacio de nombres anónimo de una clase cpp. Esto sirve muy bien para ocultar / privatizar un detalle de implementación.

Es la misma idea que una clase interna o privada, pero a un nivel de encapsulación aún mayor, ya que es completamente invisible fuera de la unidad de compilación del archivo. Nada de eso aparece en el archivo de encabezado o es visible en la declaración de la clase.

hiwaylon
fuente
Cualquier comentario constructivo sobre el -1?
hiwaylon
1
No voté en contra, pero supongo que realmente no estás respondiendo la pregunta: no estás dando ninguna razón para usar la clase privada / interna. Solo está describiendo cómo funciona y cómo hacer algo similar en c ++
Simon Bergot
En efecto. Pensé que era obvio por el mío y las otras respuestas (ocultar detalles de implementación, mayores niveles de encapsulación, etc.) pero intentaré aclarar algunas. Gracias @ Simon.
hiwaylon
1
Buena edición. Y por favor no se ofenda por este tipo de reacción. La red SO está tratando de aplicar políticas para mejorar la relación señal / ruido. Algunas personas son bastante estrictas con respecto al alcance de las preguntas y respuestas, y a veces se olvidan de ser amables (al comentar su voto negativo, por ejemplo)
Simon Bergot el
@ Simon Gracias. Es decepcionante debido a la mala calidad de comunicación que exhibe. ¿Quizás es un poco demasiado fácil ser "estricto" detrás del velo anónimo de la web? Oh bueno, nada nuevo, supongo. Gracias de nuevo por los comentarios positivos.
hiwaylon