Por que os campos privados são privados para o tipo, não a instância?

153

Em C # (e em muitos outros idiomas), é perfeitamente legítimo acessar campos particulares de outras instâncias do mesmo tipo. Por exemplo:

public class Foo
{
    private bool aBool;

    public void DoBar(Foo anotherFoo)
    {
        if (anotherFoo.aBool) ...
    }
}

Como a especificação C # (seções 3.5.1, 3.5.2) afirma que o acesso a campos privados é de um tipo, não de uma instância. Estive discutindo isso com um colega e estamos tentando descobrir uma razão pela qual funciona dessa maneira (em vez de restringir o acesso à mesma instância).

O melhor argumento que poderíamos apresentar é para verificações de igualdade, onde a classe pode querer acessar campos privados para determinar a igualdade com outra instância. Existem outras razões? Ou alguma razão de ouro que significa absolutamente que deve funcionar assim ou algo seria completamente impossível?

RichK
fonte
18
Quais são os benefícios para a alternativa?
Jon Skeet
19
É verdade que não modela muito bem o mundo real. Afinal, só porque o meu carro pode relatar a quantidade de gasolina que resta, não significa que meu carro possa saber quanto gás CADA carro resta. Então, sim, eu posso teoricamente ver ter um acesso de "instância privada". Eu sinto que eu realmente nunca iria usá-lo, porque eu estaria preocupado que, em 99% dos casos, eu realmente gostaria de ter outra instância acessar essa variável.
aquinas
2
@ Jon A posição adotada para os chamados 'benefícios' é que as classes não devem conhecer o estado interno de outra instância - independentemente do fato de ser do mesmo tipo. Não é um benefício por dizer, mas provavelmente há uma boa razão bastante para escolher um sobre o outro e isso é o que temos vindo a tentar descobrir
RichK
4
Você sempre pode fazer a variável em um par de encerramentos de apanhador / definidor, inicializado no construtor, que irá falhar se esta não é a mesma como foi durante a inicialização ...
Martin Sojka
8
O Scala fornece membros privados da instância - junto com muitos outros níveis de acesso não encontrados em Java, C # e muitas outras linguagens. Esta é uma pergunta perfeitamente legítima.
Bruno Reis

Respostas:

73

Eu acho que uma razão pela qual funciona dessa maneira é porque os modificadores de acesso funcionam em tempo de compilação . Portanto, não é fácil determinar se um determinado objeto é ou não o objeto atual . Por exemplo, considere este código:

public class Foo
{
    private int bar;

    public void Baz(Foo other)
    {
        other.bar = 2;
    }

    public void Boo()
    {
        Baz(this);
    }
}

O compilador pode necessariamente descobrir o que otheré realmente this? Nem em todos os casos. Alguém poderia argumentar que isso não deveria ser compilado, mas isso significa que temos um caminho de código em que um membro da instância privada da instância correta não está acessível, o que eu acho ainda pior.

Somente a exigência de visibilidade em nível de tipo, em vez de visibilidade em nível de objeto, garante que o problema seja tratável, além de fazer com que uma situação que pareça que deveria funcionar realmente funcione.

EDIT : O ponto de Danilel Hilgarth de que esse raciocínio é inverso tem mérito. Os designers de idiomas podem criar o idioma que desejam, e os escritores do compilador devem estar em conformidade com ele. Dito isto, os designers de linguagem têm algum incentivo para facilitar o trabalho dos escritores de compiladores. (Embora neste caso, seja fácil argumentar que membros privados poderiam ser acessados ​​via this(implícita ou explicitamente)).

No entanto, acredito que isso torna o assunto mais confuso do que precisa ser. A maioria dos usuários (inclusive eu) acharia isso desnecessariamente limitador se o código acima não funcionasse: afinal, esses são meus dados que estou tentando acessar! Por que eu deveria passar this?

Em resumo, acho que exagero o caso de ser "difícil" para o compilador. O que eu realmente quis dizer é que a situação acima parece ser aquela que os designers gostariam de ter trabalho.

