如何抽象仅表名存在差异的Retrofit服务接口?
Great question! Since Retrofit doesn’t let you pass dynamic values directly into the @GET/@POST annotations, we can use a combination of generic interfaces and Java default methods to abstract away duplicate code while keeping your original service interface signatures intact. Here’s how to do it:
Step 1: Create a Generic Base Interface
First, define a base interface that uses Retrofit’s @Path annotation to handle the variable resource name (like "reminder" or "file"). This interface will hold the core API methods with the dynamic path segment:
import io.reactivex.Observable; import retrofit2.Response; import retrofit2.http.GET; import retrofit2.http.HeaderMap; import retrofit2.http.POST; import retrofit2.http.Path; interface GenericResourceService { // Dynamic path segment: {resource} gets replaced with "reminder" or "file" @GET("{resource}/list") Observable<Response<Reminder>> getList( @Path("resource") String resourceName, @HeaderMap Map<String, String> headerMap ); @POST("{resource}/ins") Observable<Response<Reminder>> ins( @Path("resource") String resourceName, @HeaderMap Map<String, String> headerMap ); }
Step 2: Extend the Base Interface for Specific Services
Now, create your original service interfaces by extending the generic base. Use Java default methods to hardcode the resource name for each service—this keeps the original method signature exactly the same, so existing callers don’t need any changes:
// ReminderService with fixed "reminder" resource interface ReminderService extends GenericResourceService { @Override default Observable<Response<Reminder>> getList(@HeaderMap Map<String, String> headerMap) { return super.getList("reminder", headerMap); } @Override default Observable<Response<Reminder>> ins(@HeaderMap Map<String, String> headerMap) { return super.ins("reminder", headerMap); } } // FileService with fixed "file" resource interface FileService extends GenericResourceService { @Override default Observable<Response<Reminder>> getList(@HeaderMap Map<String, String> headerMap) { return super.getList("file", headerMap); } @Override default Observable<Response<Reminder>> ins(@HeaderMap Map<String, String> headerMap) { return super.ins("file", headerMap); } }
Why This Works
- Retrofit’s
@Path: This annotation safely replaces the{resource}placeholder in the URL with the specified string, resulting in endpoints likereminder/listorfile/ins. - Default Methods: These let us override the base interface’s methods to inject the specific resource name, while preserving the method signature your code already relies on.
- Code Reusability: All shared logic lives in the base interface—if you need to add a new method (like
delete), you only add it once, and all child services inherit it automatically.
Alternative: Factory-Based Approach
If you prefer not to use default methods, you can create a factory class that generates preconfigured instances of the generic interface:
class ServiceFactory { private final Retrofit retrofit; public ServiceFactory(Retrofit retrofit) { this.retrofit = retrofit; } public GenericResourceService createReminderService() { return createService("reminder"); } public GenericResourceService createFileService() { return createService("file"); } private GenericResourceService createService(String resourceName) { return new GenericResourceService() { @Override public Observable<Response<Reminder>> getList(@HeaderMap Map<String, String> headerMap) { return retrofit.create(GenericResourceService.class).getList(resourceName, headerMap); } @Override public Observable<Response<Reminder>> ins(@HeaderMap Map<String, String> headerMap) { return retrofit.create(GenericResourceService.class).ins(resourceName, headerMap); } }; } }
This works too, but the default method pattern is cleaner and more aligned with Retrofit’s interface-first design.
内容的提问来源于stack exchange,提问作者Bisma Frühling

