Por que campos privados não são protegidos o suficiente?

262

A visibilidade private dos campos / propriedades / atributos da classe é útil? Em OOP, mais cedo ou mais tarde, você fará uma subclasse de uma classe e, nesse caso, é bom entender e poder modificar completamente a implementação.

Uma das primeiras coisas que faço quando subclasse uma classe é alterar um monte de métodos private para protected . No entanto, é importante ocultar detalhes do mundo exterior , por isso precisamos de protected e não apenas de public .

Minha pergunta é: você sabe sobre um caso de uso importante em que private em vez de protected é uma boa ferramenta ou duas opções " protected & public " seriam suficientes para os idiomas da OOP? / p>     

por Adam Libuša 11.03.2016 / 10:17
fonte

17 respostas

224

Porque, como você diz, protected ainda deixa você com a capacidade de "modificar completamente a implementação". Não protege genuinamente nada dentro da aula.

Por que nos preocupamos em "proteger genuinamente" as coisas dentro da classe? Caso contrário, seria impossível alterar os detalhes da implementação sem quebrar o código do cliente . Dito de outra forma, as pessoas que escrevem subclasses são também "o mundo exterior" para a pessoa que escreveu a classe base original.

Na prática, protected membros são essencialmente uma API pública para subclasses e precisam permanecer estáveis e compatíveis com versões anteriores tanto quanto public membros. Se não tivéssemos a capacidade de criar verdadeiros private membros, então nada em uma implementação seria seguro mudar, porque você não seria capaz de descartar a possibilidade de que (não malicioso) código do cliente de alguma forma conseguiu depender dele.

A propósito, enquanto "Em OOP, mais cedo ou mais tarde, você vai fazer uma subclasse de uma classe" é tecnicamente verdade, seu argumento parece estar fazendo a muito mais strong suposição de que "cedo ou tarde, você vai fazer uma subclasse de todas as classes ", o que certamente não é o caso.

    
por 11.03.2016 / 10:30
fonte
255

In OOP, sooner or later, you are going to make a subclass of a class

