Eu tenho uma classe chamada Heading
que faz algumas coisas, mas também deve ser capaz de retornar o oposto do valor atual do cabeçalho, que finalmente deve ser usado através da criação de uma nova instância da Heading
própria classe.
Posso ter uma propriedade simples chamada reciprocal
para retornar o cabeçalho oposto do valor atual e, em seguida, criar manualmente uma nova instância da classe Heading, ou posso criar um método createReciprocalHeading()
para criar automaticamente uma nova instância da classe Heading e retorná-la ao diretório do utilizador.
No entanto, um de meus colegas recomendou que eu apenas criasse uma propriedade de classe chamada reciprocal
que retornasse uma nova instância da própria classe por meio do método getter.
Minha pergunta é: não é um anti-padrão que uma propriedade de uma classe se comporte dessa maneira?
Eu particularmente acho isso menos intuitivo porque:
- Na minha opinião, uma propriedade de uma classe não deve retornar uma nova instância de uma classe e
- O nome da propriedade, ou seja
reciprocal
, não ajuda o desenvolvedor a entender completamente seu comportamento, sem obter ajuda do IDE ou verificar a assinatura do getter.
Estou sendo muito rigoroso sobre o que uma propriedade de classe deve fazer ou é uma preocupação válida? Eu sempre tentei gerenciar o estado da classe através de seus campos e propriedades e seu comportamento através de seus métodos, e não consigo ver como isso se encaixa na definição da propriedade da classe.
fonte
Heading
um tipo imutável ereciprocal
retornar um novoHeading
é uma boa prática "poço de sucesso". (com a ressalva para as duas chamadas parareciprocal
deve retornar "a mesma coisa", ou seja, eles devem passar o teste de igualdade.)Respostas:
Não é desconhecido ter coisas como Copy () ou Clone (), mas sim, acho que você está certo em se preocupar com isso.
considere por exemplo:
Seria bom ter algum aviso de que a propriedade era um novo objeto a cada vez.
você também pode assumir que:
Contudo. Se sua classe de cabeçalho fosse um tipo de valor imutável, você seria capaz de implementar esses relacionamentos e recíproco pode ser um bom nome para a operação
fonte
Copy()
eClone()
são métodos, não propriedades. Acredito que o OP esteja se referindo a getters de propriedades retornando novas instâncias.String.Substring()
,Array.Reverse()
,Int32.GetHashCode()
, etc.If your heading class was an immutable value type
- Acho que você deve adicionar que os setters de propriedades em objetos imutáveis sempre retornem as novas instâncias (ou pelo menos se o novo valor não for o mesmo que o antigo), porque essa é a definição de objetos imutáveis. :)Uma interface de uma classe leva o usuário da classe a fazer suposições sobre como ela funciona.
Se muitas dessas suposições estão corretas e poucas estão erradas, a interface é boa.
Se muitas dessas suposições estão erradas e poucas estão certas, a interface é um lixo.
Uma suposição comum sobre Propriedades é que chamar a função get é barato. Outra suposição comum sobre propriedades é que chamar a função get duas vezes seguidas retornará a mesma coisa.
Você pode contornar isso usando consistência, a fim de alterar as expectativas. Por exemplo, para uma pequena biblioteca 3D em que você precisa
Vector
,Ray
Matrix
etc, você pode fazer com que getters comoVector.normal
eMatrix.inverse
sempre sejam caros. Infelizmente, mesmo que suas interfaces usem consistentemente propriedades caras, as interfaces serão, na melhor das hipóteses, tão intuitivas quanto aquelas que usamVector.create_normal()
eMatrix.create_inverse()
- ainda não conheço nenhum argumento forte que possa ser feito de que o uso de propriedades crie uma interface mais intuitiva, mesmo depois de alterar as expectativas.fonte
As propriedades devem retornar muito rapidamente, retornar o mesmo valor com chamadas repetidas e obter seu valor não deve ter efeitos colaterais. O que você descreve aqui não deve ser implementado como uma propriedade.
Sua intuição está correta. Um método de fábrica não retorna o mesmo valor com chamadas repetidas. Ele retorna uma nova instância a cada vez. Isso praticamente o desqualifica como propriedade. Também não é rápido se o desenvolvimento posterior adicionar peso à criação da instância, por exemplo, dependências de rede.
As propriedades geralmente devem ser operações muito simples que obtêm / configuram parte do estado atual da classe. As operações de fábrica não atendem a esse critério.
fonte
DateTime.Now
propriedade é comumente usada e refere-se a um novo objeto. Eu acho que o nome de uma propriedade pode ajudar a remover a ambiguidade sobre se o mesmo objeto é retornado ou não a cada vez. Está tudo no nome e como é usado.DateTime
é um tipo de valor e não tem conceito de identidade do objeto. Se bem entendi, o problema é que não é determinístico e está retornando um novo valor a cada vez.DateTime.New
é estático e, portanto, você não pode esperar que ele represente o estado de um objeto.Eu não acho que haja uma resposta independente de idioma para isso, já que o que constitui uma "propriedade" é uma pergunta específica do idioma, e o que um chamador de uma "propriedade" espera também é uma pergunta específica do idioma. Eu acho que a maneira mais proveitosa de pensar sobre isso é pensar no que parece do ponto de vista do chamador.
Em C #, as propriedades são distintas por serem capitalizadas (convencionalmente) (como métodos), mas não possuem parênteses (como variáveis de instância pública). Se você vir o código a seguir, documentação ausente, o que você espera?
Como um novato em C # relativo, mas que leu as Diretrizes de uso de propriedades da Microsoft , eu esperaria
Reciprocal
, entre outras coisas:Heading
classeReciprocalChanged
eventoDessas suposições, (3) e (4) provavelmente estão corretas (supondo que
Heading
seja um tipo de valor imutável, como na resposta de Ewan ), (1) é discutível, (2) é desconhecido, mas também discutível e (5) é improvável que faça sentido semântico (embora o que quer que tenha um cabeçalho talvez deva ter umHeadingChanged
evento). Isso me sugere que em uma API C #, "obter ou calcular o recíproco" não deve ser implementado como uma propriedade, mas principalmente se o cálculo for barato eHeading
imutável, é um caso limítrofe.(Observe, porém, que nenhuma dessas preocupações tem nada a ver com a possibilidade de chamar a propriedade criar uma nova instância , nem mesmo (2). Criar objetos no CLR, por si só, é bastante barato.)
Em Java, as propriedades são uma convenção de nomenclatura de método. Se eu ver
minhas expectativas são semelhantes às acima (se menos explícitas): espero que a ligação seja barata, idempotente e sem efeitos colaterais. No entanto, fora da estrutura do JavaBeans, o conceito de "propriedade" não é tão significativo em Java, e particularmente ao considerar uma propriedade imutável sem correspondência
setReciprocal()
, agetXXX()
convenção agora é um tanto antiquada. Do Effective Java , segunda edição (já com mais de oito anos):Em uma API contemporânea e mais fluente, então, eu esperaria ver
- o que sugere novamente que a chamada é barata, idempotente e sem efeitos colaterais, mas não diz nada sobre a realização de um novo cálculo ou a criação de um novo objeto. Isto é bom; em uma boa API, eu não deveria me importar.
Em Ruby, não existe uma propriedade. Existem "atributos", mas se eu vir
Não tenho como saber imediatamente se estou acessando uma variável de instância
@reciprocal
por meio de umattr_reader
método ou simples de acessador ou se estou chamando um método que executa um cálculo caro. O fato de o nome do método ser um substantivo simples, no entanto, em vez de dizercalcReciprocal
, sugere novamente que a chamada é pelo menos barata e provavelmente não tem efeitos colaterais.Em Scala, a convenção de nomenclatura é que métodos com efeitos colaterais recebem parênteses e métodos sem eles, mas
pode ser um dos seguintes:
(Observe que o Scala permite várias coisas que, no entanto, são desencorajadas pelo guia de estilo . Esse é um dos meus principais aborrecimentos com o Scala.)
A falta de parênteses me diz que a ligação não tem efeitos colaterais; o nome, novamente, sugere que a ligação deve ser relativamente barata. Além disso, eu não me importo como isso me dá valor.
Em resumo: conheça o idioma que você está usando e saiba quais expectativas outros programadores trarão para sua API. Tudo o resto é um detalhe de implementação.
fonte
Como outros já disseram, é um padrão bastante comum retornar instâncias da mesma classe.
A nomeação deve andar de mãos dadas com as convenções de nomeação do idioma.
Por exemplo, em Java, eu provavelmente esperaria que fosse chamado
getReciprocal();
Dito isto, eu consideraria objetos mutáveis vs imutáveis .
Com os imutáveis , as coisas são bem fáceis e não machucam se você devolver o mesmo objeto ou não.
Com os mutáveis , isso pode ser muito assustador.
Agora, a que b está se referindo? O recíproco do valor original de a? Ou o recíproco do que mudou? Pode ser um exemplo muito curto, mas você entendeu.
Nesses casos, procure um nome melhor que comunique o que acontece, por exemplo,
createReciprocal()
pode ser uma escolha melhor.Mas isso realmente depende do contexto também.
fonte
getReciprocal()
, pois isso "soa" como um getter de estilo JavaBeans "normal". Prefere "createXXX ()" ou possivelmente "calculateXXX ()" que, indicar ou dica para outros programadores que algo diferente está acontecendoNo interesse da responsabilidade única e da clareza, eu teria um ReverseHeadingFactory que pegasse o objeto e retornasse seu reverso. Isso deixaria mais claro que um novo objeto estava sendo retornado e significaria que o código para produzir o inverso foi encapsualizado para longe de outro código.
fonte
Depende. Normalmente, espero que as propriedades retornem coisas que fazem parte de uma instância, portanto, retornar uma instância diferente da mesma classe seria um pouco incomum.
Mas, por exemplo, uma classe de string pode ter propriedades "firstWord", "lastWord" ou se manipular Unicode "firstLetter", "lastLetter", que seriam objetos de string completos - apenas geralmente menores.
fonte
Como as outras respostas abrangem a parte Propriedade, abordarei seu número 2 sobre
reciprocal
:Não use outra coisa senão
reciprocal
. É o único termo correto para o que você está descrevendo (e é o termo formal). Não escreva software incorreto para evitar que seus desenvolvedores aprendam o domínio em que estão trabalhando. Na navegação, os termos têm significados muito específicos e usam algo aparentemente inócuoreverse
ouopposite
pode causar confusão em determinados contextos.fonte
Logicamente, não, não é propriedade de um cabeçalho. Se fosse, você também poderia dizer que um número negativo é propriedade desse número e, francamente, faria com que "propriedade" perdesse todo o significado, quase tudo seria uma propriedade.
Recíproca é uma função pura de um cabeçalho, o mesmo que negativo de um número é uma função pura desse número.
Como regra geral, se a configuração não fizer sentido, provavelmente não deve ser uma propriedade. Defini-lo ainda pode não ser permitido, é claro, mas adicionar um levantador deve ser teoricamente possível e significativo.
Agora, em alguns idiomas, pode fazer sentido tê-lo como uma propriedade, conforme especificado no contexto desse idioma, caso traga algumas vantagens devido à mecânica desse idioma. Por exemplo, se você deseja armazená-lo em um banco de dados, provavelmente é sensato torná-lo propriedade, se permitir o manuseio automático da estrutura do ORM. Ou talvez seja uma linguagem em que não haja distinção entre uma propriedade e uma função-membro sem parâmetros (não conheço essa linguagem, mas tenho certeza de que há algumas). Cabe ao designer e documentador da API distinguir entre funções e propriedades.
Edit: Para C # especificamente, eu examinaria as classes existentes, especialmente as da Biblioteca Padrão.
BigInteger
eComplex
estruturas. Mas são estruturas e têm apenas funções estáticas, e todas as propriedades são de tipos diferentes. Portanto, não há muita ajuda de design para suaHeading
classe.Vector
classe , que possui muito poucas propriedades e parece bem próxima da suaHeading
. Se você passar por isso, terá uma função recíproca, não uma propriedade.Você pode querer olhar para as bibliotecas realmente usadas pelo seu código ou para as classes similares existentes. Tente manter a consistência, geralmente é o melhor, se um caminho não estiver claramente certo e o outro errado.
fonte
Pergunta muito interessante mesmo. Não vejo violação do SOLID + DRY + KISS no que você está propondo, mas, ainda assim, cheira mal.
Um método que retorna uma instância da classe é chamado de construtor , certo? então você está criando uma nova instância a partir de um método não construtor. Não é o fim do mundo, mas como cliente, eu não esperava isso. Isso geralmente é feito em circunstâncias específicas: uma fábrica ou, às vezes, um método estático da mesma classe (útil para singletons e para ... programadores não tão orientados a objetos?)
Plus: o que acontece se você invocar
getReciprocal()
o objeto retornado? você obtém outra instância da classe, que pode ser uma cópia exata da primeira! E se você invocargetReciprocal()
naquele? Objetos espalhando alguém?Novamente: e se o invocador precisar do valor recíproco (como escalar, quero dizer)?
getReciprocal()->getValue()
? estamos violando a Lei de Deméter por pequenos ganhos.fonte