Código como:
public Thing[] getThings(){
return things;
}
Não faz muito sentido, pois seu método de acesso não está fazendo nada além de retornar diretamente a estrutura de dados interna. Você também pode simplesmente declarar Thing[] things
ser public
. A idéia por trás de um método de acesso é criar uma interface que isola os clientes de alterações internas e os impede de manipular a estrutura de dados real, exceto de maneiras discretas, conforme permitido pela interface. Como você descobriu quando todo o código do seu cliente quebrou, seu método de acesso não fez isso - é apenas um código desperdiçado. Eu acho que muitos programadores tendem a escrever código assim porque aprenderam em algum lugar que tudo precisa ser encapsulado com métodos de acesso - mas foi por isso que expliquei. Fazer isso apenas para "seguir a forma" quando o método de acesso não serve a nenhum propósito é apenas ruído.
Definitivamente, eu recomendaria sua solução proposta, que cumpre alguns dos objetivos mais importantes do encapsulamento: Oferecer aos clientes uma interface robusta e discreta que os isole dos detalhes de implementação interna da sua classe e não permita que eles toquem na estrutura de dados interna espere da maneira que você decidir ser apropriada - "a lei do privilégio menos necessário". Se você observar as grandes estruturas populares de OOP, como o CLR, o STL, o VCL, o padrão que você propôs é generalizado, exatamente por esse motivo.
Você sempre deve fazer isso? Não necessariamente. Por exemplo, se você tem classes auxiliares ou amigas que são essencialmente um componente da sua classe principal de trabalhadores e não são "voltadas para a frente", isso não é necessário - é um exagero que adicionará muito código desnecessário. E, nesse caso, eu não usaria um método de acesso - não faz sentido, conforme explicado. Apenas declare a estrutura de dados de uma maneira que tenha como escopo apenas a classe principal que a usa - a maioria dos idiomas suporta maneiras de fazer isso - friend
ou declare-a no mesmo arquivo da classe principal de trabalho, etc.
A única desvantagem que vejo na sua proposta é que é mais trabalhoso codificar (e agora você terá que re-codificar suas classes de consumidor - mas você precisa / teve que fazer isso de qualquer maneira.) Mas isso não é realmente uma desvantagem - você precisa fazer o que é certo, e às vezes isso exige mais trabalho.
Uma das coisas que torna um bom programador bom é que eles sabem quando o trabalho extra vale a pena e quando não vale. A longo prazo, colocar o extra agora renderá grandes dividendos no futuro - se não neste projeto, então em outros. Aprenda a codificar da maneira certa e use sua mente para isso, não apenas siga roboticamente os formulários prescritos.
Observe que coleções mais complexas que matrizes podem exigir que a classe envolvente implemente ainda mais de três métodos apenas para acessar a estrutura de dados interna.
Se você está expondo uma estrutura de dados inteira por meio de uma classe que contém, na IMO, é necessário pensar no porquê dessa classe ser encapsulada, se não for apenas para fornecer uma interface mais segura - uma "classe de wrapper". Você está dizendo que a classe que contém não existe para esse fim - então talvez haja algo errado no seu design. Considere dividir suas aulas em módulos mais discretos e colocá-los em camadas.
Uma classe deve ter um objetivo claro e discreto e fornecer uma interface para suportar essa funcionalidade - nada mais. Você pode estar tentando juntar coisas que não pertencem uma à outra. Quando você faz isso, as coisas vão quebrar toda vez que você precisa implementar uma mudança. Quanto menores e mais discretas forem suas aulas, mais fácil será mudar as coisas: pense em LEGO.
get(index)
,add()
,size()
,remove(index)
, eremove(Object)
. Usando a técnica proposta, a classe que contém esse ArrayList deve ter cinco métodos públicos apenas para delegar à coleção interna. E o objetivo dessa classe no programa provavelmente não está encapsulando esse ArrayList, mas fazendo outra coisa. O ArrayList é apenas um detalhe. [...]remove
não é somente leitura. Meu entendimento é que o OP quer tornar tudo público - como no código original antes da alteração proposta.public Thing[] getThings(){return things;}
É disso que eu não gosto.Você perguntou: Devo sempre encapsular inteiramente uma estrutura de dados interna?
Resposta breve: Sim, na maioria das vezes, mas nem sempre .
Resposta longa: Eu acho que as aulas seguem as seguintes categorias:
Classes que encapsulam dados simples. Exemplo: ponto 2D. É fácil criar funções públicas que ofereçam a capacidade de obter / definir as coordenadas X e Y, mas você pode ocultar os dados internos facilmente sem grandes problemas. Para essas classes, não é necessário expor os detalhes da estrutura de dados internos.
Classes de contêiner que encapsulam coleções. STL tem as classes de contêiner clássicas. Eu considero
std::string
estd::wstring
entre aqueles também. Eles fornecem uma interface rica para lidar com as abstrações, masstd::vector
,std::string
estd::wstring
também fornecem a capacidade de obter acesso aos dados brutos. Eu não teria pressa em chamá-los de aulas mal planejadas. Não sei a justificativa para essas classes que expõem seus dados brutos. No entanto, no meu trabalho, achei necessário expor os dados brutos ao lidar com milhões de nós de malha e dados nesses nós de malha por razões de desempenho.O importante sobre a exposição da estrutura interna de uma classe é que você precisa pensar muito antes de emitir um sinal verde. Se a interface for interna de um projeto, será caro alterá-la no futuro, mas não impossível. Se a interface for externa ao projeto (como quando você estiver desenvolvendo uma biblioteca que será usada por outros desenvolvedores de aplicativos), pode ser impossível alterar a interface sem perder seus clientes.
Classes que são principalmente de natureza funcional. Exemplos:
std::istream
,std::ostream
, iterators dos recipientes STL. É tolice expor os detalhes internos dessas classes.Classes híbridas. Essas são classes que encapsulam alguma estrutura de dados, mas também fornecem funcionalidade algorítmica. Pessoalmente, acho que isso é resultado de um design mal pensado. No entanto, se você os encontrar, precisará decidir se faz sentido expor seus dados internos caso a caso.
Conclusão: A única vez em que achei necessário expor a estrutura de dados interna de uma classe é quando ela se tornou um gargalo de desempenho.
fonte
Em vez de retornar os dados brutos diretamente, tente algo como isto
Portanto, você está fornecendo essencialmente uma coleção personalizada que apresenta qualquer rosto que o mundo deseje. Na sua nova implementação, então,
Agora você tem o encapsulamento adequado, oculta os detalhes da implementação e fornece compatibilidade com versões anteriores (a um custo).
fonte
List
. Os métodos de acesso que simplesmente retornam membros de dados, mesmo com uma conversão para tornar as coisas mais robustas, não são realmente um bom IMO de encapsulamento. A classe de trabalhadores deve lidar com tudo isso, não o cliente. Quanto mais burro um cliente tiver, mais robusto ele será. Como um aparte, não tenho certeza de que você respondeu à pergunta ...Você deve usar interfaces para essas coisas. Não ajudaria no seu caso, já que a matriz do Java não implementa essas interfaces, mas você deve fazê-lo a partir de agora:
Dessa forma, você pode mudar
ArrayList
paraLinkedList
ou qualquer outra coisa e não quebra nenhum código, pois provavelmente todas as coleções Java (além das matrizes) que têm acesso aleatório (pseudo?) Serão implementadasList
.Você também pode usar
Collection
, que oferecem menos métodos do queList
mas podem suportar coleções sem acesso aleatório, ouIterable
que podem até suportar fluxos, mas não oferecem muito em termos de métodos de acesso ...fonte
List
como classes derivadas, faria mais sentido, mas apenas "esperar pelo melhor" não é uma boa idéia. "Um bom programador olha nos dois sentidos em uma rua de mão única".List
(ouCollection
, ou pelo menosIterable
). Esse é o objetivo dessas interfaces, e é uma pena que as Java Arrays não as implementem, mas elas são uma interface oficial para coleções em Java, portanto, não é tão difícil supor que qualquer coleção Java as implemente - a menos que essa coleção seja mais antiga do queList
, e, nesse caso, é muito fácil para envolvê-lo com AbstractList .List
emArrayList
, mas não é como se a implementação pudesse ser 100% protegida - você sempre pode usar a reflexão para acessar campos privados. Fazer isso é desaprovado, mas o elenco também é desaprovado (embora não muito). O objetivo do encapsulamento não é impedir a invasão mal-intencionada, mas impedir que os usuários dependam dos detalhes da implementação que você pode querer alterar. O uso daList
interface faz exatamente isso - os usuários da classe podem depender daList
interface, em vez daArrayList
classe concreta que pode mudar.Isso é bastante comum para ocultar sua estrutura de dados interna do mundo exterior. Às vezes é um exagero, especialmente no DTO. Eu recomendo isso para o modelo de domínio. Se for necessário expor, devolva a cópia imutável. Junto com isso, sugiro criar uma interface com esses métodos, como obter, definir, remover etc.
fonte