Isso está errado. Nem todas as classes devem ter subclasses e algumas linguagens OOP com tipificação estática têm recursos para evitar isso, por exemplo, final (Java e C ++) ou sealed (C #).

it is good to understand and being able to modify the implementation completely.

Não, não é. É bom que uma classe possa definir claramente sua interface pública e preservar suas invariantes, mesmo que seja herdada de.

Em geral, o controle de acesso é sobre compartimentalização. Você deseja que uma parte individual do código seja compreendida sem precisar entender em detalhes como ela interage com o restante do código. O acesso privado permite isso. Se tudo estiver pelo menos protegido, você terá que entender o que cada subclasse faz para entender como funciona a classe base.

Ou para colocá-lo nos termos de Scott Meyers: partes privadas de uma classe são afetadas por uma quantidade finita de código: o código da própria classe.

As partes públicas são potencialmente afetadas por cada código existente e cada código ainda não foi escrito, o que é uma quantidade infinita de código.

As partes protegidas são potencialmente afetadas por todas as subclasses existentes, e todas as subclasses ainda a serem escritas, o que também é uma quantidade infinita de código.

A conclusão é que protegida lhe dá muito pouco mais do que público, enquanto o privado lhe dá uma melhoria real. É a existência do especificador de acesso protegido que é questionável, não privado.

    
por 11.03.2016 / 10:34
fonte
33

Sim, os campos privados são absolutamente necessários. Nesta semana, precisei escrever uma implementação personalizada de dicionário, onde eu controlava o que foi colocado no dicionário. Se o campo do dicionário fosse protegido ou tornado público, então os controles que eu tinha escrito tão cuidadosamente poderiam ter sido facilmente contornados.

Os campos privados geralmente tratam de fornecer salvaguardas de que os dados são conforme o codificador original esperado. Faça tudo protegido / público e você monta um treinador e cavalos através desses procedimentos e validação.

    
por 11.03.2016 / 10:44
fonte
12

Ao tentar raciocinar formalmente sobre a correção de um programa Orientado a Objetos , ele é típico para usar uma abordagem modular envolvendo objeto invariantes . Nesta abordagem

  1. Métodos associados a eles pré e pós condições (contratos).
  2. Objetos associados a eles invariantes.

O raciocínio modular sobre um objeto procede da seguinte maneira (para uma primeira, pelo menos uma aproximação)

  1. Prove que o construtor do objeto estabelece a invariante
  2. Para cada método não privado, suponha que o objeto invariável e a pré-condição do método mantenham a entrada, depois prove que o corpo do código implica que a pós-condição e a invariante persistem na saída do método

Imagine que verificamos um object A usando a abordagem acima. Agora, deseje verificar method g de object B , que chama method f de object A . O raciocínio modular nos permite raciocinar sobre method g sem ter que reconsiderar a implementação de method f . Desde que possamos estabelecer a invariante de object A e a pré-condição de method f no site de chamadas em method g, , podemos considerar a condição de postagem de method f como um resumo do comportamento da chamada de método. Além disso, também saberemos que, após a chamada, a invariante de A ainda é válida.

Essa modularidade do raciocínio é o que nos permite pensar formalmente sobre grandes programas. Podemos raciocinar sobre cada um dos métodos individualmente e, em seguida, compor os resultados desse raciocínio, por sua vez, sobre as partes maiores do programa.

Campos privados são muito úteis neste processo. Para saber que a invariante de um objeto continua a se manter entre duas chamadas de método nesse objeto, normalmente nos baseamos no fato de que o objeto não é modificado no período intermediário.

Por um raciocínio modular para trabalhar em um contexto onde os objetos não têm campos privados, teríamos que ter alguma maneira de garantir que qualquer campo que fosse definido por outro objeto, que a invariante fosse sempre restabelecida ( após o set de campo). É difícil imaginar um invariante de objeto que ambos mantenham, independentemente do valor dos campos do objeto, e também é útil no raciocínio sobre a correção do programa. Nós provavelmente teríamos que inventar alguma convenção complicada em torno do acesso de campo. E provavelmente também perderá alguns (na pior das hipóteses, todos) nossa capacidade de raciocinar modularmente.

Campos protegidos

Os campos protegidos restauram parte de nossa capacidade de raciocinar de maneira modular. Dependendo do idioma protected pode restringir a capacidade de definir um campo para todas as subclasses ou todas as subclasses e classes do mesmo pacote. Frequentemente, não temos acesso a todas as subclasses quando estamos raciocinando sobre a correção de um objeto que estamos escrevendo. Por exemplo, você pode estar escrevendo um componente ou biblioteca que será usado posteriormente em um programa maior (ou vários programas maiores) - alguns dos quais podem nem ter sido escritos ainda. Normalmente, você não saberá se e de que maneira ele pode ser subclassificado.

No entanto, geralmente é necessário que uma subclasse mantenha o objeto invariável da classe que ela estende. Assim, em uma linguagem onde proteger significa apenas "subclasse" e onde somos disciplinados para garantir que subclasses sempre mantenham os invariantes de sua superclasse, você poderia argumentar que a escolha de usar protegido em vez de privado perde apenas uma modularidade mínima. .

Embora eu tenha falado sobre raciocínio formal, é muitas vezes pensado que quando os programadores raciocinam informalmente sobre a exatidão do código deles também às vezes se baseiam em tipos semelhantes de argumentos.

    
por 11.03.2016 / 14:04
fonte
8

private variáveis em uma classe são melhores que protected pela mesma razão que uma instrução break dentro de um bloco switch é melhor que uma instrução goto label ; que é que programadores humanos são propensos a erros.

Variáveis

protected prestam-se a abusos não intencionais (erros do programador), assim como a declaração goto se presta à criação de código espaguete.

É possível escrever código livre de erros usando variáveis de classe protected ? Sim, claro! Assim como é possível escrever código livre de erros usando goto ; mas como o clichê vai "Só porque você pode, não significa que você deveria!"

As classes, e de fato o paradigma OO, existem para proteger contra infelizes programadores humanos propensos a erros a cometer erros. A defesa contra os erros humanos é tão boa quanto as medidas defensivas embutidas na classe. Fazer a implementação da sua classe protected é o equivalente a fazer um enorme buraco nas paredes de uma fortaleza.

As classes base não têm absolutamente nenhum conhecimento de classes derivadas. No que diz respeito a uma classe base, protected na verdade não oferece mais proteção do que public , porque nada impede que uma classe derivada crie um public getter / setter que se comporta como um backdoor.

Se uma classe base permitir acesso não dificultado aos seus detalhes internos de implementação, torna-se impossível para a própria classe se defender contra erros. As classes base não têm absolutamente nenhum conhecimento de suas classes derivadas e, portanto, não têm como evitar erros cometidos nessas classes derivadas.

A melhor coisa que uma classe base pode fazer é ocultar o máximo de sua implementação possível, como private , e colocar restrições suficientes para evitar quebras de alterações de classes derivadas ou qualquer outra coisa fora da classe.

Por fim, existem linguagens de alto nível para minimizar os erros humanos. Boas práticas de programação (como princípios SOLID ) também existem para minimizar os erros humanos.

Desenvolvedores de software que ignoram boas práticas de programação têm uma chance muito maior de falha e têm maior probabilidade de produzir soluções quebráveis e inutilizáveis. Aqueles que seguem boas práticas têm uma chance muito menor de fracasso e têm maior probabilidade de produzir soluções de manutenção funcionais.

    
por 11.03.2016 / 11:06
fonte
4

Classes herdáveis têm dois contratos - um com detentores de referências de objeto e com classes derivadas. Os membros públicos estão vinculados pelo contrato com os detentores de referência e os membros protegidos estão vinculados ao contrato com classes derivadas.

A criação de membros protected torna-a uma classe base mais versátil, mas geralmente limita as maneiras pelas quais versões futuras da classe podem mudar. Fazer membros private permite que o autor da classe tenha mais versatilidade para alterar o funcionamento interno da classe, mas limita os tipos de classes que podem ser proveitosamente derivados dela.

Como exemplo, List<T> no .NET torna a loja de apoio privada; se fosse protegido, os tipos derivados poderiam fazer algumas coisas úteis que de outra forma não seriam possíveis, mas as versões futuras de List<T> teriam que usar para sempre seu repositório monolítico desajeitado, mesmo para listas contendo milhões de itens. Tornar a loja de suporte privada permitiria que versões futuras de List<T> usassem um armazenamento de backup mais eficiente sem quebrar classes derivadas.

    
por 11.03.2016 / 22:33
fonte
4

Eu acho que há uma suposição chave em seu argumento de que quando alguém escreve uma classe eles não sabem quem pode estender essa classe adiante e por que razão . Dada essa suposição, seu argumento faria todo o sentido porque todas as variáveis que você tornasse privadas poderiam, potencialmente, interromper algum caminho de desenvolvimento no futuro. No entanto, eu rejeitaria essa suposição.

Se essa suposição for rejeitada, haverá apenas dois casos a considerar.

  1. O autor da aula original tinha ideias muito claras sobre por que ela poderia ser estendida (por exemplo, é um BaseFoo e haverá várias implementações concretas de Foo no futuro).

Nesse caso, o autor sabe que alguém estenderá a aula e por que e, portanto, saberá exatamente o que fazer protegido e o que tornar privado. Eles estão usando a distinção privada / protegida para comunicar uma interface de tipos ao usuário que criou a subclasse.

  1. O autor da classe filha está tentando invadir algum comportamento em uma classe pai.

Este caso deve ser raro (você pode argumentar que não é legítimo), e não é preferível apenas modificar a classe original na base de código original. Também pode ser um sintoma de design ruim. Nesses casos eu preferiria que a pessoa hackers no comportamento usasse apenas outros hacks como amigos (C / C ++) e setAccessible(true) (Java).

Eu acho que é seguro rejeitar essa suposição.

Isso geralmente recai sobre a idéia de composição sobre herança . A herança é frequentemente ensinada como uma maneira ideal de reduzir a reutilização de código, mas raramente deve ser a primeira opção para reutilização de código. Eu não tenho um argumento simples e pode ser bastante difícil e controverso de entender. No entanto, em minha experiência com modelagem de domínio, descobri que raramente uso herança sem ter uma compreensão muito clara de quem herdará minha aula e por quê.

    
por 11.03.2016 / 23:51
fonte
2

Todos os três níveis de acesso têm seu caso de uso, o OOP estaria incompleto sem nenhum deles. Normalmente você faz

  • torna todas as variáveis / membros de dados private . Você não quer que alguém de fora mexa com seus dados internos. Também métodos que fornecem funcionalidade auxiliar (pense em cálculos baseados em várias variáveis de membros) para sua interface pública ou protegida - isso é apenas para uso interno, e você pode querer alterar / melhorá-lo no futuro.
  • faça a interface geral da sua classe pública . É com isso que os usuários da sua classe original devem trabalhar, e como você acha que as classes derivadas devem se parecer também. Para fornecer um encapsulamento adequado, estes geralmente são apenas métodos (e classes auxiliares / estruturas, enums, typedefs, o que o usuário precisa para trabalhar com seus métodos), não variáveis.
  • declare os métodos protected que podem ser úteis para alguém que deseja estender / especializar a funcionalidade de sua classe, mas que não deve fazer parte da interface pública - na verdade, você geralmente cria membros privados para protegidos quando necessário. Em caso de dúvida você não, até saber que
    1. sua classe pode / poderá / será subclassificada,
    2. e ter uma ideia clara de quais podem ser os casos de uso de subclasses.

E você se desvia desse esquema geral somente se houver uma boa razão ™ . Cuidado com "isso facilitará minha vida quando eu puder acessá-lo livremente de fora" (e fora aqui também inclui subclasses). Quando eu implemento hierarquias de classes, geralmente inicio com classes que não possuem membros protegidos, até chegar a subclassificação / extensão / especialização, tornando-se as classes base de um framework / toolkit e, às vezes, movendo parte de sua funcionalidade original um nível acima.

    
por 12.03.2016 / 00:50
fonte
1

Uma pergunta mais interessante, talvez, é por que qualquer outro tipo de campo que não seja privado é necessário. Quando uma subclasse precisa interagir com os dados de uma superclasse, fazê-lo diretamente cria um acoplamento direto entre os dois, enquanto o uso de métodos para fornecer a interação entre os dois permite um nível de indireção que pode tornar possível fazer alterações no superclasse que de outra forma seria muito difícil.

Vários idiomas (por exemplo, Ruby e Smalltalk) não fornecem campos públicos, de modo que os desenvolvedores são desencorajados a permitir o acoplamento direto a suas implementações de classe, mas por que não ir além e ter apenas campos particulares? Não haveria perda de generalidade (porque a superclasse pode sempre fornecer acessores protegidos para a subclasse), mas garantiria que as classes sempre tivessem pelo menos um pequeno grau de isolamento de suas subclasses. Por que este não é um design mais comum?

    
por 12.03.2016 / 16:58
fonte
1

Muitas boas respostas aqui, mas vou jogar meus dois centavos de qualquer maneira. : -)

