「こんにちは、アミーゴ! 今日は BufferedInputStream クラスについていくつか興味深いことをお話しますが、まずは «ラッパー» と «砂糖の袋» から始めましょう。」

「《包装紙》と《砂糖の袋》ってどういう意味ですか?」

「これらは比喩です。聞いてください。つまり…」

«ラッパー» (または «デコレータ») デザイン パターンは、継承を使用せずにオブジェクトの機能を拡張するための非常にシンプルで便利なメカニズムです。

BufferedInputStream - 1

getName と setName という 2 つのメソッドを持つ 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;
 }
}
Cat クラスには getName と setName という 2 つのメソッドがあります。
public static void main(String[] args)
{
 Cat cat = new Cat("Oscar");

 printName(cat);
}

public static void printName(Cat cat)
{
 System.out.println(cat.getName());
}
使用方法の一例。

コンソールに「オスカー」が表示されます。

catオブジェクトのメソッド呼び出しをインターセプトし、いくつかの小さな変更を加える必要があるとします。このためには、それを独自のラッパー クラスでラップする必要があります。

オブジェクトのメソッド呼び出しの周りに独自のコードを「ラップ」したい場合は、次のことを行う必要があります。

1)独自のラッパー クラスを作成し、ラップされるオブジェクトと同じクラス/インターフェイスを継承します。

2)ラップするオブジェクトをクラスのコンストラクターに渡します。

3)新しいクラスのすべてのメソッドをオーバーライドします。オーバーライドされた各メソッド内で、ラップされたオブジェクトのメソッドを呼び出します。

4)必要な変更を加えます。メソッド呼び出しの内容を変更したり、パラメータを変更したり、その他のことを実行します。

以下の例では、Cat オブジェクトの getName メソッドへの呼び出しをインターセプトし、その戻り値をわずかに変更します。

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;
 }
}
Cat クラスには、getName と setName という 2 つのメソッドが含まれています。
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);
 }
}
ラッパークラス。このクラスには、元のオブジェクトへの参照以外のデータは格納されません。
このクラスは、コンストラクターに渡された元のオブジェクト (setName) への呼び出しを「スロー」できます。また、これらの呼び出しを「キャッチ」し、そのパラメータや結果を変更することもできます。
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());
}
使用方法の一例。

「オスカーという名の猫」。
コンソールに表示されます

言い換えれば、元の各オブジェクトを、元のオブジェクトへのリンクを受け取るラッパー オブジェクトに静かに置き換えます。ラッパー上のすべてのメソッド呼び出しは元のオブジェクトに転送され、すべてが時計仕掛けのように実行されます。

「気に入っています。このソリューションはシンプルで機能的です。」

「«砂糖の袋» についてもお話します。これはデザイン パターンというよりは比喩です。緩衝と緩衝という言葉の比喩です。緩衝とは何ですか、なぜそれが必要なのでしょうか?」

BufferedInputStream - 2

今日はリシが料理をする番で、あなたが彼を手伝っているとしましょう。リシはまだ来ていませんが、お茶が飲みたいです。スプーン一杯の砂糖を持ってきてくださいとお願いします。地下室に行くと砂糖の入った袋が見つかります。バッグごと持ってきていただいても構いませんが、そのバッグは必要ありません。スプーン一杯だけ必要です。それから、優秀なロボットのように、スプーンを 1 杯取り、私のところに持ってきます。お茶に入れてみましたが、まだ甘みが足りません。そして、もう一つ持ってきてくださいとお願いします。あなたは再び地下室に行き、別のスプーンを持ってきます。そこにエリーがやって来て、彼女のために砂糖を持ってくるように頼みます...これはすべて時間がかかりすぎて非効率です。

リシがやって来て、すべてを見て、砂糖の入ったシュガーボウルを持ってくるように頼みます。それからエリーと私はリシに砂糖を頼み始めます。彼はそれをシュガーボウルから私たちに提供するだけで、それだけです。

リシが現れた後に起こったことはバッファリングと呼ばれます。シュガーボウルはバッファーです。バッファリングのおかげで、「クライアント」はバッファからデータを少しずつ読み取ることができますが、バッファは時間と労力を節約するためにソースからデータを大量に読み取ります。

「それは素晴らしい例ですね、キム。私は完全に理解しています。スプーン一杯の砂糖を要求するのは、ストリームから 1 バイトを読み取るようなものです。」

「その通りです。BufferedInputStreamクラスは、バッファ付きラッパーの古典的な例です。これは、InputStream クラスをラップします。元の InputStream からデータを大きなブロックでバッファに読み取り、それをバッファから少しずつ取り出します。そこから読んでください。」

「非常に良いです。すべて明らかです。書き込み用のバッファはありますか?」

"はい。"

「例としては?」

「ゴミ箱を想像してみてください。毎回外に出て焼却炉にゴミを入れる代わりに、ゴミをゴミ箱に投げ込むだけです。その後、ババは2週間に1回ゴミ箱を外に持ち出します。古典的な緩衝材です。」

「なんと興味深いことでしょう!ちなみに、砂糖の袋よりもはるかに透明です。」

「そして、flush() メソッドはゴミをすぐに出すようなものです。ゲストが到着する前に使用できます。」