dlev
fonte
33
IMHO, esse raciocínio é o contrário. O compilador impõe as regras do idioma. Não os faz. Em outras palavras, se os membros privados fossem "instância privada" e não "tipo privado", o compilador teria sido implementado de outras maneiras, por exemplo, apenas permitindo, this.bar = 2mas não other.bar = 2, porque otherpoderia ser uma instância diferente.
Daniel Hilgarth
@ Daniel Esse é um bom argumento, mas como mencionei, acho que ter essa restrição seria pior do que ter visibilidade no nível de classe.
dlev
3
@dlev: Eu não disse nada sobre se acho ou não membros de "instância privada" que são bons. :-) Eu só queria ressaltar que você está argumentando que uma implementação técnica dita as regras da linguagem e que, na minha opinião, essa argumentação não está correta.
precisa saber é o seguinte
2
@Daniel Point tomado. Ainda acho que essa é uma consideração válida, embora você esteja certo de que os designers de linguagem descobrem o que querem e os escritores do compilador estão em conformidade com isso.
dlev
2
Eu não acho que o argumento do compilador seja válido. Existem idiomas ao redor que permitem apenas instância-privada (como Ruby) e que permitem instância-privada e classe-privada por opção (como Eiffel). A geração do compilador não era necessária mais difícil ou mais fácil para esses idiomas. Para mais informações, consulte também a minha resposta no tópico.
Abel
61

Como o objetivo do tipo de encapsulamento usado em C # e linguagens similares * é diminuir a dependência mútua de diferentes partes do código (classes em C # e Java), não objetos diferentes na memória.

Por exemplo, se você escrever código em uma classe que usa alguns campos em outra classe, essas classes serão fortemente acopladas. No entanto, se você estiver lidando com código no qual possui dois objetos da mesma classe, não haverá dependência extra. Uma classe sempre depende de si mesma.

No entanto, toda essa teoria sobre o encapsulamento falha assim que alguém cria propriedades (ou obtém / define pares em Java) e expõe todos os campos diretamente, o que torna as classes tão acopladas como se estivessem acessando os campos de qualquer maneira.

* Para esclarecimentos sobre os tipos de encapsulamento, consulte a excelente resposta de Abel.

Goran Jovic
fonte
3
Eu não acho que falha quando os get/setmétodos são fornecidos. Os métodos do acessador ainda mantêm uma abstração (o usuário não precisa saber que há um campo por trás dele ou quais campos podem estar por trás da propriedade). Por exemplo, se estivermos escrevendo uma Complexclasse, podemos expor propriedades para obter / definir as coordenadas polares e outro obter / definir para coordenadas cartesianas. Qual delas a classe usa abaixo é desconhecida para o usuário (pode até ser algo totalmente diferente).
Ken Wayne Vanderlinde
5
@ Ken: Ele falha se você fornecer uma propriedade para cada campo que você possui, sem sequer considerar se deve ser exposto.
Goran Jovic
@ Koran Embora eu concorde que isso não seria o ideal, ele ainda não falha completamente, pois os acessadores get / set permitem adicionar lógica adicional se os campos subjacentes mudarem, por exemplo.
ForbesLindesay
1
"Como o objetivo do encapsulamento é diminuir a dependência mútua de diferentes partes do código" >> no. Encapsulamento também pode significar encapsulamento de instância, depende do idioma ou da escola de pensamento. Uma das vozes mais definitivas no OO, Bertrand Meyer, considera isso mesmo o padrão para private. Você pode verificar minha resposta para ver minha opinião sobre as coisas, se quiser;).
Abel
@Abel: Baseei minha resposta em idiomas com encapsulamento de classe porque o OP perguntou sobre um deles e, francamente, porque eu os conheço. Obrigado pela correção e por novas informações interessantes!
Goran Jovic
49

Algumas respostas já foram adicionadas a esse segmento interessante, no entanto, eu não encontrei a verdadeira razão pela qual esse comportamento é do jeito que é. Deixe-me tentar:

Antigamente

Em algum lugar entre Smalltalk nos anos 80 e Java em meados dos anos 90, o conceito de orientação a objetos amadureceu. A ocultação de informações, não originalmente pensada como um conceito disponível apenas para OO (mencionado primeiro em 1978), foi introduzida no Smalltalk, pois todos os dados (campos) de uma classe são privados, todos os métodos são públicos. Durante os muitos novos desenvolvimentos de OO nos anos 90, Bertrand Meyer tentou formalizar muitos dos conceitos de OO em seu livro de referência Object Oriented Software Construction (OOSC), que desde então é considerado uma referência (quase) definitiva sobre conceitos e design de linguagem de OO .

No caso de visibilidade privada

De acordo com Meyer, um método deve ser disponibilizado para um conjunto definido de classes (página 192-193). Obviamente, isso oferece uma granularidade muito alta de ocultação de informações; o seguinte recurso está disponível para classe A e classe B e todos os seus descendentes:

feature {classA, classB}
   methodName

No caso de privateele diz o seguinte: sem declarar explicitamente um tipo como visível para sua própria classe, você não pode acessar esse recurso (método / campo) em uma chamada qualificada. Ou seja, se xé uma variável, x.doSomething()não é permitido. O acesso não qualificado é permitido, é claro, dentro da própria classe.

Em outras palavras: para permitir o acesso por uma instância da mesma classe, você deve permitir explicitamente o acesso ao método por essa classe. Às vezes, isso é chamado de instância-privada versus classe-privada.

Privado da instância em linguagens de programação

Conheço pelo menos dois idiomas atualmente em uso que usam informações privadas de instância ocultas, em oposição a informações privadas de classe. Uma é a Eiffel, uma linguagem projetada por Meyer, que leva o OO ao extremo. O outro é Ruby, uma linguagem muito mais comum atualmente. Em Ruby, privatesignifica: "privado para esta instância" .

Opções para design de linguagem

Foi sugerido que permitir o privado da instância seria difícil para o compilador. Acho que não, pois é relativamente simples apenas permitir ou proibir chamadas qualificadas a métodos. Se, para um método privado, doSomething()é permitido e x.doSomething()não é, um designer de linguagem definiu efetivamente acessibilidade somente de instância para métodos e campos privados.

Do ponto de vista técnico, não há razão para escolher uma maneira ou de outra (especialmente quando se considera que o Eiffel.NET pode fazer isso com IL, mesmo com herança múltipla, não há razão inerente para não fornecer esse recurso).

Obviamente, é uma questão de gosto e, como outros já mencionaram, alguns métodos podem ser mais difíceis de escrever sem o recurso de visibilidade no nível de classe de métodos e campos privados.

Por que o C # permite apenas o encapsulamento de classe e não o encapsulamento de instância

Se você observar os encadeamentos da Internet no encapsulamento de instância (um termo às vezes usado para se referir ao fato de que uma linguagem define os modificadores de acesso no nível da instância, em oposição ao nível da classe), o conceito geralmente é desaprovado. No entanto, considerando que algumas linguagens modernas usam encapsulamento de instância, pelo menos para o modificador de acesso privado, você pensa que pode ser e é útil no mundo da programação moderna.

No entanto, o C # reconheceu com mais atenção o C ++ e o Java por seu design de linguagem. Enquanto Eiffel e Modula-3 também estavam na imagem, considerando os muitos recursos de Eiffel ausentes (herança múltipla), acredito que eles escolheram a mesma rota que Java e C ++ quando se tratava do modificador de acesso privado.

Se você realmente quer saber por que deve tentar contatar Eric Lippert, Krzysztof Cwalina, Anders Hejlsberg ou qualquer outra pessoa que trabalhou no padrão de C #. Infelizmente, não consegui encontrar uma nota definitiva na Linguagem de Programação C # anotada .

Abel
fonte
1
Isso é uma bela resposta com um monte de fundo, muito interessantes, obrigado por tomar o tempo para responder a uma pergunta (relativamente) de idade
RichK
1
@RichK: Você é bem-vindo, apenas pegou a pergunta tarde demais, eu acho, mas não encontrou o tópico intrigante o suficiente para responder com alguma profundidade :)
Abel
No COM, "privado" significava "instância-privada". Eu acho que isso foi em certa medida porque uma definição de classe Fooefetivamente representava uma interface e uma classe de implementação; como o COM não tinha um conceito de referência de objeto de classe - apenas uma referência de interface - não havia como uma classe manter uma referência a algo que era garantido como outra instância dessa classe. Esse design baseado em interface tornava algumas coisas complicadas, mas significava que era possível projetar uma classe que fosse substituída por outra sem que fosse necessário compartilhar os mesmos internos.
Supercat
2
Ótima resposta. O OP deve considerar marcar esta pergunta como a resposta.
Gavin Osborn
18

Esta é apenas minha opinião, mas pragmaticamente, acho que se um programador tiver acesso à fonte de uma classe, você poderá razoavelmente confiar neles acessando os membros privados da instância de classe. Por que prender um programador na mão direita quando, à esquerda, você já lhes deu as chaves do reino?

