« Bonjour, Amigo ! Aujourd'hui, je vais te dire quelques petites choses intéressantes sur la classe BufferedInputStream, mais commençons par les 'wrappers' et un 'sac de sucre'. »

« Qu'est-ce que tu entends par 'wrapper' et 'sac de sucre' ? »

« Ce sont des métaphores. Écoute bien. Tu vois… »

Le modèle de conception appelé 'wrapper' (ou 'décorateur') est un mécanisme assez simple et pratique pour étendre la fonctionnalité d'un objet sans utiliser l'héritage.

BufferedInputStream - 1

Supposons que nous avons une classe Cat avec deux méthodes, getName et setName :

Code Java Description
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;
 }
}
La classe Cat a deux méthodes : getName et 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());
}
Un exemple d'utilisation possible.

'Oscar' s'affichera dans la console.

Supposons que nous avons besoin d'intercepter un appel de méthode sur un objet cat et éventuellement de faire quelques petits changements. Pour cela, nous devons l'emballer dans sa propre classe wrapper.

Si nous voulons « emballer » notre propre code autour des appels de méthode sur un objet, nous devons :

1) Créer notre propre classe wrapper et hériter de la même classe/interface que l'objet à emballer.

2) Passer l'objet à emballer au constructeur de notre classe.

3) Remplacer toutes les méthodes dans notre nouvelle classe. Invoquer les méthodes de l'objet emballé dans chacune des méthodes remplacées.

4) Apporte les changements que tu veux : change ce que les méthodes font, leurs paramètres et/ou autre chose.

Dans l'exemple ci-dessous, nous interceptons les appels à une méthode getName de l'objet Cat et modifions légèrement sa valeur de retour.

Code Java Description
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;
 }
}
La classe Cat contient deux méthodes : getName et 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);
 }
}
La classe wrapper. La classe ne stocke pas de données à l'exception d'une référence à l'objet original.
La classe peut « lancer » des appels à l'objet original (setName) passé au constructeur. Elle peut également « capturer » ces appels et modifier leurs paramètres et/ou résultats.
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());
}
Un exemple d'utilisation possible.

« Un chat nommé Oscar »
s'affichera dans la console

En d'autres termes, nous remplaçons discrètement chaque objet d'origine avec un objet wrapper, qui reçoit un lien vers l'objet d'origine. Tous les appels de méthode sur le wrapper sont passés à l'objet d'origine, et tout marche comme sur des roulettes.

« Ça me plaît. La solution est simple et fonctionnelle. »

« Je vais aussi te parler d'un 'sac de sucre'. C'est une métaphore plutôt qu'un modèle de conception. Une métaphore pour les termes tampon et mise en mémoire tampon. Qu'est-ce que la mise en mémoire tampon et pourquoi en avons-nous besoin ? »

BufferedInputStream - 2

Disons que c'est aujourd'hui le tour de Rishi de cuisiner, et que tu l'aides. Rishi n'est pas encore là, mais je veux boire du thé. Je te demande de m'apporter une cuillerée de sucre. Tu vas à la cave et tu trouves un sac de sucre. Tu m'apportes le sac entier, mais je n'ai pas besoin du sac. Je n'ai besoin que d'une cuillerée. Alors, en bon robot que tu es, tu en prends une cuillerée et tu me l'apportes. Je l'ajoute au thé, mais il n'est pas encore assez sucré. Alors je te demande de m'en apporter plus. Tu retournes à la cave et tu m'apportes une autre cuillerée. Et là, Ellie arrive, et te demande de lui apporter du sucre à elle aussi... Tout cela prend trop de temps et est inefficace.

Rishi vient, voit tout cela, et te demande de lui apporter un bol plein de sucre. Puis Ellie et moi-même commençons à demander du sucre à Rishi. Il nous le sert simplement à partir du bol de sucre, vite et bien.

Ce qui est survenu une fois que Rishi est arrivé est ce qu'on appelle la mise en mémoire tampon : le bol de sucre est un tampon. Grâce à ce tampon, les 'clients' peuvent lire les données à partir d'un tampon par petites portions, tandis que le tampon, pour gagner du temps et s'épargner des efforts, les lit à la source en grandes portions.

« J'aime bien ton exemple, Kim. Je comprends parfaitement. La demande d'une cuillerée de sucre revient à lire un octet dans un flux. »

« Exactement. La classe BufferedInputStream est un exemple classique de wrapper mis en mémoire tampon. Il emballe la classe InputStream. Il lit des données de l'InputStream d'origine par grands blocs dans un tampon, puis l'extrait ensuite pièce par pièce quand nous le lisons. »

« Très bien. Tout est clair. Et il y a des tampons pour l'écriture ? »

« Oh, évidemment. »

« Un exemple, peut-être ? »

« Imagine une poubelle. Au lieu d'aller dehors mettre tes ordures dans un incinérateur à chaque fois, tu les jettes juste dans la poubelle. Ensuite Bubba sort la poubelle une fois toutes les deux semaines. C'est un tampon classique. »

« C'est super intéressant ! Et bien plus clair que l'histoire du sac de sucre, d'ailleurs. »

« Et la méthode flush() revient à sortir les poubelles immédiatement. Tu peux l'utiliser avant que les invités arrivent. »