OOP继承误用问题:如何优化ResourceProvider的设计?
针对ResourceProvider的替代设计策略
方案1:构造函数注入基础参数(优先推荐)
把每个ResourceProvider的差异化参数(比如S3的bucket、URL的baseUrl)放到构造函数里初始化,接口方法仅保留统一需要的DocumentId。这种方式适合参数相对固定(比如每个Provider实例对应一个bucket)的场景,既能保持接口统一,又能满足子类的参数需求。
// S3ResourceProvider实现 public class S3ResourceProvider implements ResourceProvider { private final String bucket; private final String keyPrefix; // 初始化时传入固定参数 public S3ResourceProvider(String bucket, String keyPrefix) { this.bucket = bucket; this.keyPrefix = keyPrefix; } @Override public ResourceLocation generatePushResourceLocation(DocumentId documentId) { String fullKey = keyPrefix + documentId.getValue(); return new ResourceLocation("s3://" + bucket + "/" + fullKey); } @Override public ResourceLocation generatePullResourceLocation(DocumentId documentId) { // 可根据业务生成带签名或直接可访问的路径 return new ResourceLocation("s3://" + bucket + "/" + keyPrefix + documentId.getValue()); } } // URLResourceProvider实现 public class URLResourceProvider implements ResourceProvider { private final String baseUrl; public URLResourceProvider(String baseUrl) { this.baseUrl = baseUrl; } @Override public ResourceLocation generatePushResourceLocation(DocumentId documentId) { return new ResourceLocation(baseUrl + "/upload/" + documentId.getValue()); } @Override public ResourceLocation generatePullResourceLocation(DocumentId documentId) { return new ResourceLocation(baseUrl + "/download/" + documentId.getValue()); } }
业务层调用时,可通过工厂模式获取已配置好的Provider实例,统一调用接口方法:
// 工厂类封装实例创建逻辑 public class ResourceProviderFactory { public static ResourceProvider getS3Provider() { // 可从配置中心读取bucket等参数 return new S3ResourceProvider("user-data-bucket", "docs/"); } public static ResourceProvider getURLProvider() { return new URLResourceProvider("https://cdn.example.com/resources"); } } // 业务代码统一调用 ResourceProvider provider = ResourceProviderFactory.getS3Provider(); ResourceLocation pushLoc = provider.generatePushResourceLocation(docId);
方案2:泛型参数类封装动态参数
如果需要每次调用方法时动态传入差异化参数(比如每次用不同的bucket),可以定义泛型接口+参数子类,避免强制类型转换,同时保持接口的统一性。
// 基础参数抽象类 public abstract class ProviderParams {} // S3专用参数 public class S3Params extends ProviderParams { private String bucket; private String customKey; // Getter & Setter public String getBucket() { return bucket; } public void setBucket(String bucket) { this.bucket = bucket; } public String getCustomKey() { return customKey; } public void setCustomKey(String customKey) { this.customKey = customKey; } } // URL专用参数 public class URLParams extends ProviderParams { private String resourceUrl; // Getter & Setter public String getResourceUrl() { return resourceUrl; } public void setResourceUrl(String resourceUrl) { this.resourceUrl = resourceUrl; } } // 泛型化的ResourceProvider接口 public interface ResourceProvider<T extends ProviderParams> { ResourceLocation generatePushResourceLocation(DocumentId documentId, T params); ResourceLocation generatePullResourceLocation(DocumentId documentId, T params); } // S3实现类 public class S3ResourceProvider implements ResourceProvider<S3Params> { @Override public ResourceLocation generatePushResourceLocation(DocumentId documentId, S3Params params) { String fullKey = params.getCustomKey() + "-" + documentId.getValue(); return new ResourceLocation("s3://" + params.getBucket() + "/" + fullKey); } @Override public ResourceLocation generatePullResourceLocation(DocumentId documentId, S3Params params) { return new ResourceLocation("s3://" + params.getBucket() + "/" + params.getCustomKey() + "-" + documentId.getValue()); } }
这种方式的好处是编译期就能检查参数类型,避免运行时错误,适合参数动态变化的场景。
方案3:组合模式分离参数逻辑
如果差异化参数逻辑复杂,可以把参数处理逻辑抽成独立的组件,让ResourceProvider组合这些组件,而不是继承。比如为S3单独做一个S3ParamResolver,负责解析bucket、key等参数,Provider只负责生成资源路径。
// 参数解析器接口 public interface ParamResolver { Map<String, String> resolve(DocumentId documentId); } // S3参数解析器 public class S3ParamResolver implements ParamResolver { @Override public Map<String, String> resolve(DocumentId documentId) { Map<String, String> params = new HashMap<>(); params.put("bucket", "user-data-bucket"); params.put("key", "docs/" + documentId.getValue()); return params; } } // 组合了解析器的ResourceProvider实现 public class S3ResourceProvider implements ResourceProvider { private final ParamResolver resolver; public S3ResourceProvider(ParamResolver resolver) { this.resolver = resolver; } @Override public ResourceLocation generatePushResourceLocation(DocumentId documentId) { Map<String, String> params = resolver.resolve(documentId); return new ResourceLocation("s3://" + params.get("bucket") + "/" + params.get("key")); } // pull方法逻辑类似 }
这种方式让职责更清晰,参数解析和资源路径生成解耦,方便后续扩展新的参数逻辑。
内容的提问来源于stack exchange,提问作者Jordi
相关产品推荐
相关产品推荐

