Eu excluí recentemente uma resposta minha sobre Java na Code Review , que começou assim:
private Person(PersonBuilder builder) {
Pare. Bandeira vermelha. Um PersonBuilder criaria uma Pessoa; conhece uma pessoa. A classe Person não deve saber nada sobre um PersonBuilder - é apenas um tipo imutável. Você criou um acoplamento circular aqui, onde A depende de B, que depende de A.
A Pessoa deve apenas inserir seus parâmetros; um cliente que esteja disposto a criar uma Pessoa sem construí-la deve poder fazer isso.
Eu recebi um voto negativo e disse que (citando) bandeira vermelha, por quê? A implementação aqui tem a mesma forma que Joshua Bloch demonstrou em seu livro "Effective Java" (item # 2).
Portanto, parece que a maneira correta de implementar um padrão de construtor em Java é tornar o construtor um tipo aninhado (não é disso que se trata esta pergunta) e criar o produto (a classe do objeto que está sendo construído ) dependem do construtor , assim:
private StreetMap(Builder builder) { // Required parameters origin = builder.origin; destination = builder.destination; // Optional parameters waterColor = builder.waterColor; landColor = builder.landColor; highTrafficColor = builder.highTrafficColor; mediumTrafficColor = builder.mediumTrafficColor; lowTrafficColor = builder.lowTrafficColor; }
A mesma página da Wikipedia para o mesmo padrão do Builder possui uma implementação totalmente diferente (e muito mais flexível) para C #:
//Represents a product created by the builder public class Car { public Car() { } public int Wheels { get; set; } public string Colour { get; set; } }
Como você pode ver, o produto aqui não sabe nada sobre uma Builder
classe e, apesar de tudo, pode ser instanciado por uma chamada direta do construtor, uma fábrica abstrata, ... ou um construtor - tanto quanto eu entendo, o O produto de um padrão criacional nunca precisa saber nada sobre o que está sendo criado.
Recebi o contra-argumento (que aparentemente é explicitamente defendido no livro de Bloch) de que um padrão de construtor poderia ser usado para refazer um tipo que teria um construtor inchado com dezenas de argumentos opcionais. Então, em vez de seguir o que pensei que sabia, pesquisei um pouco neste site e descobri que, como suspeitava, esse argumento não retém a água .
Então, qual é o problema? Por que apresentar soluções superprojetadas para um problema que nem deveria existir em primeiro lugar? Se tirarmos Joshua Bloch de seu pedestal por um minuto, podemos encontrar uma única razão válida e boa para unir dois tipos de concreto e chamá-lo de melhor prática?
Isso tudo cheira a programação de cultos de carga para mim.
fonte
Respostas:
Discordo da sua afirmação de que é uma bandeira vermelha. Também discordo de sua representação do contraponto, de que existe uma maneira correta de representar um padrão de construtor.
Para mim, há uma pergunta: o Builder é uma parte necessária da API do tipo? Aqui, o PersonBuilder é uma parte necessária da API da pessoa?
É inteiramente razoável que uma classe Person com verificação de validade, imutável e / ou fortemente encapsulada seja necessariamente criada por meio de um Construtor que ele fornece (independentemente de esse construtor estar aninhado ou adjacente). Ao fazer isso, a Pessoa pode manter todos os seus campos privados ou privados e finais do pacote, além de deixar a classe aberta para modificação, se for um trabalho em andamento. (Pode acabar fechado para modificação e aberto para extensão, mas isso não é importante no momento e é especialmente controverso para objetos de dados imutáveis.)
Se for o caso de uma Pessoa ser necessariamente criada por meio de um PersonBuilder fornecido como parte de um único design de API em nível de pacote, um acoplamento circular será ótimo e uma bandeira vermelha não é garantida aqui. Notavelmente, você o declarou como um fato incontestável e não como uma opinião ou escolha de design de API, de modo que isso pode ter contribuído para uma resposta ruim. Eu não teria votado contra você, mas não culpo outra pessoa por fazê-lo, e deixarei o resto da discussão sobre "o que merece voto negativo" para o centro de ajuda e a meta da Code Review . Os votos negativos acontecem; não é "tapa".
Obviamente, se você quiser deixar muitas maneiras de criar um objeto Person, o PersonBuilder poderá se tornar um consumidor ou utilitárioda classe Person, da qual a pessoa não deve depender diretamente. Essa escolha parece mais flexível - quem não gostaria de ter mais opções de criação de objetos? -, mas para garantir garantias (como imutabilidade ou serialização), de repente a Pessoa precisa expor um tipo diferente de técnica de criação pública, como um construtor muito longo. . Se o Construtor tiver a intenção de representar ou substituir um construtor por muitos campos opcionais ou um construtor aberto à modificação, o construtor longo da classe poderá ser um detalhe de implementação melhor deixado oculto. (Lembre-se também de que um Builder oficial e necessário não o impede de escrever seu próprio utilitário de construção, apenas para que ele consuma o Builder como uma API, como na minha pergunta original acima.)
ps: notei que o exemplo de revisão de código que você listou tem uma Pessoa imutável, mas o contra-exemplo da Wikipedia lista um carro mutável com getters e setters. Pode ser mais fácil ver o maquinário necessário omitido no carro se você manter o idioma e os invariantes consistentes entre eles.
fonte
String(StringBuilder)
construtor, o que parece obedecer a esse "princípio", onde é normal que um tipo dependa do construtor. Fiquei espantado quando vi a sobrecarga do construtor. Não é como umaString
tem 42 parâmetros do construtor ...toString()
é universal.Entendo como pode ser irritante quando o "apelo à autoridade" é usado em um debate. Os argumentos devem permanecer por conta própria da IMO e, embora não seja errado apontar o conselho de uma pessoa tão respeitada, ele realmente não pode ser considerado um argumento completo, já que sabemos que o Sol viaja pela Terra porque Aristóteles disse isso. .
Dito isto, acho que a premissa de seu argumento é a mesma. Nunca devemos acoplar dois tipos concretos e chamar isso de melhor prática, porque ... Alguém disse isso?
Para que o argumento de que o acoplamento é problemático seja válido, é necessário que haja problemas específicos que ele crie. Se essa abordagem não estiver sujeita a esses problemas, não será possível argumentar logicamente que essa forma de acoplamento é problemática. Portanto, se você quiser mostrar seu ponto aqui, acho que precisa mostrar como essa abordagem está sujeita a esses problemas e / ou como a dissociação do construtor melhorará o design.
De imediato, estou tendo dificuldades para ver como a abordagem acoplada criará problemas. As classes são completamente acopladas, com certeza, mas o construtor existe apenas para criar instâncias da classe. Outra maneira de ver isso seria como uma extensão do construtor.
fonte
String
está fazendo com uma sobrecarga de construtor usando umStringBuilder
parâmetro (embora seja discutível seStringBuilder
um construtor é um construtor , mas isso é outra história).VBE
simulação, completa com janelas, painel de código ativo, um projeto e um componente com um módulo que contém a sequência fornecida. Sem ele, não sei como seria capaz de escrever 80% dos testes em Rubberduck , pelo menos sem me esfaquear nos olhos toda vez que os olhava.Para responder por que um tipo seria associado a um construtor, é necessário entender por que alguém usaria um construtor em primeiro lugar. Especificamente: Bloch recomenda usar um construtor quando você tiver um grande número de parâmetros do construtor. Esse não é o único motivo para usar um construtor, mas especulo que seja o mais comum. Em resumo, o construtor é um substituto para esses parâmetros do construtor e, muitas vezes, o construtor é declarado na mesma classe que está construindo. Portanto, a classe envolvente já tem conhecimento do construtor e passá-lo como argumento construtor não muda isso. É por isso que um tipo seria associado ao seu construtor.
Por que ter um grande número de parâmetros implica que você deve usar um construtor? Bem, ter um grande número de parâmetros não é incomum no mundo real, nem viola o princípio da responsabilidade única. Também é péssimo quando você tem dez parâmetros de String e está tentando lembrar quais são quais e se é a quinta ou a sexta String que deve ser nula ou não. Um construtor facilita o gerenciamento dos parâmetros e também pode fazer outras coisas legais sobre as quais você deve ler o livro para descobrir.
Quando você usa um construtor que não é associado ao seu tipo? Geralmente, quando os componentes de um objeto não estão disponíveis imediatamente e os objetos que contêm essas peças não precisam saber sobre o tipo que você está tentando construir ou está adicionando um construtor para uma classe sobre a qual você não tem controle. .
fonte
A
Person
turma não sabe o que está criando. Ele só tem um construtor com um parâmetro, e daí? O nome do tipo do parâmetro termina com ... Construtor que realmente não força nada. Afinal, é apenas um nome.O construtor é um glorificado um objeto de configuração. E um objeto de configuração é realmente apenas agrupar propriedades que pertencem uma à outra. Se eles pertencem um ao outro com o objetivo de serem passados para o construtor de uma classe, que assim seja! Ambos
origin
edestination
doStreetMap
exemplo são do tipoPoint
. Seria possível passar as coordenadas individuais de cada ponto para o mapa? Claro, mas as propriedades de umPoint
pertencem juntas, e você pode ter todos os tipos de métodos que ajudam na sua construção:1 A diferença é que o construtor não é apenas um objeto de dados simples, mas permite essas chamadas de setter encadeadas. O que
Builder
mais preocupa é como se construir.Agora vamos adicionar um pouco de padrão de comando à mistura, porque se você olhar de perto, o construtor é realmente um comando:
A parte especial para o construtor é que:
O que está sendo passado para o
Person
construtor pode construí-lo, mas não necessariamente. Pode ser um objeto de configuração antigo simples ou um construtor.fonte
Não, eles estão completamente errados e você está absolutamente certo. Esse modelo de construtor é simplesmente bobo. Não é da conta da Pessoa de onde vêm os argumentos do construtor. O construtor é apenas uma conveniência para os usuários e nada mais.
fonte
PersonBuilder
é de fato uma parte essencial daPerson
API. Tal como está, esta resposta não discute seu caso.