FishBasketGordo
fonte
13
Eu não entendo esse argumento. Digamos que você esteja trabalhando em um programa no qual você é o único desenvolvedor e você é o único que NUNCA usaria suas bibliotecas, você está dizendo que nesse caso você torna TODOS os membros públicos porque tem acesso ao código-fonte?
aquinas 08/08
@aquinas: isso é bem diferente. Tornar tudo público levará a práticas de programação horríveis. Permitir que um tipo veja campos particulares de outras instâncias em si é um caso muito restrito.
Igby Largeman
2
Não, não estou defendendo a divulgação de todos os membros. Céus, não! Meu argumento é que, se você já está na fonte, pode ver os membros privados, como eles são usados, convenções empregadas etc., então por que não ser capaz de usar esse conhecimento?
FishBasketGordo
7
Eu acho que a maneira de pensar ainda está nos termos do tipo. Se não é possível confiar no tipo para lidar adequadamente com os membros do tipo, o que pode? ( Normalmente , seria um programador realmente controlar as interações, mas nesta época de ferramentas de geração de código, que pode não ser sempre o caso.)
Anthony Pegram
3
Minha pergunta não é realmente de confiança, mais de necessidade. A confiança é totalmente irrelevante quando você pode usar a reflexão para coisas hediondas para os particulares. Estou me perguntando por que, conceitualmente, você pode acessar as partes privadas. Presumi que havia uma razão lógica e não uma situação de melhores práticas.
RichK 08/08
13

O motivo é realmente verificação de igualdade, comparação, clonagem, sobrecarga do operador ... Seria muito complicado implementar o operador + em números complexos, por exemplo.

Lorenzo Dematté
fonte
3
Mas tudo o que você menciona requer um tipo GETTING o valor de outro tipo. Estou a bordo dizendo: você quer inspecionar meu estado privado? Tudo bem, vá em frente. Você quer definir meu estado privado? De jeito nenhum, amigo, mude seu próprio estado. :)
aquinas
2
@aquinas Não é assim que funciona. Campos são campos. Eles são somente leitura somente se declarados como readonlye então isso se aplica também à instância declarante. Evidentemente, você está afirmando que deve haver uma regra específica do compilador para proibir isso, mas todas essas regras são um recurso que a equipe do C # precisa especificar, documentar, localizar, testar, manter e dar suporte. Se não há nenhum benefício atraente , por que eles o fazem?
Aaronaught 8/08/11
Eu concordo com o @Aaronaught: há um custo associado à implementação de todas as novas especificações e alterações no compilador. Para um cenário definido, considere um método Clone. Claro, convém realizá-lo com um construtor privado, mas talvez em alguns cenários isso não seja possível / aconselhável.
Lorenzo Dematté 9/08/11
1
Equipe c #? Estou apenas debatendo possíveis alterações de linguagem teórica em QUALQUER linguagem. "Se não houver nenhum benefício atraente ..." Eu acho que pode haver um benefício. Assim como em qualquer garantia do compilador, alguém pode achar útil garantir que uma classe modifique apenas seu próprio estado.
aquinas
@aquinas: os recursos são implementados porque são úteis para muitos ou para a maioria dos usuários - não porque podem ser úteis em alguns casos isolados.
Aaronaught 9/08/11
9

Primeiro de tudo, o que aconteceria com membros estáticos privados? Eles só podem ser acessados ​​por métodos estáticos? Você certamente não gostaria disso, porque então não seria capaz de acessar seus consts.

Quanto à sua pergunta explícita, considere o caso de a StringBuilder, que é implementado como uma lista vinculada de instâncias dela mesma:

public class StringBuilder
{
    private string chunk;
    private StringBuilder nextChunk;
}

Se você não puder acessar os membros privados de outras instâncias de sua própria classe, precisará implementar ToStringassim:

public override string ToString()
{
    return chunk + nextChunk.ToString();
}

Isso funcionará, mas é O (n ^ 2) - não muito eficiente. De fato, isso provavelmente derrota todo o propósito de ter uma StringBuilderclasse em primeiro lugar. Se você puder acessar os membros particulares de outras instâncias de sua própria classe, poderá implementá-lo ToStringcriando uma sequência do tamanho apropriado e, em seguida, fazendo uma cópia insegura de cada parte para seu local apropriado na sequência:

public override string ToString()
{
    string ret = string.FastAllocateString(Length);
    StringBuilder next = this;

    unsafe
    {
        fixed (char *dest = ret)
            while (next != null)
            {
                fixed (char *src = next.chunk)
                    string.wstrcpy(dest, src, next.chunk.Length);
                next = next.nextChunk;
            }
    }
    return ret;
}

Essa implementação é O (n), o que a torna muito rápida e é possível se você tiver acesso a membros particulares de outras instâncias da sua classe .

Gabe
fonte
Sim, mas não existem outras maneiras de contornar isso, como expor alguns campos como internos?
MikeKulls
2
@ Mike: Expor um campo, pois o internaltorna menos privado! Você acabaria expondo os internos de sua classe a tudo em sua montagem.
Gabe
3

Isso é perfeitamente legítimo em muitas línguas (C ++ para uma). Os modificadores de acesso vêm do princípio de encapsulamento no OOP. A idéia é restringir o acesso ao exterior , neste caso, ao exterior, a outras classes. Qualquer classe aninhada em C #, por exemplo, também pode acessar os membros privados dos pais.

Embora essa seja uma opção de design para um designer de idiomas. A restrição desse acesso pode complicar extremamente alguns cenários muito comuns sem contribuir muito para o isolamento das entidades.

Há uma discussão semelhante aqui

Mehran
fonte
1

Não acho que haja um motivo para não adicionarmos outro nível de privacidade, em que os dados são privados para cada instância. De fato, isso pode até proporcionar uma agradável sensação de completude ao idioma.

Mas na prática real, duvido que seja realmente útil. Como você apontou, nossa privacidade usual é útil para coisas como verificações de igualdade, bem como para a maioria das outras operações que envolvem várias instâncias de um Tipo. No entanto, também gosto do seu ponto de vista sobre a manutenção da abstração de dados, pois esse é um ponto importante no POO.

Penso que ao redor, fornecer a capacidade de restringir o acesso de tal maneira pode ser um recurso interessante a ser adicionado ao OOP. Isso é realmente útil? Eu diria que não, uma vez que uma classe deve poder confiar em seu próprio código. Como essa classe é a única coisa que pode acessar membros privados, não há motivo real para precisar a abstração de dados ao lidar com uma instância de outra classe.

Obviamente, você sempre pode escrever seu código como se o privado fosse aplicado às instâncias. Use os get/setmétodos usuais para acessar / alterar os dados. Isso provavelmente tornaria o código mais gerenciável se a classe estivesse sujeita a alterações internas.

Ken Wayne VanderLinde
fonte
0

Ótimas respostas dadas acima. Eu acrescentaria que parte desse problema é o fato de que instanciar uma classe dentro de si mesma é permitida em primeiro lugar. Faz desde que em um loop "for" de lógica recursiva, por exemplo, usar esse tipo de truque desde que você tenha lógica para encerrar a recursão. Mas instanciar ou passar a mesma classe dentro de si mesma sem criar esses loops cria logicamente seus próprios perigos, mesmo que seja um paradigma de programação amplamente aceito. Por exemplo, uma classe C # pode instanciar uma cópia de si mesma em seu construtor padrão, mas isso não quebra nenhuma regra nem cria loops causais. Por quê?

Entre ... esse mesmo problema se aplica a membros "protegidos" também. :(

Eu nunca aceitei esse paradigma de programação completamente porque ele ainda vem com um conjunto inteiro de problemas e riscos que a maioria dos programadores não entende completamente até que problemas como esse problema surjam e confundam as pessoas e desafiem toda a razão de ter membros privados.

Esse aspecto "estranho e maluco" do C # é mais uma razão pela qual uma boa programação não tem nada a ver com experiência e habilidade, mas apenas conhecer os truques e armadilhas ... como trabalhar em um carro. É o argumento de que as regras deveriam ser quebradas, o que é um modelo muito ruim para qualquer linguagem de computação.

Stokely
fonte
-1

Parece-me que se os dados fossem privados para outras instâncias do mesmo tipo, não seriam mais necessariamente o mesmo tipo. Parece não se comportar ou agir da mesma maneira que outras instâncias. O comportamento pode ser facilmente modificado com base nesses dados internos privados. Isso apenas espalharia confusão na minha opinião.

Falando de maneira geral, eu pessoalmente acho que escrever classes derivadas de uma classe base oferece funcionalidade semelhante que você está descrevendo com 'ter dados privados por instância'. Em vez disso, você apenas tem uma nova definição de classe por tipo 'exclusivo'.

C Johnson
fonte