Escrevendo a minha da próxima revista Mundo.Net, sobre ferramentas de Mock, tive contato com as interfaces fluentes. O Rhino Mocks permite que você estabeleça as expectativas do mock em uma interface fluente. Veja como fica o código:
Expect.Call(MailerMock.SendMessage("mail@host.com")).IgnoreArguments().Return(5);
//a mesma chamada em um layout mais fácil de entender
Expect.Call(MailerMock.SendMessage("mail@host.com"))
.IgnoreArguments()
.Return(5);
Ao invés de criar vários objetos e incluir cada objeto em uma propriedade do objeto principal, você simplesmente vai chamando os métodos um após o outro. Fica muito mais fácil de escrever o código, com menos linhas, e muito mais fácil de ler. É quase como uma frase separada por vírgulas. Com o código acima, por exemplo, você espera uma chamada no método SendMessage do objeto MailerMock, não quer validar os argumentos recebidos e quer forçar o retorno da chamada seja igual a 5.
Achei bem interessante e resolvi implementar o exemplo que o Martin Fowler mostra no seu post sobre o assunto. Ele mostra o exemplo de uma criação de uma ordem de compra em uma linha de código. A coisa é mais simples do que parece a primeira vista.
Comecei implementando as classes Customer, Order e OrderLine. Cada um tem suas propriedades, como nome, endereço de entrega, etc. O customer tem um método NewOrder que retorna um objeto Order. Até ai o código é “normal”, então tenho que criar o método With, que recebe os dados para criar uma OrderLine. O pulo do gato é retornar o próprio o objeto Order no método. O método ShipTo segue o mesmo princípio, recebe o endereço, grava na variável e retorna o this.
public class Customer
{
private string _name;
public string Name
{
get { return _name; }
}
public Customer(string name)
{
_name = name;
}
public Order NewOrder()
{
return new Order(this);
}
}
public class Order
{
private Customer _customer;
private List<OrderLine> _orderLines;
private string _shipToAddress;
public Customer Customer
{
get { return _customer; }
}
public List<OrderLine> OrderLines
{
get { return _orderLines; }
}
public string ShipToAddress
{
get { return _shipToAddress; }
}
public Order(Customer customer)
{
_orderLines = new List<OrderLine>();
_customer = customer;
}
public Order With(int itemId, float itemPrice)
{
_orderLines.Add(new OrderLine(itemId, itemPrice));
return this;
}
public Order ShipTo(string address)
{
_shipToAddress = address;
return this;
}
}
public class OrderLine
{
private int _itemId;
private float _itemPrice;
public int ItemId
{
get { return _itemId; }
}
public float ItemPrice
{
get { return _itemPrice; }
}
public OrderLine(int itemId, float itemPrice)
{
_itemId = itemId;
_itemPrice = itemPrice;
}
}
A criação da ordem tanto pode ser no método tradicional como utilizando a interface fluente, como mostra o código abaixo.
//Modo tradicional
Customer cust = new Customer("Other Customer");
Order order = cust.NewOrder();
order.OrderLines.Add(new OrderLine(1, 5.0F));
order.OrderLines.Add(new OrderLine(2, 9.4F));
order.ShipTo("My address");
//Modo fluente
Customer cust = new Customer("My Customer");
Order order = cust.NewOrder()
.With(1, 10.0F)
.With(2, 12.3F)
.ShipTo("My address");
Bom, não foi tão difícil em um exemplo simples, a coisa pode ficar bem mais complicada quando você quer forçar uma ordem para as chamadas ou limitar a quantidade de vezes que um método pode ser chamado.
Algumas aplicações interessantes de interfaces fluentes:
Posts sobre aplicação prática de interfaces fluentes: