Mutável com lógica interna ou imutável com lógica externa?

5

Eu sei que objetos imutáveis são preferíveis a objetos mutáveis para raciocínio e manutenção. Mas em ocasiões que tornam uma classe imutável ter alguns custos, deixe-me explicar isso com um exemplo simples:

class Mutable  
{
    private int i;
    public Mutable(int _i) { i = _i}
    public void Print() // impure function
    {
         print(i);
         i++;   // logic inside
    }
}

... caller

Immutable m = new Mutable(1);
while (true)
{        
    m.Print();
}

Eu chamo de mutável porque seu estado interno muda e Print varia em cada chamada.

Versus.

class Immutable  
{
    private int i;
    public Mutable(int _i) { i = _i}
    public int Print() // pure function
    {
         print(i);
         return i;  // returns its state  
    }
}

... caller
int i =1;
while (true)
{
    Immutable im = new Immutable(i);
    i = im.Print(); // get the previous obeject's state
    i++; // logic outside
}

Preferir uma solução imutável me faz fazer muito trabalho fora de um objeto que deveria encapsular essa lógica.

Meu problema real ( você pode pular o exemplo abaixo )

Eu tenho uma turma que desenha um texto em uma caixa:

class TextDrawer
{
   private string text;
   private Rectangle box;
   public TextDrawer(_text, _box) {...}
   public void DrawTextInBox()
   {
   }
}

É provável que os Text excedentes da caixa e parte dela não tenham sido escritos. Eu deveria saber a parte não escrita restante para desenhá-la em uma nova caixa (na verdade eu tiro um instantâneo da caixa antiga como um quadro de vídeo e preciso criar um novo quadro para o texto restante para fazer um vídeo do texto)

Agora tenho duas opções:

1- Torne o TextDrawer mutável (seu estado interno é mutável) e DrawTextInBox uma função não pura e em cada chamada ele desenha a parte restante do texto na caixa.

class TextDrawer
{

   private string text;
   private Rectangle box;
   private string remaining;   

   public TextDrawer(_text, _box)
   {
      remaining = text = _text;
      box = _box;
   }    
   public void DrawTextInBox()
   {
      draw as you can from remaining
      update remaining part;
   }
}

2- Mantenha a função TextDrawer imutável e DrawTextInBox a pura que retorna a parte restante, então o chamador deve rastrear o restante e passá-lo novamente para o TextDrawer ou instanciar um novo TextDrawer por o texto restante até que não haja nada para escrever.

class TextDrawer
{
   private string text;
   private Rectangle box;
   public TextDrawer(_text, _box) { ... }
   public string DrawTextInBox()
   {
       draw as you can from text;
       return remaining part;
   }
}

A segunda abordagem é imutável, mas na verdade muda a responsabilidade de rastrear a parte restante fora da classe. A primeira abordagem é mutável, mas encapsula a lógica e só precisa de uma chamada para DrawTextInBox para desenhar a parte restante.

Pergunta : A lógica dos exemplos acima foi simples, mas essa lógica pode ser mais complexa, então não sei se é necessário extrair essa lógica de uma classe para torná-la imutável ou deixar ser mutável e ter a lógica dentro?

    
por Ahmad 14.04.2015 / 08:54
fonte

1 resposta

3

No seu exemplo:

int i =1;
while (true)
{
    Immutable im = new Immutable(i);
    i = im.Print(); // get the previous obeject's state
    i++; // logic outside
}

Immutable deve retornar o novo estado que a solução mutável teria armazenado, ou seja, i + 1 . Você já tem o estado antigo . Retornar o estado antigo e forçar o chamador a atualizar o estado em si é inútil. Você fez isso corretamente em seu problema real, no qual você retorna a parte restante da string em vez da string original.

Você deve preferir uma solução imutável, a menos que você tenha boas razões para isso. Uma boa razão pode ser que a solução imutável é muito lenta, mas primeiro você precisa definir claramente o que é "rápido o suficiente" e fazer o perfil do aplicativo para mostrar que o gargalo é sua classe imutável e que a alternativa mutável resolverá o problema. p>     

por 14.04.2015 / 18:07
fonte