Em muitos aspectos, eu realmente gosto da idéia de interfaces Fluent, mas com todos os recursos modernos de C # (inicializadores, lambdas, parâmetros nomeados), eu me pego pensando: "vale a pena?" E "Esse é o padrão certo para usar?". Alguém poderia me dar, se não uma prática aceita, pelo menos sua própria experiência ou matriz de decisão sobre quando usar o padrão Fluente?
Conclusão:
Algumas boas regras práticas das respostas até agora:
- As interfaces fluentes ajudam muito quando você tem mais ações do que setters, pois as chamadas se beneficiam mais com a passagem do contexto.
- As interfaces fluentes devem ser pensadas como uma camada sobre uma API, não o único meio de uso.
- Os recursos modernos, como lambdas, inicializadores e parâmetros nomeados, podem trabalhar lado a lado para tornar uma interface fluente ainda mais amigável.
Aqui está um exemplo do que quero dizer com os recursos modernos, tornando-o menos necessário. Tomemos, por exemplo, uma interface fluente (talvez um exemplo ruim) que me permita criar um Funcionário como:
Employees.CreateNew().WithFirstName("Peter")
.WithLastName("Gibbons")
.WithManager()
.WithFirstName("Bill")
.WithLastName("Lumbergh")
.WithTitle("Manager")
.WithDepartment("Y2K");
Pode ser facilmente escrito com inicializadores como:
Employees.Add(new Employee()
{
FirstName = "Peter",
LastName = "Gibbons",
Manager = new Employee()
{
FirstName = "Bill",
LastName = "Lumbergh",
Title = "Manager",
Department = "Y2K"
}
});
Eu também poderia ter usado parâmetros nomeados nos construtores neste exemplo.
Respostas:
Escrever uma interface fluente (eu já brinquei com ela) exige mais esforço, mas tem uma recompensa, porque se você fizer certo, a intenção do código de usuário resultante é mais óbvia. É essencialmente uma forma de idioma específico do domínio.
Em outras palavras, se seu código for lido muito mais do que está escrito (e qual código não é?), Considere criar uma interface fluente.
Interfaces fluentes são mais sobre contexto e muito mais do que apenas maneiras de configurar objetos. Como você pode ver no link acima, usei uma API fluente para obter:
objectA.
intellisense, fornece várias dicas. No meu caso acima,plm.Led.
fornece todas as opções para controlar o LED embutido eplm.Network.
fornece o que você pode fazer com a interface de rede.plm.Network.X10.
Fornece o subconjunto de ações de rede para dispositivos X10. Você não conseguirá isso com inicializadores de construtor (a menos que deseje construir um objeto para cada tipo de ação diferente, o que não é idiomático).Uma coisa que normalmente faço é:
Não vejo como você pode fazer algo assim sem uma interface fluente.
Edit 2 : Você também pode fazer melhorias de legibilidade realmente interessantes, como:
fonte
Scott Hanselman fala sobre isso no episódio 260 de seu podcast Hanselminutes com Jonathan Carter. Eles explicam que uma interface fluente é mais como uma interface do usuário em uma API. Você não deve fornecer uma interface fluente como o único ponto de acesso, mas fornecê-la como algum tipo de interface de código no topo da "interface API regular".
Jonathan Carter também fala um pouco sobre o design da API em seu blog .
fonte
Interfaces fluentes são recursos muito poderosos para fornecer dentro do contexto do seu código, quando você usa o raciocínio "certo".
Se seu objetivo é simplesmente criar enormes cadeias de códigos de uma linha como uma espécie de pseudo-caixa preta, provavelmente você está latindo na árvore errada. Se, por outro lado, você o estiver usando para agregar valor à sua interface de API, fornecendo um meio de encadear chamadas de método e melhorar a legibilidade do código, então, com muito bom planejamento e esforço, acho que vale a pena.
Eu evitaria seguir o que parece se tornar um "padrão" comum ao criar interfaces fluentes, onde você nomeia todos os seus métodos fluentes "com" -algo, pois rouba uma interface API potencialmente boa de seu contexto e, portanto, seu valor intrínseco .
A chave é pensar na sintaxe fluente como uma implementação específica de uma linguagem específica do domínio. Como um bom exemplo do que estou falando, dê uma olhada no StoryQ, que emprega fluência como um meio de expressar uma DSL de uma maneira muito valiosa e flexível.
fonte
position.withX(5)
versusposition.getDistanceToOrigin()
Nota inicial: Estou questionando uma suposição na pergunta e tiro minhas conclusões específicas (no final deste post). Como isso provavelmente não resulta em uma resposta abrangente e abrangente, estou marcando isso como CW.
A meu ver, essas duas versões devem significar e fazer coisas diferentes.
Diferentemente da versão não fluente, a versão fluente oculta o fato de que o novo
Employee
também éAdd
editado para a coleçãoEmployees
- apenas sugere que um novo objeto éCreate
d.O significado de
….WithX(…)
é ambíguo, especialmente para pessoas provenientes de F #, que possui umawith
palavra - chave para expressões de objeto : elas podem interpretarobj.WithX(x)
como um novo objeto derivadoobj
idêntico a,obj
exceto por suaX
propriedade, cujo valor éx
. Por outro lado, com a segunda versão, fica claro que nenhum objeto derivado é criado e que todas as propriedades são especificadas para o objeto original.Isso
….With…
tem outro significado: alternar o "foco" da inicialização da propriedade para um objeto diferente. O fato de sua API fluente ter dois significados diferentesWith
dificulta a interpretação correta do que está acontecendo ... e talvez por isso você tenha usado o recuo no seu exemplo para demonstrar o significado pretendido desse código. Seria mais claro assim:Conclusões: "Ocultar" um recurso de linguagem simples o suficiente
new T { X = x }
, com uma API fluente (Ts.CreateNew().WithX(x)
) pode obviamente ser feito, mas:Deve-se tomar cuidado para que os leitores do código fluente resultante ainda entendam exatamente o que ele faz. Ou seja, a API fluente deve ter um significado transparente e inequívoco. Criar uma API desse tipo pode ser mais trabalhoso do que o esperado (pode ser necessário testá-lo para facilitar o uso e a aceitação) e / ou…
projetar pode ser mais trabalhoso do que o necessário: neste exemplo, a API fluente adiciona muito pouco "conforto do usuário" à API subjacente (um recurso de linguagem). Pode-se dizer que uma API fluente deve tornar o recurso API / idioma subjacente "mais fácil de usar"; isto é, deve poupar ao programador uma quantidade considerável de esforço. Se é apenas outra maneira de escrever a mesma coisa, provavelmente não vale a pena, porque não facilita a vida do programador, mas apenas dificulta o trabalho do designer (veja a conclusão nº 1 logo acima).
Os dois pontos acima assumem silenciosamente que a API fluente é uma camada sobre uma API ou recurso de idioma existente. Essa suposição pode ser outra boa orientação: uma API fluente pode ser uma maneira extra de fazer algo, não a única. Ou seja, pode ser uma boa ideia oferecer uma API fluente como uma opção de "inclusão".
fonte
Eu gosto do estilo fluente, ele expressa intenção muito claramente. Com o exemplo de inicializador de objetos que você tem depois, é necessário ter criadores de propriedades públicas para usar essa sintaxe, não com o estilo fluente. Dizendo isso, com o seu exemplo, você não ganha muito com os setters públicos porque quase optou por um estilo set / get estilo java-esque.
O que me leva ao segundo ponto, não tenho certeza se eu usaria o estilo fluente da maneira que você usa, com muitos criadores de propriedades, provavelmente usaria a segunda versão para isso, acho melhor quando você tenha muitos verbos para encadear, ou pelo menos muitas ações em vez de configurações.
fonte
Eu não conhecia o termo interface fluente , mas ele me lembra algumas APIs que usei, incluindo LINQ .
Pessoalmente, não vejo como os recursos modernos do C # impediriam a utilidade dessa abordagem. Prefiro dizer que eles andam de mãos dadas. Por exemplo, é ainda mais fácil conseguir essa interface usando métodos de extensão .
Talvez esclareça sua resposta com um exemplo concreto de como uma interface fluente pode ser substituída usando um dos recursos modernos que você mencionou.
fonte