Particular é bom pelo mesmo motivo que os dados globais são ruins.

Se uma classe declara dados privados, então você absolutamente sabe que o único código que está mexendo com esses dados é o código na classe. Quando há um bug, você não precisa pesquisar toda a criação para encontrar todos os lugares que possam alterar esses dados. Você sabe que está na aula. Quando você faz uma alteração no código e altera algo sobre como esse campo é usado, não é necessário rastrear todos os lugares possíveis que podem usar esse campo e estudar se a alteração planejada os interromperá. Você sabe que os únicos lugares estão dentro da classe.

Eu tive muitas, muitas vezes que tive que fazer alterações em classes que estão em uma biblioteca e usadas por vários aplicativos, e eu tenho que pisar com muito cuidado para ter certeza de não quebrar algum aplicativo que eu conheço nada sobre. Quanto mais dados públicos e protegidos houver, maior a possibilidade de problemas.

    
por 14.03.2016 / 05:29
fonte
1

Acho que vale a pena mencionar algumas opiniões divergentes.

Em teoria, é bom ter um nível de acesso controlado por todas as razões mencionadas em outras respostas.

Na prática, muitas vezes, quando a revisão de código, vejo pessoas (que gostam de usar privado), alterando o nível de acesso do privado - > protegido e não muitas vezes de protegido - > público. Quase sempre, alterar propriedades de classe envolve a modificação de setters / getters. Estes desperdiçaram muito do meu tempo (revisão de código) e deles (mudança de código).

