"Xin chào, Amigo! Hôm nay tôi sẽ kể cho bạn một vài điều thú vị về lớp BufferedInputStream, nhưng hãy bắt đầu với « giấy gói » và « túi đường »."

"Cậu nói «giấy gói» và «túi đường» nghĩa là gì?"

"Đây là những phép ẩn dụ. Nghe này. Vì vậy..."

Mẫu thiết kế «trình bao bọc» (hoặc «trang trí») là một cơ chế khá đơn giản và thuận tiện để mở rộng chức năng của đối tượng mà không cần sử dụng tính kế thừa.

BufferedInputStream - 1

Giả sử chúng ta có một lớp Cat với hai phương thức: getName và setName:

mã Java Sự miêu tả
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;
 }
}
Lớp Cat có hai phương thức: getName và 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());
}
Một ví dụ về cách nó có thể được sử dụng.

«Oscar» sẽ được hiển thị trên bảng điều khiển.

Giả sử chúng ta cần chặn một lệnh gọi phương thức trên một đối tượng mèo và có thể thực hiện một số thay đổi nhỏ. Đối với điều này, chúng ta cần bọc nó trong lớp trình bao bọc của chính nó.

Nếu chúng ta muốn "bọc" mã của riêng mình xung quanh phương thức gọi trên một số đối tượng, thì chúng ta cần phải:

1) Tạo lớp trình bao bọc của riêng chúng ta và kế thừa từ cùng một lớp/giao diện làm đối tượng được bao bọc.

2) Truyền đối tượng được bao bọc cho hàm tạo của lớp chúng ta.

3) Ghi đè tất cả các phương thức trong lớp mới của chúng tôi. Gọi các phương thức của đối tượng được bao bọc bên trong mỗi phương thức được ghi đè.

4) Thực hiện bất kỳ thay đổi nào bạn muốn: thay đổi những gì phương thức gọi thực hiện, thay đổi tham số của chúng và/hoặc làm điều gì đó khác.

Trong ví dụ bên dưới, chúng tôi chặn các cuộc gọi đến phương thức getName của đối tượng Cat và thay đổi một chút giá trị trả về của nó.

mã Java Sự miêu tả
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;
 }
}
Lớp Cat chứa hai phương thức: getName và 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);
 }
}
Lớp bao bọc. Lớp không lưu trữ bất kỳ dữ liệu nào ngoại trừ tham chiếu đến đối tượng ban đầu.
Lớp có thể "ném" các cuộc gọi đến đối tượng ban đầu (setName) được truyền cho hàm tạo. Nó cũng có thể "bắt" các cuộc gọi này và sửa đổi các tham số và/hoặc kết quả của chúng .
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());
}
Một ví dụ về cách nó có thể được sử dụng.

«Chú mèo tên Oscar».
sẽ được hiển thị trên bảng điều khiển

Nói cách khác, chúng ta lặng lẽ thay thế từng đối tượng ban đầu bằng một đối tượng bao bọc, đối tượng này sẽ nhận được một liên kết đến đối tượng ban đầu. Tất cả các cuộc gọi phương thức trên trình bao bọc được chuyển tiếp đến đối tượng ban đầu và mọi thứ chạy như đồng hồ.

"Tôi thích nó. Giải pháp rất đơn giản và thiết thực."

"Tôi cũng sẽ kể cho bạn nghe về «túi đường». Đây là một phép ẩn dụ hơn là một mẫu thiết kế. Một phép ẩn dụ cho từ đệm và đệm. Đệm là gì và tại sao chúng ta cần nó?"

BufferedInputStream - 2

Giả sử hôm nay đến lượt Rishi nấu ăn và bạn đang giúp anh ấy. Rishi vẫn chưa ở đây, nhưng tôi muốn uống trà. Tôi yêu cầu bạn mang cho tôi một thìa đường. Bạn đi xuống tầng hầm và tìm một túi đường. Bạn có thể mang cho tôi cả cái túi, nhưng tôi không cần cái túi. Tôi chỉ cần một muỗng đầy. Sau đó, giống như một người máy tốt bụng, bạn lấy một thìa đầy và mang đến cho tôi. Tôi thêm nó vào trà, nhưng nó vẫn không đủ ngọt. Và tôi yêu cầu bạn mang cho tôi một cái nữa. Bạn lại xuống tầng hầm và mang một thìa đầy khác. Sau đó, Ellie đi cùng, và tôi nhờ bạn mang đường cho cô ấy... Tất cả những việc này mất quá nhiều thời gian và không hiệu quả.

Rishi đến, nhìn thấy tất cả những điều này và yêu cầu bạn mang cho anh ta một bát đường đầy đường. Sau đó, Ellie và tôi bắt đầu xin đường cho Rishi. Anh ấy chỉ đơn giản phục vụ chúng tôi từ bát đường, và thế thôi.

Điều xảy ra sau khi Rishi xuất hiện được gọi là đệm : bát đường là đệm. Nhờ bộ đệm, "máy khách" có thể đọc dữ liệu từ bộ đệm theo từng phần nhỏ , trong khi bộ đệm, để tiết kiệm thời gian và công sức, đọc chúng từ nguồn theo phần lớn .

"Đó là một ví dụ hay, Kim. Tôi hoàn toàn hiểu. Yêu cầu một thìa đường giống như đọc một byte từ một luồng."

"Chính xác. Lớp BufferedInputStream là một ví dụ cổ điển về trình bao bọc được đệm. Nó bao bọc lớp InputStream. Nó đọc dữ liệu từ InputStream gốc trong các khối lớn vào bộ đệm, sau đó kéo từng phần ra khỏi bộ đệm khi chúng ta đọc từ đó."

"Rất tốt. Tất cả đều rõ ràng. Có viết đệm không?"

"Ồ chắc chắn rồi."

"Có thể là một ví dụ?"

"Hãy tưởng tượng một cái thùng rác. Thay vì phải đi ra ngoài để bỏ rác vào lò đốt mỗi lần, bạn chỉ cần ném nó vào thùng rác. Sau đó, Bubba mang cái thùng ra ngoài hai tuần một lần. Một bộ đệm cổ điển."

"Thật thú vị! Nhân tiện, rõ ràng hơn nhiều so với một túi đường."

"Và phương thức flush() giống như đổ rác ngay lập tức. Bạn có thể sử dụng nó trước khi khách đến."