Ao usar o encadeamento de métodos, como:
var car = new Car().OfBrand(Brand.Ford).OfModel(12345).PaintedIn(Color.Silver).Create();
pode haver duas abordagens:
Reutilize o mesmo objeto, assim:
public Car PaintedIn(Color color) { this.Color = color; return this; }
Crie um novo objeto do tipo
Car
a cada etapa, como este:public Car PaintedIn(Color color) { var car = new Car(this); // Clone the current object. car.Color = color; // Assign the values to the clone, not the original object. return car; }
O primeiro está errado ou é uma escolha pessoal do desenvolvedor?
Acredito que a primeira abordagem possa causar rapidamente o código intuitivo / enganoso. Exemplo:
// Create a car with neither color, nor model.
var mercedes = new Car().OfBrand(Brand.MercedesBenz).PaintedIn(NeutralColor);
// Create several cars based on the neutral car.
var yellowCar = mercedes.PaintedIn(Color.Yellow).Create();
var specificModel = mercedes.OfModel(99).Create();
// Would `specificModel` car be yellow or of neutral color? How would you guess that if
// `yellowCar` were in a separate method called somewhere else in code?
Alguma ideia?
coding-style
readability
method-chaining
Arseni Mourzenko
fonte
fonte
var car = new Car(Brand.Ford, 12345, Color.Silver);
?Respostas:
Eu colocaria a API fluente em sua própria classe "builder" separada do objeto que está criando. Dessa forma, se o cliente não quiser usar a API fluente, você ainda poderá usá-la manualmente e não poluir o objeto de domínio (aderindo ao princípio de responsabilidade única). Nesse caso, o seguinte seria criado:
Car
qual é o objeto de domínioCarBuilder
que contém a API fluenteO uso seria assim:
A
CarBuilder
classe ficaria assim (estou usando a convenção de nomenclatura C # aqui):Observe que essa classe não será segura para threads (cada thread precisará de sua própria instância do CarBuilder). Observe também que, embora a API fluente seja um conceito muito interessante, provavelmente é um exagero com o objetivo de criar objetos de domínio simples.
Esse acordo é mais útil se você estiver criando uma API para algo muito mais abstrato e tiver configuração e execução mais complexas, e é por isso que funciona muito bem em testes de unidade e estruturas de DI. Você pode ver outros exemplos na seção Java do artigo da wikipedia Fluent Interface com objetos de persistência, manipulação de datas e simulação.
EDITAR:
Como observado nos comentários; você poderia tornar a classe Builder uma classe interna estática (dentro de Car) e Car poderia ser imutável. Este exemplo de deixar Car imutável parece um pouco tolo; mas em um sistema mais complexo, onde você absolutamente não deseja alterar o conteúdo do objeto que é criado, convém fazê-lo.
Abaixo está um exemplo de como fazer a classe interna estática e como lidar com uma criação de objeto imutável que ela constrói:
O uso seria o seguinte:
Edit 2: Pete nos comentários fez um post no blog sobre o uso de construtores com funções lambda no contexto de gravação de testes de unidade com objetos de domínio complexos. É uma alternativa interessante para tornar o construtor um pouco mais expressivo.
No caso de
CarBuilder
você precisar ter esse método:Que pode ser usado assim:
fonte
build()
(ouBuild()
), não o nome do tipo que ele constrói (Car()
no seu exemplo). Além disso, seCar
for um objeto verdadeiramente imutável (por exemplo, todos os seus campos sãoreadonly
), mesmo o construtor não poderá modificá-lo, de modo que oBuild()
método se tornará responsável pela construção da nova instância. Uma maneira de fazer isso éCar
ter apenas um construtor, o que leva um construtor como argumento; então oBuild()
método pode apenasreturn new Car(this);
.Depende.
Seu carro é um objeto de entidade ou de valor ? Se o carro é uma entidade, a identidade do objeto é importante, portanto, você deve retornar a mesma referência. Se o objeto for um objeto de valor, ele deve ser imutável, o que significa que a única maneira é retornar uma nova instância sempre.
Um exemplo deste último seria a classe DateTime no .NET, que é um objeto de valor.
No entanto, se o modelo for uma entidade, eu gosto da resposta de Spoike sobre o uso de uma classe de construtor para criar seu objeto. Em outras palavras, esse exemplo que você deu apenas faz sentido IMHO se o carro for um objeto de valor.
fonte
Crie um construtor interno estático separado.
Use argumentos normais do construtor para os parâmetros necessários. E API fluente para opcional.
Não crie um novo objeto ao definir a cor, a menos que renomeie o método NewCarInColour ou algo semelhante.
Eu faria algo assim com a marca conforme necessário e o restante opcional (isso é java, mas o seu parece javascript, mas com certeza eles são intercambiáveis com um pouco de nit nit):
fonte
O mais importante é que, seja qual for a decisão que você escolher, ela estará claramente declarada no nome e / ou comentário do método.
Não existe um padrão; algumas vezes o método retornará um novo objeto (a maioria dos métodos String o fará) ou retornará esse objeto para fins de encadeamento ou eficiência de memória.
Certa vez, projetei um objeto de vetor 3D e, para todas as operações matemáticas, tinha dois métodos implementados. Por instante, o método de escala:
fonte
scale
(o mutador) escaledBy
(o gerador).Vejo aqui alguns problemas que acho confusos ... Sua primeira linha na pergunta:
Você está chamando um construtor (novo) e um método create ... Um método create () quase sempre seria um método estático ou um método construtor, e o compilador deve capturá-lo em um aviso ou erro para que você saiba, Dessa forma, essa sintaxe está errada ou tem nomes terríveis. Mas mais tarde, você não usa os dois, então vamos ver isso.
Novamente com o create, apenas não com um novo construtor. O problema é que acho que você está procurando um método copy (). Então, se esse for o caso, e é apenas um nome ruim, vejamos uma coisa ... você chama mercedes.Paintedin (Color.Yellow) .Copy () - Deve ser fácil olhar para isso e dizer que está sendo pintado 'antes de ser copiado - apenas um fluxo normal de lógica, para mim. Então coloque a cópia primeiro.
para mim, é fácil ver lá que você está pintando a cópia, fazendo o seu carro amarelo.
fonte
A primeira abordagem tem a desvantagem que você mencionou, mas desde que você esclareça nos documentos, qualquer codificador semi-competente não deve ter problemas. Todo o código de encadeamento de métodos com o qual trabalhei pessoalmente funcionou dessa maneira.
A segunda abordagem obviamente tem a desvantagem de ser mais trabalhosa. Você também precisa decidir se as cópias que você devolve vão fazer cópias rasas ou profundas: o melhor pode variar de classe para classe ou método para método, para que você esteja introduzindo inconsistências ou comprometendo o melhor comportamento. Vale a pena notar que esta é a única opção para objetos imutáveis, como strings.
Faça o que fizer, não misture e combine na mesma classe!
fonte
Prefiro pensar como o mecanismo "Extension Methods".
fonte
Esta é uma variação dos métodos acima. As diferenças são que existem métodos estáticos na classe Car que correspondem aos nomes dos métodos no Builder, portanto, você não precisa criar explicitamente um Builder:
É possível usar os mesmos nomes de método usados nas chamadas do construtor em cadeia:
Além disso, existe um método .copy () na classe que retorna um construtor preenchido com todos os valores da instância atual, para que você possa criar uma variação em um tema:
Por fim, o método .build () do construtor verifica se todos os valores necessários foram fornecidos e lançados, se houver algum. Pode ser preferível exigir alguns valores no construtor do construtor e permitir que o restante seja opcional; nesse caso, você desejaria um dos padrões nas outras respostas.
fonte