Também me incomoda o fato de que isso significa que suas aulas não estão fechadas para modificação.

Isso foi com o código interno, onde você sempre pode alterá-lo se precisar também. A situação é pior com o código de terceiros quando não é tão fácil alterar o código.

Então, quantos programadores acham que é um aborrecimento? Bem, quantos estão usando linguagens de programação que não têm privacidade? Claro, as pessoas não estão apenas usando essas linguagens porque elas não têm especificadores particulares, mas ajuda a simplificar as linguagens e a simplicidade é importante.

Imo é muito semelhante à tipagem dinâmica / estática. Em teoria, a tipagem estática é muito boa. Na prática, isso apenas previne como 2% de erros A Eficácia Irrazoável da Digitação Dinâmica ... . O uso privado provavelmente evita erros menores que isso.

Acho que os princípios do SOLID são bons, desejo que as pessoas se importem com eles mais do que se preocupam em criar uma aula com público, protegido e privado.

    
por 14.03.2016 / 11:27
fonte
0

Eu também gostaria de adicionar outro exemplo prático de porque protected não é suficiente. Na minha universidade, os primeiros anos realizam um projeto no qual eles têm que desenvolver uma versão para desktop de um jogo de tabuleiro (para o qual uma IA é desenvolvida e conectada a outros jogadores através de uma rede). Algum código parcial é fornecido a eles para iniciá-los, incluindo uma estrutura de teste. Algumas das propriedades da classe principal do jogo são expostas como protected para que as classes de teste que estendem essa classe tenham acesso a elas. Mas esses campos não são informações confidenciais.

