써드 파티 라이브러리를 랩핑하여 그 위에 추가 추상화 계층을 추가합니다. 몇 가지 장점이 있습니다.
코드베이스가 변경에보다 유연 해집니다
라이브러리를 다른 라이브러리로 교체해야하는 경우 래퍼에서 구현 을 한곳 에서 변경하면됩니다 . 래퍼의 구현을 변경할 수 있으며 다른 것에 대해 변경할 필요가 없습니다. 즉, 느슨하게 연결된 시스템을 가지고 있습니다. 그렇지 않으면 전체 코드베이스를 살펴보고 어디에서나 수정해야합니다. 원하는 것은 아닙니다.
라이브러리의 API와 독립적으로 랩퍼의 API를 정의 할 수 있습니다.
다른 라이브러리는 매우 다른 API를 가질 수 있으며 동시에 필요한 것은 아닙니다. 일부 라이브러리는 모든 호출과 함께 토큰을 전달해야하는 경우 어떻게합니까? 라이브러리를 사용해야 할 때마다 앱에서 토큰을 전달하거나 더 중앙 어딘가에 안전하게 보관할 수 있지만 어쨌든 토큰이 필요합니다. 래퍼 클래스는이 모든 것을 다시 간단하게 만듭니다. 왜냐하면 토큰을 래퍼 클래스 내부에 유지하고 앱의 어떤 구성 요소에도 토큰을 노출시키지 않고 필요를 완전히 추상화 할 수 있기 때문입니다. 좋은 API 디자인을 강조하지 않는 라이브러리를 사용하면 큰 이점이 있습니다.
단위 테스트가 훨씬 간단합니다
단위 테스트는 한 가지만 테스트해야합니다. 클래스를 단위 테스트하려면 종속성을 조롱해야합니다. 해당 클래스가 네트워크 호출을하거나 소프트웨어 외부의 다른 리소스에 액세스하는 경우 더욱 중요합니다. 타사 라이브러리를 래핑하면 해당 호출을 조롱하고 테스트 데이터 또는 단위 테스트에 필요한 모든 것을 쉽게 반환 할 수 있습니다. 이러한 추상화 계층이 없으면이 작업을 수행하기가 훨씬 어려워집니다. 대부분의 경우 이로 인해 많은 추악한 코드가 생성됩니다.
느슨하게 연결된 시스템을 만듭니다
최소한 래퍼의 동작을 변경하지 않는 한 래퍼를 변경해도 소프트웨어의 다른 부분에는 영향을 미치지 않습니다. 이 래퍼와 같은 추상화 계층을 도입하면 라이브러리 호출을 단순화하고 해당 라이브러리에서 앱의 종속성을 거의 완전히 제거 할 수 있습니다. 소프트웨어는 래퍼 만 사용하므로 래퍼의 구현 방법이나 작동 방식에 차이가 없습니다.
실제 예
솔직 해지자 사람들은 몇 시간 동안 이와 같은 장점과 단점에 대해 논쟁 할 수 있습니다. 그래서 나는 단지 당신에게 예를 보여줍니다.
일종의 Android 앱이 있고 이미지를 다운로드해야한다고 가정 해 봅시다. Picasso 또는 Universal Image Loader 와 같이 이미지를로드하고 캐싱하는 바람이 많은 라이브러리가 있습니다 .
이제 우리가 사용하는 라이브러리를 래핑하는 데 사용할 인터페이스를 정의 할 수 있습니다.
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가 현재 우리에게 절대적으로 필요한 기능을 지원하지 않기 때문에 Picasso를 Universal Image Loader로 교체해야한다고 상상해보십시오. 이제 코드베이스를 훑어보고 Picasso에 대한 모든 호출을 정중하게 바꾼 다음 몇 개의 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;
}
}
타사 라이브러리를 래핑하여 요약하면 코드베이스가 변경에보다 유연 해지고, 전체적으로 간단하고, 테스트하기가 쉬워지고, 소프트웨어의 여러 구성 요소의 결합을 줄일 수 있습니다.