15 setembro, 2016 3 Comentários AUTOR: elemarjr CATEGORIAS: CSharp, Java Tags:,

Garantindo que recursos sejam liberados (em Java e em C#)

Tempo de leitura: 2 minutos

Algumas classes são projetadas para consumir recursos com custo expressivo, além de memória. Garantir que recursos sejam adequadamente liberados é um grande desafio quando estamos projetando uma API.

Como fazemos normalmente

Em Java, a partir da versão 7, essa seria a abordagem mais comum.

import java.util.*;
import java.util.function.*;

class Resource implements AutoCloseable
{
	public Resource() { System.out.println("created ..."); }
	public void foo() { System.out.println("Foo"); }
	public void fee() { System.out.println("Fee"); }
	public void close() { System.out.println("cleanup...");}
}

public class Program {
	public static void main(String[] args)
	{
		try (Resource r = new Resource()) {
			r.foo();
			r.fee();
		}
	}
}

No exemplo, a interface AutoCloseable habilita a utilização do tipo Resource nessa forma especial da expressão try - isso garante que o método close seja evocado sempre após a execução do bloco, independentemente da ocorrência de uma exceção.

Em C#, temos algo semelhante (há bem mais tempo).

using System;
using static System.Console;

class Resource : IDisposable
{
	public Resource() { WriteLine("created ..."); }
	public void Foo() { WriteLine("Foo"); }
	public void Fee() { WriteLine("Fee"); }
	public void Dispose() { WriteLine("cleanup..."); } 
}

public class Program
{
	public static void Main()
	{
		using (var r = new Resource()) {
			r.Foo();
			r.Fee();
		}
	}
}

Sabores de sintaxe a parte, a solução é exatamente a mesma. Em ambos os casos: marcamos a nossa classe com uma interface, implementamos a liberação de recursos em um método com nome significativo (pessoalmente, prefiro Dispose a close), usamos um açúcar sintático para garantir que o método seja evocado.

Qual é o problema?!

O principal problema é que não há como garantir que o programador que esteja utilizando a classe chame o método para liberação de recursos (usando ou não os açúcares sintáticos). Além disso, temos que ser cuidadosos na implementação da classe para determinar qual deveria ser o comportamento que ela deverá seguir depois que os métodos para liberação de recursos são evocados.

Uma solução melhor

Venkat Subramaniam oferece uma alternativa muito interessante para esse problema nessa excelente palestra.

import java.util.*;
import java.util.function.*;

class Resource
{
	private Resource() { System.out.println("created ..."); }
	public void foo() { System.out.println("Foo"); }
	public void fee() { System.out.println("Fee"); }
	private void close() { System.out.println("cleanup...");}
	
	public static void use(Consumer<Resource> block) {
		Resource resource = new Resource();
		try {
			block.accept(resource);
		} finally {
			resource.close();
		}
	}
}

public class Program {
	public static void main(String[] args)
	{
		Resource.use(r -> {
			r.foo();
			r.fee();
		});
	}
}

Vejamos o que foi feito.

  • Tanto o método close quanto o construtor agora são privados;
  • Não há mais necessidade de implementar interface alguma;
  • A utilização do recurso agora é feita através de um método estático;
  • O método estático é uma high order function que recebe uma expressão lambda com o bloco que desejamos executar;
  • O método estático cria e libera a instância da classe (o que oferece interessantes alternativas de reaproveitamento em momentos oportunos.

Eis uma implementação dessa estratégia em C#.

using System;
using static System.Console;

class Resource
{
	private Resource() { WriteLine("created ..."); }
	public void Foo() { WriteLine("Foo"); }
	public void Fee() { WriteLine("Fee"); }
	private void Dispose() { WriteLine("cleanup..."); } 
	public static void Use(Action<Resource> block)
	{
		var r = new Resource();
		try {
			block(r);
		} finally {
			r.Dispose();
		}
	}
}

public class Program
{
	public static void Main()
	{
		Resource.Use(r => {
			r.Foo();
			r.Fee();
		});
	}
}

Repare que caso o construtor demande parâmetros, bastaria adicionar tais parâmetros ao método estático.

Era isso. O que você acha dessa abordagem?

3 Comentários

  1. Francisco 5 meses ago says:

    Achei muito bom! Parabéns!

    Responder
  2. Robson Castilho 4 meses ago says:

    Aquele momento "por que eu não pensei nisso antes? " 🙂
    Gostei bastante: bem simples, elegante e útil.
    Valeu por compartilhar.

    Responder
  3. Lucas Lopes 4 meses ago says:

    Muito interessante!

    Responder