Quando e por que usar classes aninhadas?


30

Usando a Programação Orientada a Objetos, temos o poder de criar uma classe dentro de uma classe (uma classe aninhada), mas nunca criei uma classe aninhada nos meus 4 anos de experiência em codificação.
Para que servem as classes aninhadas?

Eu sei que uma classe pode ser marcada como privada se estiver aninhada e que podemos acessar todos os membros privados dessa classe a partir da classe que o contém. Poderíamos apenas colocar as variáveis ​​como privadas na própria classe que os contém.
Então, por que criar uma classe aninhada?

Em quais cenários as classes aninhadas devem ser usadas ou são mais poderosas em termos de uso sobre outras técnicas?


1
Você tem boas respostas e, às vezes, só tenho uma classe trabalhadora ou um suporte que eu só preciso dentro da classe.
28416 paparazzo

Respostas:


19

A principal característica das classes aninhadas é que elas podem acessar membros privados da classe externa enquanto possuem todo o poder de uma classe em si. Além disso, eles podem ser privados, o que permite um encapsulamento bastante poderoso em determinadas circunstâncias:

Aqui, travamos o setter completamente na fábrica, já que a classe é privada, nenhum consumidor pode fazer o downcast e acessar o setter, e podemos controlar completamente o que é 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};
}

Fora isso, é útil para implementar interfaces de terceiros em um ambiente controlado, onde ainda podemos acessar membros privados.

Se, por exemplo, forneçamos uma instância de alguma interface para outro objeto, mas não queremos que nossa classe principal o implemente, podemos permitir que uma classe interna o 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)); }
}

Na maioria dos casos eu esperaria delegados para ser uma maneira mais limpa de fazer o que você está mostrando no segundo exemplo
Ben Aaronson

@BenAaronson como você implementaria uma interface aleatória usando delegados?
Esben Skov Pedersen 30/03

@EsbenSkovPedersen bem para o seu exemplo, em vez de passar uma instância Outer, você iria passar por um Func<int>, que seria apenas() => _example
Ben Aaronson

@BenAaronson neste caso extremamente simples, você está certo, mas para exemplos mais complexos, muitos delegados se tornam desajeitados.
Esben Skov Pedersen 31/03

@EsbenSkovPedersen: Seu exemplo tem algum mérito, mas o IMO deve ser usado apenas nos casos em que a criação Innernão está aninhada e internalnão funciona (ou seja, quando você não está lidando com assemblies diferentes). A legibilidade atingida pelas classes de aninhamento o torna menos favorável do que o uso internal(sempre que possível).
Flater

23

Normalmente, uma classe aninhada N é criada dentro de uma classe C sempre que C precisa usar algo internamente que nunca deve ser (diretamente) usado fora de C, e por qualquer motivo que algo precise ser um novo tipo de objeto em vez de algum existente tipo.

Acredito que isso geralmente acontece implementando um método que retorna um objeto implementando alguma interface, e queremos manter o tipo concreto desse objeto oculto, porque não será útil em nenhum outro lugar.

A implementação de IEnumerable é um bom exemplo disso:

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

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

Simplesmente não há razão para alguém de fora BlobOfBusinessDatasaber ou se importar com o BusinessDatumEnumeratortipo de concreto , portanto é melhor mantê-lo dentro de casa BlobOfBusinessData.

Isso não era para ser um exemplo de "melhores práticas" de como implementar IEnumerablecorretamente, apenas o mínimo necessário para transmitir a idéia, então deixei de fora as coisas como um IEnumerable.GetEnumerator()método explícito .


6
Outro exemplo que eu uso com programadores mais recentes é uma Nodeclasse em a LinkedList. Qualquer pessoa que use o LinkedListnão se importa com a Nodeimplementação, desde que possa acessar o conteúdo. A única entidade que se importa é a LinkedListprópria classe.
Mage Xy

3

Então, por que criar uma classe aninhada?

Eu posso pensar em algumas razões importantes:

1. Ativar encapsulamento

Muitas vezes, as classes aninhadas são detalhes de implementação da classe. Os usuários da classe principal não devem se preocupar com sua existência. Você deve poder alterá-los à vontade, sem exigir que os usuários da classe principal alterem seu código.

2. Evite a poluição do nome

Você não deve adicionar tipos, variáveis, funções etc. em um escopo, a menos que sejam apropriados nesse escopo. Isso é ligeiramente diferente do encapsulamento. Pode ser útil expor a interface de um tipo aninhado, mas o local apropriado para o tipo aninhado ainda é a classe principal. No território C ++, os tipos de iteradores são um exemplo. Eu não sou experiente em C # o suficiente para lhe dar exemplos concretos.

Vamos criar um exemplo simplista para ilustrar por que mover uma classe aninhada para o mesmo escopo da classe principal é poluição por nome. Digamos que você esteja implementando uma classe de lista vinculada. Normalmente, você usaria

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

Se você decidir passar Nodepara o mesmo escopo LinkedList, você terá

public class LinkedListNode
{
}

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

LinkedListNodeé improvável que seja útil sem a LinkedListprópria classe. Mesmo que sejam LinkedListfornecidas algumas funções que retrocedam um LinkedListNodeobjeto que um usuário LinkedListpossa usar, ele ainda será LinkedListNodeútil apenas quando LinkedListfor usado. Por esse motivo, tornar a classe "node" uma classe LinkedListsemelhante está poluindo o escopo que a contém.


0
  1. Eu uso classes públicas aninhadas para classes 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. Use-os para variações 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();

O chamador pode escolher qual versão é adequada para qual problema. Às vezes, uma classe não pode ser totalmente construída em uma única passagem e requer uma versão mutável. Isso sempre é verdade ao manipular dados cíclicos. Mutables podem ser convertidos para somente leitura posteriormente.

  1. Eu os 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; }
    }

0

A classe aninhada pode ser usada sempre que você desejar criar mais de uma instância da classe ou sempre que desejar tornar esse tipo mais disponível.

A classe aninhada aumenta os encapsulamentos e também leva a códigos mais legíveis e de manutenção.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.