segunda-feira, 13 de abril de 2009

DDD: Manter regras de negócio em coleções

Havendo necessidade de utilizar coleções dentro do seu domínio, você pode se deparar com alguns problemas para garantir a implementação de regras de negócio.

Vamos pegar como exemplo as salas de uma escola, dentro da sala estão contidos vários utensílios como cadeiras, carteiras e lousa, uma forma bem simples de modelar ficaria como abaixo:

public class Sala
{
private float areaLousa;
private List utensilios;

public Sala()
{
utensilios = new List();
}

public float AreaLousa
{
get { return areaLousa; }
set { areaLousa = value; }
}

public List Utensilios
{
get { return utensilios; }
set { utensilios = value; }
}
}

public abstract class Utensilio
{
private int id;

public int Id
{
get { return id; }
set { id = value; }
}
}

public class Cadeira : Utensilio
{
private bool almofadada;

public bool Almofadada
{
get { return almofadada; }
set { almofadada = value; }
}
}

public class Lousa : Utensilio
{
private float altura;
private float largura;

public float Altura
{
get { return altura; }
set { altura = value; }
}

public float Largura
{
get { return largura; }
set { largura = value; }
}

public float Area
{
get { return altura * largura; }
}
}

Para adicionar um utensílio a sala, faríamos da seguinte forma:

Lousa lousa = new Lousa();
lousa.Altura = 10;
lousa.Largura = 20;
lousa.Id = 1;

Sala sala = new Sala();
sala.Utensilios.Add(lousa);

Mas vamos começar a aplicar algumas regras simples, como exemplo eu irei aplicar regras somente ao utensílio do tipo lousa:

  • A sala só pode ter uma lousa;
  • A lousa tem que caber na sala;

Para atender as regras acima a nossa classe Sala ficaria da seguinte forma:

public class Sala
{
private float areaLousa;
private List utensilios;
private bool temLousa;

public Sala()
{
utensilios = new List();
}

public void AdicionarUtensilio(Utensilio utensilio)
{
if (utensilio == null)
throw new ArgumentNullException("utensilio");

if (utensilio is Lousa)
{
if (temLousa)
throw new ArgumentException("Já existe uma lousa para a sala", "utensilio");

if ((utensilio as Lousa).Area > this.areaLousa)
throw new ArgumentException("A lousa não cabe na sala", "utensilio");

temLousa = true;
}

utensilios.Add(utensilio);
}

public float AreaLousa
{
get { return areaLousa; }
set { areaLousa = value; }
}

public List Utensilios
{
get { return utensilios; }
set { utensilios = value; }
}

public bool TemLousa
{
get { return temLousa; }
}
}

Agora para adicionar um utensílio a sala, temos que realizar o seguinte procedimento:

Lousa lousa = new Lousa();
lousa.Altura = 10;
lousa.Largura = 20;
lousa.Id = 1;

Cadeira cadeira = new Cadeira();
cadeira.Almofadada = false;

Sala sala = new Sala();
sala.AreaLousa = 250;
sala.AdicionarUtensilio(lousa);
sala.AdicionarUtensilio(cadeira);

Mas temos uma brecha no nosso código, a propriedade “Utensílios” permite que criemos outra lista de utensílios e substitua ao da classe, por isso a tornaremos somente leitura:

public List Utensilios
{
get { return utensilios; }
}

Ainda temos brecha, podemos adicionar um Utensílio na coleção como fizemos no primeiro exemplo sem passar pelo método AdicionarUtensilio e com isso permitindo que sejam burladas as regras para adicionar utensílios.

Para resolver esse problema, temos que fazer com que o atributo “utensilios” também seja exposto somente como readonly, bloqueando o atributo de receber valores sem que seja pelo método AdicionarUtensilio, para que isso seja garantido a propriedade ficará da seguinte forma:

public ReadOnlyCollection Utensilios
{
get { return utensilios.AsReadOnly(); }
}

Desta forma, o método Add da coleção Utensílios não estará disponível para classes externas, tornando o método AdicionarUtensilios() a única forma de adicionar um Utensílio a classe Sala.

Nenhum comentário: