
本文介绍如何在 Java 中设计 Currency 父类及其子类(如 USD、GBP、YEN),复用通用逻辑(如 API 获取持仓),同时让各子类自主提供差异化字段(如 bankAccountId),实现类型安全、可遍历、无冗余的面向对象结构。
本文介绍如何在 java 中设计 `currency` 父类及其子类(如 `usd`、`gbp`、`yen`),复用通用逻辑(如 api 获取持仓),同时让各子类自主提供差异化字段(如 `bankaccountid`),实现类型安全、可遍历、无冗余的面向对象结构。
在面向对象设计中,当多个子类共享相同行为但依赖各自特有状态时,不应将具体字段(如 bankAccountId)上提至父类硬编码,也不应重复实现逻辑。正确的解法是:将共性逻辑封装在父类中,将差异点抽象为受保护的抽象方法或 final 方法委托——即采用「模板方法模式(Template Method Pattern)」。
✅ 正确设计方案:抽象父类 + 子类职责分离
首先,定义抽象基类 Currency,它包含通用的 getHoldings(String apiKey) 方法,但将账户标识等子类专属信息提取为抽象方法:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public abstract class Currency {
protected final String apiBaseurl = "https://api.example.com"; // 可配置为常量或注入
// ✅ 抽象方法:由每个子类提供其专属银行账户 ID
protected abstract String getBankAccountId();
// ✅ 共用逻辑:所有子类复用同一份 HTTP 调用与解析流程
public final float getHoldings(String apiKey) {
try {
HttpClient httpClient = HttpClient.newHttpClient();
URI uri = new URIBuilder(apiBaseurl)
.addParameter("module", "account")
.addParameter("action", "tokenbalance")
.addParameter("accountid", getBankAccountId()) // ← 关键:委托给子类实现
.addParameter("apikey", apiKey)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(uri)
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode rootNode = new ObjectMapper().readTree(response.body());
return (float) rootNode.path("result").asDouble(); // 更健壮的类型转换
} catch (Exception e) {
throw new RuntimeException("Failed to fetch holdings for " + this.getClass().getSimpleName(), e);
}
}
// ✅ 可选:统一定义其他共享属性/行为(如 symbol、name)
public abstract String getSymbol();
}? 注意:URIBuilder 并非 JDK 原生类(常见于 Apache HttpClient),若使用标准 java.net.URI,建议改用 java.net.http.HttpClient 配套的 URI.create() + 字符串拼接,或引入 URIBuilder 所在库(如 org.apache.http.client.utils.URIBuilder)。此处保留原风格以聚焦设计逻辑。
接着,子类仅需专注提供差异化字段,无需重写业务逻辑:
立即学习“Java免费学习笔记(深入)”;
public class USD extends Currency {
@Override
protected String getBankAccountId() {
return "usd_acct_789123";
}
@Override
public String getSymbol() {
return "USD";
}
}
public class GBP extends Currency {
@Override
protected String getBankAccountId() {
return "gbp_acct_456789";
}
@Override
public String getSymbol() {
return "GBP";
}
}
public class YEN extends Currency {
@Override
protected String getBankAccountId() {
return "yen_acct_135790";
}
@Override
public String getSymbol() {
return "JPY";
}
}✅ 客户端调用:多态遍历,零耦合
现在你可以像最初设想的那样,安全地遍历并统一调用:
Portfolio portfolio = new Portfolio();
String myApiKey = "duivbncvavsuivavcshinseo";
portfolio.setOwnedCurrencies(Arrays.asList(
new USD(),
new GBP(),
new YEN()
));
for (Currency currency : portfolio.getOwnedCurrencies()) {
System.out.printf("%s holdings: %.2f%n",
currency.getSymbol(),
currency.getHoldings(myApiKey)
);
}输出示例:
USD holdings: 1250.75 GBP holdings: 892.33 JPY holdings: 145600.00
⚠️ 关键注意事项与进阶建议
-
不要滥用泛型如 Currency
:本场景不涉及类型参数化容器或算法,强行泛型反而增加复杂度且无助于解决字段隔离问题。 - 字段粒度控制:子类中 bankAccountId 等字段应设为 private,仅通过受保护的 getter 向父类暴露,保障封装性。
- 异常处理必须显式:网络请求易失败,父类中 try-catch 并包装为运行时异常(如 RuntimeException)是合理选择,避免子类被迫处理检查异常。
- 可扩展性增强:若未来需支持不同 API 协议(如 REST vs GraphQL)、认证方式(Bearer Token vs Query Param),可将 getHoldings() 进一步拆分为 buildRequest() 和 parseResponse() 模板钩子方法。
- 线程安全提示:HttpClient 是线程安全的,建议在应用层单例复用,而非每次新建;ObjectMapper 同理,应作为静态 final 成员复用。
✅ 总结
真正“可复用”的不是字段,而是逻辑流程;真正“可定制”的不是方法体,而是数据源契约。通过 abstract 方法定义子类必须提供的契约,配合 final 封装通用流程,即可在保持多态调用能力的同时,彻底消除代码重复。这是 Java 继承设计中最经典、最稳健的实践范式之一。








