¿Cuándo y por qué usar clases anidadas?

29

Usando la Programación Orientada a Objetos tenemos el poder de crear una clase dentro de una clase (una clase anidada), pero nunca he creado una clase anidada en mis 4 años de experiencia en codificación.
¿Para qué sirven las clases anidadas?

Sé que una clase se puede marcar como privada si está anidada y que podemos acceder a todos los miembros privados de esa clase desde la clase que la contiene. Podríamos poner las variables como privadas en la clase que las contiene.
Entonces, ¿por qué crear una clase anidada?

¿En qué escenarios deberían usarse las clases anidadas o son más poderosas en términos de uso que otras técnicas?

mayur rathi
fuente
1
Tienes algunas buenas respuestas y a veces solo tendré una clase trabajadora o un puntal que solo necesito dentro de la clase.
paparazzo

Respuestas:

19

La característica principal de las clases anidadas es que pueden acceder a miembros privados de la clase externa mientras tienen todo el poder de una clase. También pueden ser privados, lo que permite una encapsulación bastante poderosa en ciertas circunstancias:

Aquí bloqueamos el setter completamente a la fábrica ya que la clase es privada, ningún consumidor puede rechazarlo y acceder al setter, y podemos controlar completamente lo que está permitido.

public interface IFoo 
{
    int Foo{get;}      
}
public class Factory
{
    private class MyFoo : IFoo
    {
        public int Foo{get;set;}
    }
    public IFoo CreateFoo(int value) => new MyFoo{Foo = value};
}

Aparte de eso, es útil para implementar interfaces de terceros en un entorno controlado donde todavía podemos acceder a miembros privados.

Si, por ejemplo, proporcionáramos una instancia de alguna interfaz a otro objeto, pero no queremos que nuestra clase principal lo implemente, podríamos permitir que una clase interna lo implemente.

public class Outer
{
    private int _example;
    private class Inner : ISomeInterface
    {
        Outer _outer;
        public Inner(Outer outer){_outer = outer;}
        public int DoStuff() => _outer._example;
    }
    public void DoStuff(){_someDependency.DoBar(new Inner(this)); }
}
Esben Skov Pedersen
fuente
En la mayoría de los casos, esperaría que los delegados sean una forma más limpia de hacer lo que está mostrando en el segundo ejemplo
Ben Aaronson el
@BenAaronson, ¿cómo implementaría una interfaz aleatoria utilizando delegados?
Esben Skov Pedersen
@EsbenSkovPedersen Bueno, por ejemplo, en lugar de pasar una instancia de Outer, pasarías un Func<int>, que sería() => _example
Ben Aaronson el
@BenAaronson en este caso extremadamente simple tiene razón, pero para ejemplos más complejos, demasiados delegados se vuelven torpes.
Esben Skov Pedersen
@EsbenSkovPedersen: su ejemplo tiene algo de mérito, pero la OMI solo debe usarse en casos en los que hacer que Innerno internalesté anidado y no funcione (es decir, cuando no se trata de ensamblajes diferentes). El impacto de legibilidad de las clases de anidamiento lo hace menos favorable que usar internal(cuando sea posible).
Flater
23

Típicamente, una clase N anidada se crea dentro de una clase C cuando C necesita usar algo interno que nunca debe usarse (directamente) fuera de C, y por cualquier razón, algo debe ser un nuevo tipo de objeto en lugar de algo existente tipo.

Creo que esto sucede con mayor frecuencia al implementar un método que devuelve un objeto que implementa alguna interfaz, y queremos mantener oculto el tipo concreto de ese objeto porque no será útil en ningún otro lugar.

Implementar IEnumerable es un buen ejemplo de esto:

class BlobOfBusinessData: IEnumerable<BusinessDatum>
{
    public IEnumerator<BusinessDatum> GetEnumerator()
    {
         return new BusinessDatumEnumerator(...);
    }

    class BusinessDatumEnumerator: IEnumerator<BusinessDatum>
    {
        ...
    }
}

