โดยการห่อห้องสมุดบุคคลที่สามให้คุณเพิ่มเลเยอร์สิ่งที่เป็นนามธรรมเพิ่มเติมที่ด้านบนของมัน นี่มีข้อดีบางประการ:
ฐานรหัสของคุณมีความยืดหยุ่นมากขึ้นต่อการเปลี่ยนแปลง
ถ้าคุณเคยต้องแทนที่ห้องสมุดกับอีกหนึ่งคนที่คุณจะต้องเปลี่ยนการดำเนินงานของคุณในเสื้อคลุมของคุณ - ในสถานที่หนึ่ง คุณสามารถเปลี่ยนการใช้งานของ wrapper และไม่ต้องเปลี่ยนอะไรเกี่ยวกับสิ่งอื่นในคำอื่น ๆ ที่คุณมีระบบคู่ที่หลวม มิฉะนั้นคุณจะต้องผ่าน codebase ทั้งหมดของคุณและทำการแก้ไขทุกที่ - ซึ่งเห็นได้ชัดว่าไม่ใช่สิ่งที่คุณต้องการ
คุณสามารถกำหนด API ของ wrapper ได้อย่างอิสระจาก API ของไลบรารี
ห้องสมุดที่แตกต่างกันอาจมี API ที่แตกต่างกันอย่างมากมายและในขณะเดียวกันก็ไม่มีสิ่งใดที่คุณต้องการ เกิดอะไรขึ้นถ้าห้องสมุดบางอันต้องการโทเค็นที่จะส่งผ่านไปพร้อมกับการโทรทุกครั้ง? คุณสามารถส่งโทเค็นไปรอบ ๆ ในแอพของคุณได้ทุกที่ที่คุณต้องการใช้ห้องสมุดหรือคุณสามารถเก็บมันไว้ที่ไหนสักแห่งในใจกลางเมือง แต่ในกรณีใดก็ตามที่คุณต้องการโทเค็น คลาส wrapper ของคุณทำให้สิ่งทั้งหมดนี้ง่ายขึ้นอีกครั้ง - เพราะคุณสามารถเก็บโทเค็นไว้ในคลาส wrapper ของคุณได้อย่าเปิดเผยให้ส่วนประกอบใด ๆ ในแอพของคุณและสรุปความต้องการโดยสิ้นเชิง ข้อได้เปรียบอย่างมากหากคุณเคยใช้ห้องสมุดที่ไม่เน้นการออกแบบ API ที่ดี
การทดสอบหน่วยเป็นวิธีที่ง่ายกว่า
การทดสอบหน่วยควรทดสอบสิ่งเดียวเท่านั้น ถ้าคุณต้องการทดสอบหน่วยคุณต้องจำลองการอ้างอิงของมัน สิ่งนี้มีความสำคัญยิ่งขึ้นหากคลาสนั้นทำการโทรผ่านเครือข่ายหรือเข้าถึงทรัพยากรอื่นนอกซอฟต์แวร์ของคุณ โดยการห่อห้องสมุดบุคคลที่สามเป็นการง่ายที่จะจำลองการโทรและส่งคืนข้อมูลการทดสอบหรือสิ่งที่การทดสอบหน่วยต้องการ หากคุณไม่มีเลเยอร์ของสิ่งที่เป็นนามธรรมมันจะกลายเป็นเรื่องยากที่จะทำเช่นนี้ - และส่วนใหญ่แล้วผลลัพธ์นี้จะเป็นรหัสที่น่าเกลียดมาก
คุณสร้างระบบคู่ที่หลวม
การเปลี่ยนแปลง wrapper ของคุณจะไม่มีผลกับส่วนอื่น ๆ ของซอฟต์แวร์ของคุณ - อย่างน้อยตราบใดที่คุณไม่เปลี่ยนพฤติกรรมของ wrapper ด้วยการแนะนำเลเยอร์สิ่งที่เป็นนามธรรมเช่นเสื้อคลุมนี้คุณสามารถลดความซับซ้อนของการโทรไปยังไลบรารีและลบการพึ่งพาแอปของคุณในไลบรารีนั้นออกไปเกือบทั้งหมด ซอฟต์แวร์ของคุณจะใช้ wrapper และจะไม่สร้างความแตกต่างในการใช้ wrapper หรือวิธีการทำงานของมัน
ตัวอย่างการปฏิบัติ
มาซื่อสัตย์กันเถอะ ผู้คนสามารถโต้เถียงเกี่ยวกับข้อดีและข้อเสียของบางสิ่งเช่นนี้มาหลายชั่วโมง - ซึ่งเป็นเหตุผลว่าทำไมฉันถึงค่อนข้างแสดงตัวอย่างให้คุณเห็น
สมมติว่าคุณมีแอพ Android บางประเภทและคุณต้องดาวน์โหลดรูปภาพ มีพวงของห้องสมุดออกมีที่ให้โหลดและแคชภาพสายลมเช่นเป็นปิกัสโซหรือโหลดภาพสากล
ตอนนี้เราสามารถกำหนดอินเทอร์เฟซที่เราจะใช้เพื่อตัดไลบรารีใดก็ตามที่เราใช้:
public interface ImageService {
Bitmap load(String url);
}
นี่คืออินเทอร์เฟซที่ตอนนี้เราสามารถใช้งานได้ตลอดทั้งแอพเมื่อใดก็ตามที่เราต้องการโหลดภาพ ImageService
เราสามารถสร้างการดำเนินงานของอินเตอร์เฟซนี้และใช้ฉีดพึ่งพาการฉีดตัวอย่างของการดำเนินงานที่ทุกที่ที่เราใช้
สมมติว่าเราเริ่มใช้ Picasso เป็นครั้งแรก ตอนนี้เราสามารถเขียนการใช้งานImageService
ที่ใช้ Picasso ภายใน:
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();
}
}
ตรงไปตรงมาถ้าคุณถามฉัน กระดาษห่อหุ้มรอบ ๆ ห้องสมุดไม่จำเป็นต้องมีความซับซ้อนที่จะเป็นประโยชน์ อินเทอร์เฟซและการใช้งานนั้นมีโค้ดที่รวมกันน้อยกว่า 25 บรรทัดดังนั้นมันจึงแทบไม่มีความพยายามในการสร้างสิ่งนี้ แต่เราได้รับบางสิ่งจากการทำเช่นนี้ ดูContext
ฟิลด์ในการใช้งานหรือไม่ เฟรมเวิร์กการพึ่งพาของตัวเลือกที่คุณเลือกจะดูแลการพึ่งพานั้นก่อนที่เราจะใช้ImageService
แอพของคุณตอนนี้ไม่จำเป็นต้องสนใจว่ารูปภาพจะถูกดาวน์โหลดอย่างไรและอะไรก็ตามที่ห้องสมุดมี แอปทั้งหมดของคุณเห็นคือImageService
และเมื่อจำเป็นต้องมีภาพที่มันเรียกload()
พร้อมกับ url - ง่ายและตรงไปตรงมา
อย่างไรก็ตามประโยชน์ที่แท้จริงมาเมื่อเราเริ่มเปลี่ยนสิ่งต่าง ๆ ลองนึกภาพตอนนี้เราจำเป็นต้องแทนที่ Picasso ด้วย Universal Image Loader เพราะ Picasso ไม่รองรับคุณสมบัติบางอย่างที่เราต้องการในตอนนี้ ตอนนี้เราต้องใช้โค๊ดโค้ดโค๊ดของเราและแทนที่การเรียกไปยังปิกัสโซ่อย่างน่าเบื่อและจากนั้นจัดการกับข้อผิดพลาดในการคอมไพล์หลายครั้งเพราะเราลืมโทรปิกัสโซ่ไปสองสามครั้ง? ไม่ทั้งหมดที่เราต้องทำคือสร้างการใช้งานใหม่ImageService
และบอกกรอบการฉีดของเราเพื่อใช้งานการดำเนินการนี้จากนี้:
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);
}
}
ในขณะที่คุณสามารถเห็นการใช้งานอาจแตกต่างกันมาก แต่ก็ไม่สำคัญ เราไม่จำเป็นต้องเปลี่ยนรหัสบรรทัดเดียวที่ใดก็ได้ในแอปของเรา เราใช้ห้องสมุดที่แตกต่างอย่างสิ้นเชิงซึ่งอาจมีคุณสมบัติแตกต่างไปจากเดิมอย่างสิ้นเชิงหรืออาจมีการใช้แตกต่างกันมาก แต่แอพของเราไม่สนใจ เช่นเดียวกับที่ผ่านมาแอพที่เหลือของเราเพิ่งเห็นImageService
อินเทอร์เฟซด้วยload()
วิธีการและวิธีการใช้งานนี้ไม่สำคัญอีกต่อไป
อย่างน้อยสำหรับฉันทั้งหมดนี้แล้วเสียงสวยดีอยู่แล้ว แต่รอ! ยังมีอีกมาก ImageService
ลองนึกภาพคุณกำลังเขียนการทดสอบหน่วยสำหรับการเรียนที่คุณกำลังทำงานบนและชั้นนี้ใช้ แน่นอนว่าคุณไม่สามารถปล่อยให้การทดสอบหน่วยของคุณทำการเรียกเครือข่ายไปยังทรัพยากรบางอย่างที่ตั้งอยู่บนเซิร์ฟเวอร์อื่น แต่เนื่องจากคุณกำลังใช้อยู่ImageService
คุณสามารถปล่อยload()
คืนค่าสแตติกที่Bitmap
ใช้สำหรับการทดสอบหน่วยโดยการใช้การเยาะเย้ยImageService
:
public class MockImageService implements ImageService {
private final Bitmap mMockBitmap;
public MockImageService(Bitmap mockBitmap) {
mMockBitmap = mockBitmap;
}
@Override
public Bitmap load(String url) {
return mMockBitmap;
}
}
ในการสรุปโดยการห่อไลบรารี่ของบุคคลที่สามฐานรหัสของคุณจะมีความยืดหยุ่นมากขึ้นต่อการเปลี่ยนแปลงโดยรวมง่ายขึ้นง่ายต่อการทดสอบและคุณลดการเชื่อมต่อของส่วนประกอบต่าง ๆ ในซอฟต์แวร์ของคุณ - สิ่งต่าง ๆ ที่สำคัญยิ่งขึ้นเรื่อย ๆ