Como assistente de veiculação para a unidade, muitas vezes vejo alunos fazendo todo o código protected ou public (talvez porque eles vissem as outras protected e public e acreditassem que deveriam seguir o mesmo caminho). Eu pergunto a eles por que o nível de proteção deles é inadequado, e muitos não sabem por quê. A resposta é que as informações confidenciais que eles estão expondo às subclasses significam que outro jogador pode trapacear simplesmente estendendo essa classe e acessando as informações altamente sensíveis para o jogo (essencialmente a localização oculta do oponente, eu acho que é semelhante a como seria se você poderia ver as peças do seu oponente em um quadro de naves de batalha estendendo alguma classe). Isso torna o código deles muito perigoso no contexto do jogo.

Além disso, existem muitas outras razões para manter algo privado até mesmo para suas subclasses. Pode ser para esconder os detalhes da implementação que podem atrapalhar o funcionamento correto da turma se for alterado por alguém que não sabe necessariamente o que está fazendo (pensando principalmente em outras pessoas usando seu código aqui).

    
por 11.03.2016 / 23:55
fonte
0

Métodos / variáveis privados geralmente estarão ocultos de uma subclasse. Isso pode ser uma coisa boa.

Um método privado pode fazer suposições sobre parâmetros e deixar a verificação de integridade para o chamador.

Um método protegido deve entradas de verificação de integridade.

    
por 13.03.2016 / 20:31
fonte
-1

"privado" significa: Não se destina a ser alterado ou acessado por ninguém, exceto a própria classe. Não pretende ser alterado ou acessado por subclasses. Subclasses? Quais subclasses? Você não deve subclassificar isso!

"protegido" significa: destina-se apenas a ser alterado ou acessado por classes ou subclasses. Dedução provável de que você é suposto a subclasse, caso contrário, por que "protegido" e não "privado"?

Há uma clara diferença aqui. Se eu fizer algo privado, você deve manter seus dedos sujos fora disso. Mesmo se você é uma subclasse.

    
por 12.03.2016 / 13:08
fonte
-1

One of the first things I do when I subclass a class is to change a bunch of private methods to protected

Algum raciocínio sobre os métodos private vs. protected :

private métodos impedem a reutilização de código. Uma subclasse não pode usar o código no método privado e pode ter que implementá-lo novamente - ou reimplementar o (s) método (s) que originalmente dependem do método privado & c.

Por outro lado, qualquer método que não seja private pode ser visto como uma API fornecida pela classe para "o mundo externo", no sentido de que subclasses de terceiros são consideradas "mundo exterior" também, como alguém sugeriu em sua resposta já.

Isso é uma coisa ruim? - Eu não penso assim.

Naturalmente, uma API (pseudo) pública bloqueia o programador original e impede a refatoração dessas interfaces. Mas, ao contrário, por que um programador não deveria projetar seus próprios "detalhes de implementação" de uma maneira tão limpa e estável quanto sua API pública? Ele deve usar private para que ele possa ser desleixado sobre a estruturação de seu código "privado"? Pensando talvez que ele poderia limpá-lo mais tarde, porque ninguém vai notar? - Não.

