
在java ddd项目中,当实体需要根据特定业务场景拥有条件属性时,如何设计以保证类型安全和可扩展性是一个常见挑战。本文探讨了两种方案:通过枚举控制属性访问,以及利用继承和泛型实现类型分离。我们将分析枚举方案违反开闭原则的弊端,并推荐使用继承结合泛型来构建清晰、可维护且符合solid原则的领域模型。
在领域驱动设计(DDD)中,实体(Entity)是业务核心概念的载体。然而,在复杂的业务场景下,同一个实体可能在不同的用例(Use Case)中表现出不同的行为或拥有不同的属性。例如,一个Token实体在大多数API中可能只需要包含基本的标识信息,但在某个特定API中,它需要额外携带Locales(本地化信息)属性。
此时,我们面临一个设计挑战:如何在不污染通用Token实体接口的前提下,为特定场景引入Locales属性,并确保只有需要该属性的用例才能访问它?这既要考虑代码的清晰性、可维护性,也要遵循良好的面向对象设计原则。
设计思路:
这种方案的核心是在基础Token实体中直接添加Locales属性,并引入一个枚举类型(例如TokenType),用于标识Token的具体类型。Locales的访问器(getter)会根据TokenType的值来决定是返回实际的本地化信息,还是返回一个空的Optional对象,甚至抛出异常。
立即学习“Java免费学习笔记(深入)”;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
public enum TokenType {
STANDARD,
LOCALIZED
}
public class Token {
private String id;
private TokenType type;
private List<Locale> locales; // 即使是STANDARD类型,也可能存在此字段
public Token(String id, TokenType type, List<Locale> locales) {
this.id = id;
this.type = type;
this.locales = locales;
}
public String getId() {
return id;
}
public TokenType getType() {
return type;
}
/**
* 根据Token类型返回本地化信息。
* 仅当type为LOCALIZED时返回实际值,否则返回Optional.empty()。
*/
public Optional<List<Locale>> getLocales() {
if (this.type == TokenType.LOCALIZED) {
return Optional.ofNullable(locales);
}
return Optional.empty();
// 另一种处理方式是抛出 UnsupportedOperationException
}
// ... 其他通用属性和方法
}优点(表面上):
缺点与风险:
设计思路:
此方案遵循面向对象的多态性原则,通过继承来扩展实体功能,并结合泛型在用例层实现类型安全。基础Token类包含所有通用属性,而特定类型的Token(如LocalizedToken)则继承自Token并添加其特有的属性和行为。用例层则通过泛型限制其操作的Token类型。
import java.util.List;
import java.util.Locale;
// 基础Token实体,包含所有通用属性
public class Token {
private String id;
public Token(String id) {
this.id = id;
}
public String getId() {
return id;
}
// ... 其他通用属性和方法
}
// 扩展的LocalizedToken实体,包含特有的locales属性
public class LocalizedToken extends Token {
private List<Locale> locales;
public LocalizedToken(String id, List<Locale> locales) {
super(id);
this.locales = locales;
}
public List<Locale> getLocales() {
return locales;
}
}
// 示例:使用泛型在用例层操作不同类型的Token
public class CreateTokensUseCase<T extends Token> {
// 假设有一个通用的TokenRepository
private TokenRepository tokenRepository;
public CreateTokensUseCase(TokenRepository tokenRepository) {
this.tokenRepository = tokenRepository;
}
public T execute(T token) {
// 在这里可以进行一些通用的Token创建逻辑
// 如果需要访问特定属性,可以通过类型检查进行向下转型
if (token instanceof LocalizedToken localizedToken) {
System.out.println("Creating localized token with locales: " + localizedToken.getLocales());
} else {
System.out.println("Creating standard token.");
}
return tokenRepository.save(token); // 假设save方法也是泛型的
}
}
// 示例:仓储接口设计
interface TokenRepository {
<T extends Token> T save(T token);
Optional<Token> findById(String id); // 返回基础Token
Optional<LocalizedToken> findLocalizedTokenById(String id); // 特定类型查询
}
// 示例:服务层调用
public class TokenApplicationService {
private final CreateTokensUseCase<Token> standardTokenCreator;
private final CreateTokensUseCase<LocalizedToken> localizedTokenCreator;
public TokenApplicationService(TokenRepository tokenRepository) {
this.standardTokenCreator = new CreateTokensUseCase<>(tokenRepository);
this.localizedTokenCreator = new CreateTokensUseCase<>(tokenRepository);
}
public Token createStandardToken(String id) {
return standardTokenCreator.execute(new Token(id));
}
public LocalizedToken createLocalizedToken(String id, List<Locale> locales) {
return localizedTokenCreator.execute(new LocalizedToken(id, locales));
}
}优点:
缺点:
在上述两种方案中,基于继承和泛型实现类型分离的方案,尽管初期投入更大,但从长远来看,它提供了更高的可维护性、可扩展性和类型安全性,严格遵循了SOLID原则中的开闭原则。
推荐策略:
注意事项:
面对Java DDD项目中实体条件属性的设计挑战,虽然基于枚举的方案在初期看起来简单,但其违反开闭原则的本质会导致后期维护成本急剧增加。相比之下,采用继承结合泛型的方法,虽然初期需要更多的设计和重构,但它能构建出更健壮、更具扩展性且类型安全的领域模型,是符合SOLID原则的最佳实践。这种设计哲学鼓励我们拥抱变化,通过扩展而非修改来适应新的业务需求。
以上就是Java领域模型中条件属性的最佳实践:避免枚举陷阱的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号