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?
Respostas:
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:
O compilador pode necessariamente descobrir o que
other
é realmentethis
? 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 só 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.
fonte
this.bar = 2
mas nãoother.bar = 2
, porqueother
poderia ser uma instância diferente.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.
fonte
get/set
mé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 umaComplex
classe, 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).private
. Você pode verificar minha resposta para ver minha opinião sobre as coisas, se quiser;).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:
No caso de
private
ele 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, sex
é 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,
private
significa: "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 ex.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 .
fonte
Foo
efetivamente 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.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?
fonte
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.
fonte
readonly
e 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?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
const
s.Quanto à sua pergunta explícita, considere o caso de a
StringBuilder
, que é implementado como uma lista vinculada de instâncias dela mesma:Se você não puder acessar os membros privados de outras instâncias de sua própria classe, precisará implementar
ToString
assim:Isso funcionará, mas é O (n ^ 2) - não muito eficiente. De fato, isso provavelmente derrota todo o propósito de ter uma
StringBuilder
classe em primeiro lugar. Se você puder acessar os membros particulares de outras instâncias de sua própria classe, poderá implementá-loToString
criando uma sequência do tamanho apropriado e, em seguida, fazendo uma cópia insegura de cada parte para seu local apropriado na sequência:Essa implementação é O (n), o que a torna muito rápida e só é possível se você tiver acesso a membros particulares de outras instâncias da sua classe .
fonte
internal
torna menos privado! Você acabaria expondo os internos de sua classe a tudo em sua montagem.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
fonte
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/set
mé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.fonte
Ó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.
fonte
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'.
fonte