O programador deve colocar um pouco de reflexão em seu código "privado" também, para estruturá-lo de uma forma que permita ou até mesmo promova a reutilização do máximo possível, em primeiro lugar. Então, as partes não privadas podem não se tornar um fardo no futuro, como alguns temem.

Um monte de código (framework) que vejo adota um uso inconsistente de private : protected , métodos não finais que mal fazem mais do que delegar a um método privado são comumente encontrados. protected , métodos não finais cujo contrato só pode ser cumprido através do acesso direto a campos privados também.

Esses métodos não podem ser logicamente substituídos / aprimorados, embora tecnicamente não haja nada lá para tornar isso (compilador) óbvio.

Deseja extensão e herança? Não faça seus métodos private .

Não quer que determinado comportamento de sua classe seja alterado? Faça seus métodos final .

Realmente não pode ter seu método chamado fora de um contexto certo e bem definido? Faça seu método private e / ou pense em como você pode disponibilizar o contexto bem definido necessário para reutilização por meio de outro método% wrapper protected .

É por isso que defendo usar private com parcimônia. E para não confundir private com final . - Se a implementação de um método é vital para o contrato geral da classe e, portanto, não deve ser substituído / substituído, torne-o final !

Para campos, private não é muito ruim. Contanto que o (s) campo (s) possa (m) ser razoavelmente "usado (s)" por meio de métodos apropriados (isso não é getXX() ou setXX() !).

    
por 11.03.2016 / 14:47
fonte
-1

Do you know about an important use case where private instead of protected is a good tool, or would two options "protected & public" be enough for OOP languages?

Privado : quando você tem algo que nunca será útil para qualquer subclasse chamar ou substituir.

Protegido : quando você tem algo que tem uma implementação / constante específica da subclasse.

Um exemplo:

public abstract Class MercedesBenz() extends Car {
  //Might be useful for subclasses to know about their customers
  protected Customer customer; 

  /* Each specific model has its own horn. 
     Therefore: protected, so that each subclass might implement it as they wish
  */
  protected abstract void honk();

  /* Taken from the car class. */
  @Override
  public void getTechSupport(){
     showMercedesBenzHQContactDetails(customer);
     automaticallyNotifyLocalDealer(customer);
  }

  /* 
     This isn't specific for any subclass.
     It is also not useful to call this from inside a subclass,
     because local dealers only want to be notified when a 
     customer wants tech support. 
   */
  private void automaticallyNotifyLocalDealer(){
    ...
  }
}
    
por 18.03.2016 / 11:12
fonte
-2

Foi difícil para mim entender esse assunto, então gostaria de compartilhar um pouco da minha experiência:

  • Qual é o campo protected ? É nada mais do que um campo, que não pode ser acessado fora de uma classe, ou seja, publicamente da seguinte forma: $classInstance->field . E o truque que é "é isso". As crianças da sua turma terão um acesso completo a elas, porque essa é sua parte interna de direito.
  • Qual é o campo privado ? É um " true private " para sua própria classe e sua própria implementação dessa classe. "Mantenha fora do alcance das crianças", assim como na garrafa de um remédio. Você terá a garantia de que não é possível recuperar os derivativos da sua classe. Seus métodos, quando chamados, terão exato o que você declarou

ATUALIZAÇÃO: um exemplo prático de uma tarefa real que resolvi. Aqui está: você tem um token, como USB ou LPT (que era o meu caso), e você tem um middleware. O token pede um código PIN, abre se estiver correto e você pode enviar parte criptografada e um número de chave para decifrar. As chaves são armazenadas em token, você não pode lê-las, apenas as usa. E havia chaves temporárias para uma sessão, assinadas por uma chave em um token, mas armazenadas em um middleware em si. A chave temporária não deveria vazar para fora, apenas para existir em um nível de driver. E usei campos private para armazenar essa chave temporária e alguns dados relacionados à conexão de hardware. Portanto, nenhum derivativo conseguiu usar não apenas uma interface pública, mas também algumas sub-rotinas "à mão" protected que fiz para uma tarefa, mas não consegui abrir um cofre com as chaves e a interação HW. Faz sentido?

    
por 11.03.2016 / 21:24
fonte