Simplemente no hay ninguna razón para que alguien externo BlobOfBusinessDatasepa o se preocupe por el BusinessDatumEnumeratortipo de concreto , por lo que podríamos mantenerlo adentro BlobOfBusinessData.

Ese no fue un ejemplo de "mejores prácticas" de cómo implementarlo IEnumerablecorrectamente, solo el mínimo para transmitir la idea, así que omití cosas como un IEnumerable.GetEnumerator()método explícito .

Ixrec
fuente
66
Otro ejemplo que uso con los programadores más nuevos es una Nodeclase en a LinkedList. A cualquiera que use el LinkedListno le importa cómo Nodese implementa, siempre que pueda acceder a los contenidos. La única entidad a la que le importa es la LinkedListclase misma.
Mage Xy
3

Entonces, ¿por qué crear una clase anidada?

Puedo pensar en un par de razones importantes:

1. Habilitar la encapsulación

Muchas veces las clases anidadas son detalles de implementación de la clase. Los usuarios de la clase principal no deberían tener que preocuparse por su existencia. Debería poder cambiarlos a voluntad sin requerir que los usuarios de la clase principal cambien su código.

2. Evitar la contaminación del nombre

No debe agregar tipos, variables, funciones, etc. en un ámbito a menos que sean apropiados en ese ámbito. Esto es ligeramente diferente de la encapsulación. Podría ser útil exponer la interfaz de un tipo anidado, pero el lugar adecuado para el tipo anidado sigue siendo la clase principal. En C ++ land, los tipos de iterador son un ejemplo de ello. No tengo experiencia en C # lo suficiente como para darle ejemplos concretos.

Hagamos un ejemplo simplista para ilustrar por qué mover una clase anidada al mismo alcance que la clase principal es la contaminación del nombre. Digamos que está implementando una clase de lista vinculada. Normalmente, usarías

publid class LinkedList
{
   class Node { ... }
   // Use Node to implement the LinkedList class.
}

Si decide Nodesubir al mismo alcance que LinkedList, tendrá

public class LinkedListNode
{
}

public class LinkedList
{
  // Use LinkedListNode to implement the class
}

LinkedListNodees poco probable que sea útil sin la LinkedListclase misma. Incluso si se LinkedListproporcionan algunas funciones que devuelven un LinkedListNodeobjeto que un usuario LinkedListpodría usar, sigue siendo LinkedListNodeútil solo cuando LinkedListse usa. Por esa razón, hacer que la clase "nodo" sea una clase de igual LinkedListes contaminar el alcance que contiene.

R Sahu
fuente
0
  1. Utilizo clases públicas anidadas para clases auxiliares relacionadas.

    public class MyRecord {
        // stuff
        public class Comparer : IComparer<MyRecord> {
        }
        public class EqualsComparer : IEqualsComparer<MyRecord> {
        }
    }
    MyRecord[] array;
    Arrays.sort(array, new MyRecord.Comparer());
  2. Úselos para variaciones relacionadas.

    // Class that may or may not be mutable.
    public class MyRecord {
        protected string name;
        public virtual String Name { get => name; set => throw new InvalidOperation(); }
    
        public Mutable {
            public override String { get => name; set => name = value; }
        }
    }
    
    MyRecord mutableRecord = new MyRecord.Mutable();

La persona que llama puede elegir qué versión es adecuada para cada problema. A veces, una clase no se puede construir completamente en una sola pasada y requiere una versión mutable. Esto siempre es cierto cuando se manejan datos cíclicos. Las mutables se pueden convertir a solo lectura más tarde.

  1. Los uso para registros internos

    public class MyClass {
        List<Line> lines = new List<Line>();
    
        public void AddUser( string name, string address ) => lines.Add(new Line { Name = name, Address = address });
    
        class Line { string Name; string Address; }
    }
Etiqueta
fuente
0

La clase anidada se puede usar siempre que desee crear más de una instancia de la clase o cuando desee que ese tipo esté más disponible.

La clase anidada aumenta las encapsulaciones y dará lugar a un código más fácil de leer y mantener.

Ishan Shah
fuente