Não estou procurando uma opinião sobre semântica, mas simplesmente para um caso em que ter getters usados com sensatez é um impedimento real. Talvez isso me jogue em uma espiral interminável de confiar neles, talvez a alternativa seja mais limpa e lide com getters automaticamente, etc. Algo concreto.
Ouvi todos os argumentos, ouvi dizer que eles são ruins porque forçam você a tratar objetos como fontes de dados, que violam o "estado puro" de "um objeto" não revelam muito, mas estejam preparados para aceite muito ".
Mas absolutamente nenhuma razão sensata para o fato de que uma getData
coisa é ruim, de fato, algumas pessoas argumentaram que se trata muito de semântica, getters tão bons por si só, mas simplesmente não os nomeie getX
para mim, isso é pelo menos engraçado .
O que é uma coisa, sem opiniões, que será interrompida se eu usar getters de maneira sensata e para dados que claramente a integridade do objeto não se quebra se for divulgada?
É claro que permitir um getter para uma string usada para criptografar algo está além do absurdo, mas estou falando de dados que seu sistema precisa para funcionar. Talvez seus dados sejam extraídos de um Provider
objeto, mas, ainda assim, o objeto ainda precisa permitir Provider
que você faça um $provider[$object]->getData
, não há como contorná-lo.
Por que estou perguntando: para mim, os getters, quando usados com sensibilidade e em dados que são tratados como "seguros" são enviados por Deus, 99% dos meus getters são usados para identificar o objeto, como, pergunto, por meio de código Object, what is your name? Object, what is your identifier?
, quem trabalha com um objeto deve saber essas coisas sobre um objeto, porque quase tudo sobre programação é identidade e quem mais sabe melhor o que é que o próprio objeto? Portanto, não vejo problemas reais, a menos que você seja um purista.
Examinei todas as perguntas do StackOverflow sobre "por que os getters / setters" são ruins e, embora eu concorde que os setters são realmente ruins em 99% dos casos, os getters não precisam ser tratados da mesma forma apenas porque rimam.
Um setter compromete a identidade do seu objeto e dificulta a depuração de quem está alterando os dados, mas um getter não está fazendo nada.
fonte
Respostas:
Você não pode escrever um bom código sem getters.
A razão disso não é porque os getters não quebram o encapsulamento, eles o fazem. Não é porque os getters não tentam as pessoas a não se incomodarem em seguir o POO, o que os levaria a colocar métodos com os dados em que atuam. Eles fazem. Não, você precisa de getters por causa dos limites.
As idéias de encapsulamento e manutenção de métodos juntamente com os dados em que atuam simplesmente não funcionam quando você se depara com um limite que impede você de mover um método e, portanto, obriga a mover dados.
É realmente assim tão simples. Se você usa getters quando não há limites, acaba sem objetos reais. Tudo começa a tender para o procedimento. O que funciona tão bem quanto nunca.
OOP verdadeiro não é algo que você pode espalhar por toda parte. Funciona apenas dentro desses limites.
Esses limites não são muito finos. Eles têm código neles. Esse código não pode ser OOP. Também não pode ser funcional. Nenhum código tem nossos ideais retirados para que ele possa lidar com a dura realidade.
Michael Fetters chamou esse código de fáscia, depois daquele tecido conjuntivo branco que mantém seções de uma laranja juntas.
Esta é uma maneira maravilhosa de pensar sobre isso. Ele explica por que não há problema em ter os dois tipos de código na mesma base de código. Sem essa perspectiva, muitos novos programadores se apegam a seus ideais com força, então partem seus corações e desistem desses ideais quando atingem seu primeiro limite.
Os ideais funcionam apenas em seu devido lugar. Não desista deles só porque eles não funcionam em todos os lugares. Use-os onde eles trabalham. Esse lugar é a parte suculenta que a fáscia protege.
Um exemplo simples de limite é uma coleção. Isso contém algo e não faz ideia do que é. Como um designer de coleção poderia mover a funcionalidade comportamental do objeto em espera para a coleção quando não tem idéia do que ele estará segurando? Você não pode. Você está enfrentando um limite. É por isso que as coleções têm getters.
Agora, se você soubesse, poderia mudar esse comportamento e evitar o estado de mudança. Quando você sabe, você deveria. Você nem sempre sabe.
Algumas pessoas chamam isso de pragmático. E isso é. Mas é bom saber por que temos que ser pragmáticos.
Você expressou que não deseja ouvir argumentos semânticos e parece estar defendendo a colocação de "apanhadores sensatos" em todos os lugares. Você está pedindo que essa ideia seja contestada. Acho que posso mostrar que a ideia tem problemas com a maneira como você a estruturou. Mas também acho que sei de onde você vem, porque eu estive lá.
Se você quiser getters em todos os lugares, veja Python. Não há palavra-chave privada. No entanto, o Python faz POO muito bem. Quão? Eles usam um truque semântico. Eles nomeiam qualquer coisa que seja privada com um sublinhado principal. Você pode ler o conteúdo, desde que seja responsável por isso. "Somos todos adultos aqui", costumam dizer.
Então, qual é a diferença entre isso e apenas colocar getters em tudo em Java ou C #? Desculpe, mas é semântica. A convenção sublinhada de Pythons indica claramente a você que você está bisbilhotando atrás da única porta dos funcionários. Tapa getters em tudo e você perde esse sinal. Com a reflexão, você poderia ter retirado o privado de qualquer maneira e ainda não ter perdido o sinal semântico. Simplesmente não há um argumento estrutural a ser feito aqui.
Então, o que nos resta é o trabalho de decidir onde pendurar a placa "apenas funcionários". O que deve ser considerado privado? Você chama isso de "receptores sensatos". Como eu disse, a melhor justificativa para um getter é um limite que nos afasta de nossos ideais. Isso não deve resultar em getters sobre tudo. Quando isso resulta em um getter, você deve considerar mover o comportamento ainda mais para a parte interessante, onde você pode protegê-lo.
Essa separação deu origem a alguns termos. Um objeto de transferência de dados ou DTO não possui comportamento. Os únicos métodos são getters e às vezes setters, às vezes um construtor. Esse nome é lamentável, porque não é um objeto verdadeiro. Os getters e setters são realmente apenas códigos de depuração que oferecem um local para definir um ponto de interrupção. Se não fosse por essa necessidade, eles seriam apenas uma pilha de campos públicos. Em C ++, costumávamos chamá-los de estruturas. A única diferença que eles tinham de uma classe C ++ era o padrão para o público.
Os DTOs são bons porque você pode jogá-los sobre um muro de fronteira e manter seus outros métodos com segurança em um bom objeto de comportamento interessante. Um verdadeiro objeto. Sem getters para violar, é encapsulamento. Meus objetos de comportamento podem comer DTOs usando-os como Objetos de Parâmetro . Às vezes, tenho que fazer uma cópia defensiva para impedir o estado mutável compartilhado . Eu não espalhe DTOs mutáveis por dentro da parte suculenta dentro dos limites. Eu os encapsulo. Eu os escondo. E quando finalmente encontro um novo limite, lancei um novo DTO e o joguei por cima do muro, tornando-o o problema de outra pessoa.
Mas você deseja fornecer getters que expressam identidade. Bem, parabéns, você encontrou um limite. As entidades têm uma identidade que vai além de sua referência. Ou seja, além do endereço de memória. Portanto, tem que ser armazenado em algum lugar. E algo tem que ser capaz de se referir a isso por sua identidade. Uma pessoa que expressa identidade é perfeitamente razoável. Uma pilha de código que usa esse getter para tomar decisões que a Entidade poderia ter tomado por si mesma não é.
No final, não é a existência de getters que está errada. Eles são muito melhores que os campos públicos. O que é ruim é quando eles são usados para fingir que você está sendo orientado a objetos quando não está. Getters são bons. Ser orientado a objetos é bom. Os getters não são orientados a objetos. Use getters para criar um local seguro para se orientar a objetos.
fonte
clearly
? Se dados são passados para mim a partir de algo, claramente , por valor , meu objeto agora é o proprietário dos dados, mas pela semântica, ainda não, nesse ponto, simplesmente obtém os dados de outra pessoa. Em seguida, ele deve executar operações para transformar esses dados, tornando-os próprios, no momento em que os dados são tocados, você deve fazer alterações semânticas: você recebeu dados nomeados como$data_from_request
e agora operou? Dê um nome$changed_data
. Deseja expor esses dados a outras pessoas? Crie um gettergetChangedRequestData
, então, a propriedade está claramente definida. Ou é?Os getters violam o Princípio de Hollywood ("Não ligue para nós, ligaremos para você")
O Princípio de Hollywood (aka Inversão de Controle) afirma que você não chama o código da biblioteca para fazer as coisas; em vez disso, a estrutura chama seu código. Como a estrutura controla as coisas, não é necessário transmitir seu estado interno aos clientes. Você não precisa saber.
Na sua forma mais insidiosa, violar o Princípio de Hollywood significa que você está usando um getter para obter informações sobre o estado de uma classe e, em seguida, toma decisões sobre quais métodos chamar nessa classe com base no valor que você obtém. é uma violação do encapsulamento no seu melhor.
Usar um getter implica que você precisa desse valor quando realmente não precisa .
Você pode realmente precisar dessa melhoria de desempenho
Em casos extremos de objetos leves que devem ter o máximo desempenho possível, é possível (embora extremamente improvável) que você não possa pagar a penalidade de desempenho muito pequena que um getter impõe. Isso não acontecerá 99,9% das vezes.
fonte
Generator
objeto que percorre todos os meusItems
objetos e depois chamagetName
cada umItem
para fazer algo mais. Qual é o problema com isso? Então, em troca, oGenerator
cospe strings formatadas. Isso está dentro da minha estrutura, para a qual eu tenho uma API sobre a qual as pessoas podem usar para executar o que os usuários fornecem, mas sem tocar na estrutura.What is the issue with this?
Nada que eu possa ver. Isso é essencialmente o que umamap
função faz. Mas essa não é a pergunta que você fez. Você essencialmente perguntou: "Existem quaisquer condições em que um getter pode ser desaconselhável." Eu respondi com dois, mas isso não significa que você abandona completamente os levantadores.Getters vazam detalhes de implementação e abstração de interrupção
Considerar
public int millisecondsSince1970()
Seus clientes pensam "oh, isso é um int" e haverá um zilhão de chamadas supondo que seja um int , fazendo matemática inteira comparando datas, etc ... Quando você perceber que precisa de muito tempo , haverá muitas do código legado com problemas. Em um mundo Java, você adicionaria
@deprecated
à API, todo mundo a ignorará e você ficará impedido de manter um código obsoleto e com erros. :-( (Presumo que outras línguas sejam semelhantes!)Nesse caso em particular, não tenho certeza de qual seria a melhor opção, e a resposta @candiedorange está totalmente correta, há muitas ocasiões em que você precisa de getters, mas este exemplo ilustra as desvantagens. Todo public getter tende a "trancar" você em uma determinada implementação. Use-os se você realmente precisar "ultrapassar fronteiras", mas use o mínimo possível e com cuidado e prudência.
fonte
timepoint
etimespan
respectivamente. (epoch
é um valor particular detimepoint
.) Nessas classes, eles fornecem getters comogetInt
ougetLong
ougetDouble
. Se as classes que manipulam o tempo são escritas para que (1) delegem aritmética relacionada ao tempo a esses objetos e métodos, e (2) forneçam acesso a esses objetos de tempo por meio de getters, o problema é resolvido, sem a necessidade de se livrar dos getters .Eu acho que a primeira chave é lembrar que os absolutos estão sempre errados.
Considere um programa de catálogo de endereços, que simplesmente armazena as informações de contato das pessoas. Mas, vamos fazer o certo e dividir em camadas de controlador / serviço / repositório.
Não vai ser uma
Person
classe que contém dados reais: nome, endereço, número de telefone, etc ..Address
ePhone
são classes, também, para que possamos utilizar polimorfismo, mais tarde, aos regimes de apoio não-americanos. Todas as três classes são objetos de transferência de dados , portanto, não serão mais que getters e setters. E isso faz sentido: eles não terão nenhuma lógica além da substituiçãoequals
egetHashcode
; pedir para que você forneça uma representação das informações de uma pessoa completa não faz sentido: é para apresentação em HTML, um aplicativo GUI personalizado, um aplicativo de console, um ponto de extremidade HTTP etc.?É aí que entram o controlador, o serviço e o repositório. Todas essas classes serão muito lógicas e leves, graças à injeção de dependência.
O controlador receberá um serviço injetado, o que expõe métodos como
get
esave
, os quais terão alguns argumentos razoáveis (por exemplo,get
podem ter substituições para pesquisar por nome, pesquisar por CEP ou apenas obter tudo;save
provavelmente serão necessários um únicoPerson
e salve-o). Quais getters o controlador teria? Ser capaz de obter o nome da classe concreta pode ser útil para o registro, mas que estado ela conterá além do estado que foi injetado nela?Da mesma forma, a camada de serviço injetará um repositório, para que ele possa realmente obter e persistir
Person
s, e possa ter alguma "lógica de negócios" (por exemplo, talvez exijamos que todos osPerson
objetos tenham pelo menos um endereço ou telefone número). Mas, novamente: que estado esse objeto tem além do que é injetado? Novamente, nenhum.A camada de repositório é onde as coisas ficam um pouco mais interessantes: ele estabelecerá uma conexão com um local de armazenamento, por exemplo. um arquivo, um banco de dados SQL ou um armazenamento na memória. Aqui é tentador adicionar um getter para obter o nome do arquivo ou a cadeia de conexão SQL, mas esse é o estado que foi injetado na classe. O repositório deve ter um getter para saber se está ou não conectado com êxito ao seu armazenamento de dados? Bem, talvez, mas para que servem essas informações fora da própria classe do repositório? A própria classe do repositório deve ser capaz de tentar se reconectar ao seu armazenamento de dados, se necessário, o que sugere que a
isConnected
propriedade já possui um valor duvidoso. Além disso, umaisConnected
propriedade provavelmente estará errada exatamente quando mais precisaria estar correta: verificarisConnected
antes de tentar obter / armazenar dados, não garante que o repositório ainda esteja conectado quando a chamada "real" for feita, portanto, não elimina a necessidade de tratamento de exceção em algum lugar (há argumentos para onde isso deve ir além o escopo desta questão).Considere também testes de unidade (você está escrevendo testes de unidade, certo?): O serviço não espera que uma classe específica seja injetada, mas espera que uma implementação concreta de uma interface seja injetada. Isso permite que o aplicativo como um todo mude onde os dados são armazenados sem ter que fazer nada além de trocar o repositório que está sendo injetado no serviço. Ele também permite que o serviço seja testado por unidade, injetando um repositório simulado. Isso significa pensar em termos de interfaces, em vez de classes. A interface do repositório exporia getters? Ou seja, existe algum estado interno que seria: comum a todos os repositórios, útil fora do repositório e não injetado no repositório? Eu seria pressionado a pensar em qualquer um.
TL; DR
Conclusão: além das classes cujo único objetivo é transportar dados, nada mais na pilha tem um lugar para colocar um getter: o estado é injetado ou irrelevante fora da classe trabalhadora.
fonte
Membros mutáveis.
Se você tem uma coleção de coisas em um objeto que você expõe por meio de um getter, você está potencialmente se expondo a erros associados às coisas erradas sendo adicionadas à coleção.
Se você tem um objeto mutável que está expondo por meio de um getter, está potencialmente se abrindo para que o estado interno do seu objeto seja alterado de uma maneira que você não espera.
Um exemplo muito ruim seria um objeto que está contando de 1 a 100, expondo seu valor atual como uma referência mutável. Isso permite que um objeto de terceiros altere o valor de Current de forma que o valor possa estar fora dos limites esperados.
Isso é principalmente um problema com a exposição de objetos mutáveis. Expor estruturas ou objetos imutáveis não é um problema, pois qualquer alteração nessas alterações altera uma cópia (geralmente por uma cópia implícita no caso de uma estrutura ou por uma cópia explícita no caso de um objeto imutável).
Usar uma metáfora que facilite ver a diferença.
Uma flor tem várias coisas únicas. Tem um
Color
e tem um número de pétalas (NumPetals). Se estou observando aquela flor, no mundo real, posso ver claramente sua cor. Posso então tomar decisões com base nessa cor. Por exemplo, se a cor é preta, não dê para a namorada, mas se a cor for vermelha, dê para a namorada. Em nosso modelo de objeto, a cor da flor seria exposta como um getter no objeto da flor. Minha observação dessa cor é importante para as ações que executarei, mas não afeta o objeto da flor. Eu não deveria ser capaz de mudar a cor dessa flor. Da mesma forma, não faz sentido ocultar a propriedade da cor. Uma flor geralmente não pode impedir as pessoas de observar sua cor.Se eu pintar a flor, devo chamar o método ReactToDye (Color dyeColor) na flor, que mudará a flor de acordo com suas regras internas. Em seguida, posso consultar a
Color
propriedade novamente e reagir a qualquer alteração após a chamada do método ReactToDye. Seria errado modificar diretamente oColor
da flor e, se pudesse, a abstração se desintegraria.Às vezes (com menos frequência, mas ainda com frequência suficiente), os setters são um projeto OO bastante válido. Se eu tiver um objeto Customer, é bastante válido expor um setter para o endereço deles. Se você chama isso
setAddress(string address)
ouChangeMyAddressBecauseIMoved(string newAddress)
simplesmentestring Address { get; set; }
é uma questão de semântica. O estado interno desse objeto precisa mudar e a maneira apropriada de fazer isso é definir o estado interno desse objeto. Mesmo se eu precisar de um registro histórico de endereços em queCustomer
ele morou, posso usar o configurador para alterar meu estado interno adequadamente. Nesse caso, não faz sentido oCustomer
imutável e não há melhor maneira de alterar um endereço do que fornecer um setter para fazê-lo.Não tenho certeza de quem está sugerindo que Getters e setters são ruins ou quebram paradigmas orientados a objetos, mas se o fizerem, provavelmente o estão fazendo em resposta a um recurso específico da linguagem que eles usam. Tenho a sensação de que isso surgiu da cultura Java "tudo é um objeto" (e possivelmente se estendeu a outras linguagens). O mundo .NET não tem essa discussão. Getters e Setters são um recurso de idioma de primeira classe que é usado não apenas por pessoas que escrevem aplicativos no idioma, mas também pela própria API do idioma.
Você deve ter cuidado ao usar getters. Você não deseja expor os dados incorretos e nem os dados da maneira errada (por exemplo, objetos mutáveis, referências a estruturas que podem permitir que um consumidor modifique o estado interno). Mas você deseja modelar seus objetos para que os dados sejam encapsulados de forma que seus objetos possam ser usados por consumidores externos e protegidos contra uso indevido por esses mesmos consumidores. Muitas vezes, isso exigirá getters.
Para resumir: Getters e Setters são ferramentas válidas de design orientado a objetos. Eles não devem ser usados de maneira tão liberal que todos os detalhes da implementação sejam expostos, mas você não deseja usá-los com moderação para que os consumidores de um objeto não possam usá-lo com eficiência.
fonte
A manutenção quebrará.
A qualquer momento (devo dizer quase sempre), você oferece um incentivo, basicamente doa mais do que lhe foi pedido. Isso também significa que você precisa manter mais do que o solicitado, e assim reduz a capacidade de manutenção sem motivo real.
Vamos com o
Collection
exemplo de @ candied_orange . Ao contrário do que ele escreveCollection
, não deveria ter um getter. As coleções existem por motivos muito específicos, principalmente para iterar todos os itens. Essa funcionalidade não é uma surpresa para ninguém, portanto deve ser implementada noCollection
, em vez de empurrar esse caso de uso óbvio para o usuário, usando ciclos for e getters ou outros enfeites.Para ser justo, alguns limites fazer getters necessidade. Isso acontece se você realmente não sabe para que serão usados. Por exemplo, você pode obter programaticamente o rastreamento de pilha da
Exception
classe Java hoje em dia, porque as pessoas queriam usá-lo por todos os tipos de razões curiosas que os escritores não imaginavam ou não podiam imaginar.fonte
Collection
s, simultaneamente, como em(A1, B1), (A2, B2), ...
. Isso requer uma implementação de "zip". Como você implementa "zip" se a função de iteração reversa é implementada em um dos doisCollection
- como você acessa o item correspondente no outro?Collection
tem que implementarzip
sozinho, dependendo de como a biblioteca é escrita. Caso contrário, você o implementa e envia uma solicitação de recebimento ao criador da biblioteca. Note que concordei que algumas fronteiras precisam de getters às vezes, meu problema real é que getters estão em toda parte hoje em dia.Collection
objeto, pelo menos para sua própria implementação dezip
. (Ou seja, o getter tem que ter visibilidade pacote, pelo menos.) E agora você depender do criador da biblioteca aceitar o seu pedido puxar ...Collection
obviamente pode acessar seu próprio estado interno, ou para ser mais preciso qualquerCollection
instância pode acessar estado interno de qualquer outro. É do mesmo tipo, sem necessidade de getters.