Essa pergunta sobre quando usar o privado e quando usar o protegido nas aulas me fez pensar. (Vou estender essa pergunta também para as classes e métodos finais, pois estão relacionados. Estou programando em Java, mas acho que isso é relevante para todas as linguagens OOP)
Uma boa regra geral é: torne tudo o mais privado possível.
- Torne todas as aulas finais, a menos que você precise subclassificá-las imediatamente.
- Torne todos os métodos finais, a menos que você precise subclassificar e substituí-los imediatamente.
- Torne todos os parâmetros do método final, a menos que você precise alterá-los no corpo do método, o que é meio estranho na maioria das vezes.
Isso é bem direto e claro, mas e se eu estiver escrevendo principalmente bibliotecas (código-fonte aberto no GitHub) em vez de aplicativos?
Eu poderia citar muitas bibliotecas e situações em que
- Uma biblioteca foi estendida de uma maneira que os desenvolvedores nunca pensariam em
- Isso tinha que ser feito com a "magia do carregador de classes" e outros hacks devido a restrições de visibilidade
- As bibliotecas foram usadas de uma maneira para a qual não foram criadas e a maneira necessária de funcionalidade "invadida" em
- As bibliotecas não puderam ser usadas devido a um pequeno problema (erro, falta de funcionalidade, comportamento "errado") que não pôde ser alterado devido à visibilidade reduzida
- Um problema que não pôde ser corrigido levou a soluções alternativas enormes, feias e com erros, em que substituir uma função simples (privada ou final) poderia ter ajudado
Na verdade, comecei a nomear esses nomes até que a pergunta ficasse muito longa e resolvi removê-los.
Eu gosto da ideia de não ter mais código do que o necessário, mais visibilidade do que o necessário, mais abstração do que o necessário. E isso pode funcionar ao escrever um aplicativo para o usuário final, onde o código é usado apenas por quem o escreve. Mas como isso acontece se o código for usado por outros desenvolvedores, onde é improvável que o desenvolvedor original tenha pensado em todos os casos de uso possíveis com antecedência e que mudanças / refatores sejam difíceis / impossíveis de realizar?
Como as grandes bibliotecas de código aberto não são novidade, qual é a maneira mais comum de lidar com a visibilidade nesses projetos com linguagens orientadas a objetos?
fonte
Respostas:
A infeliz verdade é que muitas bibliotecas são escritas , não projetadas . Isso é triste, porque um pouco de pensamento prévio pode evitar muitos problemas no caminho.
Se pretendermos projetar uma biblioteca, haverá um conjunto de casos de uso antecipados. A biblioteca pode não satisfazer todos os casos de uso diretamente, mas pode servir como parte de uma solução. Portanto, a biblioteca precisa ser flexível o suficiente para se adaptar.
A restrição é que geralmente não é uma boa ideia pegar o código-fonte da biblioteca e modificá-lo para lidar com o novo caso de uso. Para bibliotecas proprietárias, a fonte pode não estar disponível e, para bibliotecas de código aberto, pode ser indesejável manter uma versão bifurcada. Pode não ser possível mesclar adaptações altamente específicas no projeto upstream.
É aqui que entra o princípio aberto-fechado: a biblioteca deve ser aberta para extensão sem modificar o código-fonte. Isso não vem naturalmente. Esse deve ser um objetivo de design intencional. Há várias técnicas que podem ajudar aqui, os padrões clássicos de design de POO são algumas delas. Em geral, especificamos ganchos nos quais o código do usuário pode ser conectado com segurança à biblioteca e adicionar funcionalidade.
Apenas tornar público todo método ou permitir que cada classe seja subclassificada não é suficiente para alcançar a extensibilidade. Primeiro de tudo, é realmente difícil estender a biblioteca se não estiver claro onde o usuário pode se conectar à biblioteca. Por exemplo, substituir a maioria dos métodos não é seguro porque o método da classe base foi escrito com suposições implícitas. Você realmente precisa projetar para extensibilidade.
Mais importante, quando algo faz parte da API pública, você não pode recuperá-lo. Você não pode refatorá-lo sem quebrar o código downstream. A abertura prematura limita a biblioteca a um design abaixo do ideal. Por outro lado, tornar as coisas internas privadas, mas adicionar ganchos, se mais tarde houver necessidade delas, é uma abordagem mais segura. Embora essa seja uma maneira sensata de lidar com a evolução a longo prazo de uma biblioteca, isso não é satisfatório para usuários que precisam usar a biblioteca no momento .
Então, o que acontece? Se houver um problema significativo com o estado atual da biblioteca, os desenvolvedores poderão obter todo o conhecimento sobre os casos de uso reais acumulados ao longo do tempo e escrever uma Versão 2 da biblioteca. Vai ser ótimo! Ele irá corrigir todos os erros por design! Também levará mais tempo do que o esperado, em muitos casos fracassando. E se a nova versão for muito diferente da versão antiga, pode ser difícil incentivar os usuários a migrar. Você fica mantendo duas versões incompatíveis.
fonte
Cada classe / método público e extensível faz parte da sua API e deve ser suportado. Limitar esse conjunto a um subconjunto razoável da biblioteca permite maior estabilidade e limita o número de coisas que podem dar errado. É uma decisão de gerenciamento (e até mesmo os projetos OSS são gerenciados até certo ponto) com base no que você pode razoavelmente oferecer suporte.
A diferença entre o OSS e o código-fonte fechado é que a maioria das pessoas está tentando criar e aumentar uma comunidade em torno do código, para que mais de uma pessoa mantenha a biblioteca. Dito isto, existem várias ferramentas de gerenciamento disponíveis:
Em projetos maduros, o que você verá é algo nesse sentido:
Nesse ponto, se a alteração foi aceita, mas o usuário deseja acelerar a correção, ele pode fazer o trabalho e enviar uma solicitação de recebimento ou um patch (dependendo da ferramenta de controle de versão).
Nenhuma API é estática. No entanto, seu crescimento precisa ser moldado de alguma forma. Ao manter tudo fechado até que haja uma necessidade demonstrada de abrir as coisas, você evita obter a reputação de uma biblioteca de bugs ou instável.
fonte
Vou reformular minha resposta, uma vez que parece que isso afetou algumas pessoas.
A visibilidade da propriedade / método da classe não tem nada a ver com segurança nem abertura da fonte.
A razão pela qual a visibilidade existe é porque os objetos são frágeis para 4 problemas específicos:
Se você construir seu módulo sem o encapsulamento, seus usuários se acostumarão a alterar diretamente o estado do módulo. Isso funciona bem em um único ambiente de thread, mas quando você pensa em adicionar threads; você será forçado a tornar o estado privado e usar bloqueios / monitores junto com getters e setters que fazem outros threads aguardarem pelos recursos, em vez de correrem neles. Isso significa que os programas dos usuários não funcionarão mais porque as variáveis privadas não podem ser acessadas de maneira convencional. Isso pode significar que você precisa de muitas reescritas.
A verdade é que é muito mais fácil codificar com um único tempo de execução em thread, e a palavra-chave privada permite que você adicione a palavra-chave sincronizada ou alguns bloqueios, e o código dos usuários não será quebrado se você o encapsular desde o início .
Todo objeto tem várias coisas necessárias para ser verdade para estar em um estado consistente. Infelizmente, essas coisas vivem no espaço visível do cliente, porque é caro mover cada objeto para seu próprio processo e conversar com ele por meio de mensagens. Isso significa que é muito fácil para um objeto travar o programa inteiro se o usuário tiver total visibilidade.
Isso é inevitável, mas você pode evitar colocar acidentalmente um objeto em um estado inconsistente, fechando uma interface sobre seus serviços que evitam falhas acidentais, permitindo apenas que o usuário interaja com o estado do objeto por meio de uma interface cuidadosamente criada que torna o programa muito mais robusto . Isso não significa que o usuário não possa intencionalmente corromper os invariantes, mas se o fizerem, é o cliente que trava, tudo o que eles precisam fazer é reiniciar o programa (os dados que você deseja proteger não devem ser armazenados no lado do cliente )
Outro bom exemplo em que você pode melhorar a usabilidade de seus módulos é tornar o construtor privado; porque se o construtor lançar uma exceção, ele matará o programa. Uma abordagem preguiçosa para resolver isso é fazer com que o construtor gere um erro de tempo de compilação que você não pode construí-lo, a menos que esteja em um bloco de tentativa / captura. Tornando o construtor privado e adicionando um método público estático de criação, você pode fazer com que o método create retorne nulo se ele não for construído, ou use uma função de retorno de chamada para lidar com o erro, tornando o programa mais amigável.
Muitas classes têm muitos estados e métodos e é fácil ficar sobrecarregado tentando rolar por elas; Muitos desses métodos são apenas ruídos visuais, como funções auxiliares, estado. tornar as variáveis e os métodos privados ajuda a reduzir a poluição do escopo e facilita o usuário a encontrar os serviços que está procurando.
Em essência, permite que você tenha funções auxiliares dentro da classe e não fora da classe; sem controle de visibilidade sem distrair o usuário com vários serviços que o usuário nunca deve usar, para que você possa dividir métodos em vários métodos auxiliares (embora ele ainda polua seu escopo, mas não o usuário).
Uma interface bem criada pode ocultar seus bancos de dados / janelas / imagens internos dos quais depende para fazer seu trabalho e, se você quiser mudar para outro banco de dados / outro sistema de janelas / outra biblioteca de imagens, poderá manter a interface e os usuários não vai perceber.
Por outro lado, se você não fizer isso, poderá facilmente tornar impossível alterar suas dependências, porque elas estão expostas e o código depende disso. Com um sistema grande o suficiente, o custo da migração pode se tornar inacessível, enquanto um encapsulamento pode proteger os usuários do cliente que se comportam bem contra decisões futuras de troca de dependências.
fonte