— Привіт, Аміго! Сьогодні я розповім тобі трохи цікавих речей про клас BufferedInputStream, але почнемо ми з «обгортки» та «мішка цукру ».

— Це що ще за «обгортка» та «мішок цукру»?

— Це – метафори. Слухай. Отже…

Паттерн проектування обгортка (Wrapper або Decorator) – це досить простий і зручний механізм розширення функціональності об'єктів.

BufferedInputStream - 1

Нехай у нас є клас Cat з двома методами getName та setName:

Код на Java Опис
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;
 }
}
Клас Кіт має два методи: getName & setName
public static void main(String[] args)
{
 Cat cat = new Cat("Васька");

 printName(cat);
}

public static void printName(Сat cat)
{
 System.out.println(cat.getName());
}
Приклад використання.

У консоль буде виведено рядок «Васька».

Припустимо, нам потрібно перехопити виклик методів у об'єкта cat і, можливо, внести туди невеликі зміни. Для цього нам знадобиться повернути його у свій клас-обгортку.

Якщо ми хочемо обернути виклики методів якогось об'єкта своїм кодом, то нам потрібно:

1) Створити свій клас-обгортку та успадкуватися від класу/інтерфейсу, для якого робимо обгортку.

2) Передати об'єкт, що обертається, у конструктор нашого класу.

3) Перевизначити всі методи в нашому новому класі та викликати в них методи об'єкта, що обертається.

4) Внести свої зміни до смаку: змінювати результати дзвінків, параметри або робити щось ще.

У прикладі нижче ми перехоплюємо виклик методу getName у об'єкта cat і трохи змінюємо його результат.

Код на Java Опис
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;
 }
}
Клас Кот містить два методи – отримати ім'я та встановити ім'я.
class CatWrapper extends Cat
{
 private Cat original;
 public CatWrapper (Cat cat)
 {
  super(cat.getName()) ;
  this.original = cat;
 }

 public String getName()
 {
  return "Кіт на ім'я" + original.getName();
 }

 public void setName(String name)
 {
  original.setName(name);
 }
}
Клас-обгортка. Клас не зберігає жодних даних, крім посилання на оригінальний об'єкт.
Клас може «прокидати» виклики оригінальному об'єкту (setName), переданому йому в конструкторі. А також «перехоплювати» ці виклики та модифікувати їх параметри та результати.
public static void main(String[] args)
{
 Cat cat = new Cat("Васька");
 Cat catWrap = новий CatWrapper (cat);
 printName(catWrap);
}

public static void printName(Cat named)
{
 System.out.println(named.getName());
}
Приклад використання.

У консоль буде виведено рядок
«Кіт на ім'я Васька».

Тобто. ми тихенько підмінюємо кожен оригінальний об'єкт на об'єкт-обертку, в який вже передаємо посилання на оригінальний об'єкт. Всі виклики методів обгортки йдуть до оригінального об'єкта, і все працює як годинник.

— Мені сподобалося. Рішення нескладне та функціональне.

— Ще я розповім тобі про «мішок цукру», але це не патерн, а метафора. Метафора до слова буфер і буферизація. Що ж таке буферизація і для чого вона потрібна?

BufferedInputStream - 2

Припустимо, сьогодні чергу Ріші готувати, а ти йому допомагаєш. Ріші ще немає, а я хочу випити чай і прошу тебе принести мені ложечку цукру. Ти пішов у підвал, там стоїть мішок із цукром. Ти можеш принести мені цілий мішок, але мішок мені не потрібний. Мені потрібна лише одна ложка. Тоді ти, як добрий робот, набрав одну ложку і приніс мені. Я додала її до чаю, але все одно не дуже солодко. І я попросила ще одну. Ти знову сходив у підвал і приніс ложку. Потім прийшла Еллі, і я попросила тебе принести цукру для неї… Це все надто довго та неефективно.

Прийшов Ріша, подивився на все це і попросив тебе принести йому повну цукорницю цукру. Потім я та Еллі стали просити цукор у Ріші. Він просто давав його нам із цукорниці, і все.

Те, що сталося після появи Ріші називається буферизацією, а цукорниця – це буфер. Завдяки буферизації «клієнти» можуть читати дані з буфера маленькими порціями, а буфер, щоб заощадити час і сили, читає їх із джерела великими порціями.

— Класний приклад, Кім. Я все зрозумів. Прохання ложки цукру – це аналог читання із потоку одного байта.

— Так. Клас BufferedInputStream – класичний представник обгортки-буфера. Він – клас-обгортка над InputStream. При читанні даних з нього він читає їх з оригінального InputStream'а великими порціями в буфер, а потім віддає з буфера потихеньку.

— Чудово. Все зрозуміло. А буфери для запису бувають?

— Так, звичайно.

— А чи можна приклад?

— Уяви собі відро для сміття. Замість того, щоб щоразу ходити викидати сміття на вулиці в дезінтегратор, ти просто викидаєш його у відро для сміття. А Скрафі раз на два тижні виносить його надвір. Класичний буфер.

BufferedInputStream - 3

— Як цікаво. І набагато зрозуміліше, до речі, ніж із мішком цукру.

— А метод flush() – це винести сміття негайно. Можна використовувати перед відвідуванням гостей.