Bằng cách gói một thư viện bên thứ ba, bạn thêm một lớp trừu tượng bổ sung lên trên nó. Điều này có một vài lợi thế:
Cơ sở mã của bạn trở nên linh hoạt hơn với các thay đổi
Nếu bạn cần thay thế thư viện bằng một thư viện khác, bạn chỉ cần thay đổi triển khai trong trình bao bọc của mình - ở một nơi . Bạn có thể thay đổi việc triển khai trình bao bọc và không phải thay đổi điều gì khác, nói cách khác, bạn có một hệ thống kết nối lỏng lẻo. Nếu không, bạn sẽ phải trải qua toàn bộ cơ sở mã của mình và sửa đổi ở mọi nơi - đó rõ ràng không phải là điều bạn muốn.
Bạn có thể xác định API của trình bao bọc độc lập với API của thư viện
Các thư viện khác nhau có thể có các API khác nhau rất lớn và đồng thời không ai trong số chúng có thể chính xác là những gì bạn cần. Điều gì xảy ra nếu một số thư viện cần mã thông báo được chuyển cùng với mỗi cuộc gọi? Bạn có thể chuyển mã thông báo xung quanh trong ứng dụng của mình bất cứ nơi nào bạn cần sử dụng thư viện hoặc bạn có thể bảo vệ nó ở một nơi nào đó tập trung hơn, nhưng trong mọi trường hợp bạn cần mã thông báo. Lớp trình bao bọc của bạn làm cho toàn bộ điều này trở lại đơn giản - bởi vì bạn chỉ có thể giữ mã thông báo bên trong lớp trình bao bọc của mình, không bao giờ để lộ nó cho bất kỳ thành phần nào trong ứng dụng của bạn và hoàn toàn không cần đến nó. Một lợi thế rất lớn nếu bạn từng sử dụng một thư viện không nhấn mạnh vào thiết kế API tốt.
Kiểm thử đơn vị là cách đơn giản hơn
Kiểm tra đơn vị chỉ nên kiểm tra một điều. Nếu bạn muốn kiểm tra đơn vị một lớp, bạn phải chế giễu các phụ thuộc của nó. Điều này càng trở nên quan trọng hơn nếu lớp đó thực hiện các cuộc gọi mạng hoặc truy cập một số tài nguyên khác bên ngoài phần mềm của bạn. Bằng cách gói thư viện của bên thứ ba, thật dễ dàng để giả lập các cuộc gọi đó và trả lại dữ liệu kiểm tra hoặc bất cứ điều gì mà bài kiểm tra đơn vị yêu cầu. Nếu bạn không có một lớp trừu tượng như vậy, việc này sẽ trở nên khó khăn hơn nhiều - và hầu hết thời gian điều này dẫn đến rất nhiều mã xấu.
Bạn tạo ra một hệ thống kết nối lỏng lẻo
Các thay đổi đối với trình bao bọc của bạn không có tác dụng đối với các phần khác trong phần mềm của bạn - ít nhất là miễn là bạn không thay đổi hành vi của trình bao bọc của mình. Bằng cách giới thiệu một lớp trừu tượng như trình bao bọc này, bạn có thể đơn giản hóa các cuộc gọi đến thư viện và gần như loại bỏ hoàn toàn sự phụ thuộc của ứng dụng của bạn vào thư viện đó. Phần mềm của bạn sẽ chỉ sử dụng trình bao bọc và nó sẽ không tạo ra sự khác biệt về cách trình bao bọc được thực hiện hoặc cách thức thực hiện.
Ví dụ thực tế
Hãy Trung thực. Mọi người có thể tranh luận về những lợi thế và bất lợi của một cái gì đó như thế này trong nhiều giờ - đó là lý do tại sao tôi thay vì chỉ cho bạn một ví dụ.
Giả sử bạn có một số loại ứng dụng Android và bạn cần tải xuống hình ảnh. Có một loạt các thư viện ngoài kia làm cho việc tải và lưu trữ hình ảnh trở nên dễ dàng, ví dụ như Picasso hoặc Trình tải hình ảnh phổ quát .
Bây giờ chúng ta có thể định nghĩa một giao diện mà chúng ta sẽ sử dụng để bao bọc bất kỳ thư viện nào chúng ta kết thúc bằng cách sử dụng:
public interface ImageService {
Bitmap load(String url);
}
Đây là giao diện bây giờ chúng ta có thể sử dụng trong toàn bộ ứng dụng bất cứ khi nào chúng ta cần tải hình ảnh. Chúng ta có thể tạo một triển khai giao diện này và sử dụng phép nội xạ phụ thuộc để thêm một thể hiện của triển khai đó ở mọi nơi chúng ta sử dụng ImageService
.
Giả sử ban đầu chúng tôi quyết định sử dụng Picasso. Bây giờ chúng ta có thể viết một triển khai ImageService
sử dụng Picasso trong nội bộ:
public class PicassoImageService implements ImageService {
private final Context mContext;
public PicassoImageService(Context context) {
mContext = context;
}
@Override
public Bitmap load(String url) {
return Picasso.with(mContext).load(url).get();
}
}
Khá thẳng về phía trước nếu bạn hỏi tôi. Wrapper quanh các thư viện không cần phải phức tạp để có ích. Giao diện và cách triển khai có ít hơn 25 dòng mã kết hợp, do đó hầu như không có bất kỳ nỗ lực nào để tạo ra điều này, nhưng chúng tôi đã đạt được điều gì đó bằng cách làm điều này. Xem Context
lĩnh vực trong việc thực hiện? Khung tiêm phụ thuộc mà bạn chọn sẽ quan tâm đến việc tiêm phụ thuộc đó trước khi chúng tôi sử dụng ImageService
, ứng dụng của bạn bây giờ không phải quan tâm đến cách hình ảnh được tải xuống và bất kỳ phụ thuộc nào mà thư viện có thể có. Tất cả các ứng dụng của bạn nhìn thấy là một ImageService
và khi nó cần một hình ảnh, nó gọi load()
bằng một url - đơn giản và dễ hiểu.
Tuy nhiên, lợi ích thực sự đến khi chúng ta bắt đầu thay đổi mọi thứ. Hãy tưởng tượng bây giờ chúng ta cần thay thế Picasso bằng Trình tải hình ảnh phổ quát vì Picasso không hỗ trợ một số tính năng mà chúng tôi hoàn toàn cần ngay bây giờ. Bây giờ chúng ta có phải lướt qua cơ sở mã của mình và thay thế một cách tẻ nhạt tất cả các cuộc gọi đến Picasso và sau đó xử lý hàng tá lỗi biên dịch vì chúng ta quên một vài cuộc gọi Picasso không? Không. Tất cả những gì chúng ta cần làm là tạo ra một triển khai mới ImageService
và nói với khung tiêm phụ thuộc của chúng ta để sử dụng triển khai này kể từ bây giờ:
public class UniversalImageLoaderImageService implements ImageService {
private final ImageLoader mImageLoader;
public UniversalImageLoaderImageService(Context context) {
DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(true)
.build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
.defaultDisplayImageOptions(defaultOptions)
.build();
mImageLoader = ImageLoader.getInstance();
mImageLoader.init(config);
}
@Override
public Bitmap load(String url) {
return mImageLoader.loadImageSync(url);
}
}
Như bạn có thể thấy việc thực hiện có thể rất khác nhau, nhưng nó không quan trọng. Chúng tôi đã không phải thay đổi một dòng mã ở bất kỳ nơi nào khác trong ứng dụng của mình. Chúng tôi sử dụng một thư viện hoàn toàn khác nhau có thể có các tính năng hoàn toàn khác nhau hoặc có thể được sử dụng rất khác nhau nhưng ứng dụng của chúng tôi không quan tâm. Giống như trước khi phần còn lại của ứng dụng của chúng tôi chỉ nhìn thấy ImageService
giao diện với load()
phương thức của nó và tuy nhiên phương thức này được triển khai không còn quan trọng nữa.
Ít nhất với tôi điều này tất cả đã nghe khá hay rồi, nhưng chờ đã! Vẫn còn nhiều nữa. Hãy tưởng tượng bạn đang viết bài kiểm tra đơn vị cho một lớp bạn đang làm việc và lớp này sử dụng ImageService
. Tất nhiên, bạn không thể để các kiểm tra đơn vị của mình thực hiện các cuộc gọi mạng đến một số tài nguyên trên một số máy chủ khác nhưng vì hiện tại bạn đang sử dụng nên ImageService
bạn có thể dễ dàng load()
trả lại một tĩnh Bitmap
được sử dụng cho các kiểm tra đơn vị bằng cách thực hiện một thử nghiệm giả ImageService
:
public class MockImageService implements ImageService {
private final Bitmap mMockBitmap;
public MockImageService(Bitmap mockBitmap) {
mMockBitmap = mockBitmap;
}
@Override
public Bitmap load(String url) {
return mMockBitmap;
}
}
Để tóm tắt bằng cách gói các thư viện của bên thứ ba, cơ sở mã của bạn trở nên linh hoạt hơn với các thay đổi, nói chung đơn giản hơn, dễ kiểm tra hơn và bạn giảm việc ghép các thành phần khác nhau trong phần mềm của mình - tất cả những điều quan trọng hơn là bạn duy trì phần mềm càng lâu.