"Olá, amigo! Hoje vou contar algumas coisas interessantes sobre a classe BufferedInputStream, mas vamos começar com « wrappers » e um « saco de açúcar »."

"O que você quer dizer com «invólucro» e «saco de açúcar»?"

"Estas são metáforas. Ouça. Então..."

O padrão de design «wrapper» (ou «decorator») é um mecanismo bastante simples e conveniente para estender a funcionalidade do objeto sem usar herança.

BufferedInputStream - 1

Suponha que temos uma classe Cat com dois métodos: getName e setName:

código Java Descrição
class Cat
{
 private String name;
 public Cat(String name)
 {
  this.name = name;
 }
 public String getName()
 {
  return this.name;
 }
 public void setName(String name)
 {
  this.name = name;
 }
}
A classe Cat tem dois métodos: getName e setName
public static void main(String[] args)
{
 Cat cat = new Cat("Oscar");

 printName(cat);
}

public static void printName(Cat cat)
{
 System.out.println(cat.getName());
}
Um exemplo de como pode ser usado.

«Oscar» será exibido no console.

Suponha que precisamos interceptar uma chamada de método em um objeto cat e talvez fazer algumas pequenas alterações. Para isso, precisamos envolvê -lo em sua própria classe wrapper.

Se quisermos "envolver" nosso próprio código em torno das chamadas de método em algum objeto, precisamos:

1) Crie nossa própria classe wrapper e herde da mesma classe/interface do objeto a ser empacotado.

2) Passe o objeto a ser encapsulado para o construtor da nossa classe.

3) Substitua todos os métodos em nossa nova classe. Invoque os métodos do objeto agrupado dentro de cada um dos métodos substituídos.

4) Faça as alterações que desejar: altere o que as chamadas do método fazem, altere seus parâmetros e/ou faça outra coisa.

No exemplo abaixo, interceptamos chamadas para o método getName de um objeto Cat e alteramos ligeiramente seu valor de retorno.

código Java Descrição
class Cat
{
 private String name;
 public Cat(String name)
 {
  this.name = name;
 }
 public String getName()
 {
  return this.name;
 }
 public void setName(String name)
 {
  this.name = name;
 }
}
A classe Cat contém dois métodos: getName e setName.
class CatWrapper extends Cat
{
 private Cat original;
 public CatWrapper (Cat cat)
 {
  super(cat.getName());
  this.original = cat;
 }

 public String getName()
 {
  return "A cat named " + original.getName();
 }

 public void setName(String name)
 {
  original.setName(name);
 }
}
A classe wrapper. A classe não armazena nenhum dado, exceto uma referência ao objeto original.
A classe é capaz de "lançar" chamadas para o objeto original (setName) passado para o construtor. Ele também pode "capturar" essas chamadas e modificar seus parâmetros e/ou resultados .
public static void main(String[] args)
{
 Cat cat = new Cat("Oscar");
 Cat catWrap = new CatWrapper (cat);
 printName(catWrap);
}

public static void printName(Cat named)
{
 System.out.println(named.getName());
}
Um exemplo de como pode ser usado.

«Um gato chamado Oscar».
será exibido no console

Em outras palavras, substituímos silenciosamente cada objeto original por um objeto wrapper, que recebe um link para o objeto original. Todas as chamadas de método no wrapper são encaminhadas para o objeto original e tudo funciona como um relógio.

"Gostei. A solução é simples e funcional."

"Também falarei sobre um «saco de açúcar». Isso é mais uma metáfora do que um padrão de design. Uma metáfora para a palavra buffer e buffering. O que é buffering e por que precisamos dele?"

BufferedInputStream - 2

Digamos que hoje seja a vez de Rishi cozinhar e você o está ajudando. Rishi ainda não chegou, mas quero beber chá. Peço que me traga uma colher de açúcar. Você vai ao porão e encontra um saco de açúcar. Você pode me trazer a sacola inteira, mas não preciso da sacola. Eu só preciso de uma colher. Então, como um bom robô, você pega uma colher e traz para mim. Eu adiciono ao chá, mas ainda não é doce o suficiente. E peço que me traga mais um. Você vai novamente ao porão e traz outra colherada. Aí vem a Ellie, e peço que traga açúcar para ela... Tudo isso demora muito e é ineficiente.

Rishi chega, vê tudo isso e pede que você traga para ele um açucareiro cheio de açúcar. Então Ellie e eu começamos a pedir açúcar a Rishi. Ele simplesmente nos serve do açucareiro, e isso é tudo.

O que aconteceu depois que Rishi apareceu é chamado de buffer : o açucareiro é um buffer. Graças ao buffer, os "clientes" podem ler dados de um buffer em pequenas porções , enquanto o buffer, para economizar tempo e esforço, os lê da fonte em grandes porções .

"Esse é um exemplo legal, Kim. Entendo perfeitamente. O pedido de uma colher de açúcar é como ler um byte de um fluxo."

"Exatamente. A classe BufferedInputStream é um exemplo clássico de wrapper em buffer. Ele envolve a classe InputStream. Ele lê os dados do InputStream original em grandes blocos em um buffer e, em seguida, os extrai do buffer peça por peça conforme leia dele."

"Muito bem. Está tudo limpo. Existem buffers para escrever?"

"Ah com certeza."

"Talvez um exemplo?"

"Imagine uma lata de lixo. Em vez de sair para colocar o lixo em um incinerador toda vez, você simplesmente joga na lata de lixo. Então Bubba leva a lata para fora uma vez a cada duas semanas. Um amortecedor clássico."

"Que interessante! E bem mais claro que um saco de açúcar, por sinal."

"E o método flush() é como tirar o lixo imediatamente. Você pode usá-lo antes que os convidados cheguem."