
问题背景与挑战
在软件开发中,我们经常会遇到这样的场景:多个数据传输对象(dtos)或记录(records)在结构上存在高度相似性,它们可能共享大部分字段,但又因业务需求略有差异。例如,一个创建请求对象(createobjectrequest)和一个更新请求对象(updateobjectrequest),它们可能都包含 customobjecta 和 customobjectb 等字段,但 createobjectrequest 可能额外包含 customobjectc。当需要对这些对象执行相同的验证逻辑时,一种直观但低效的做法是为每种类型定义一个重载方法:
public record CreateObjectRequest (
CustomObjectA a,
CustomObjectB b,
CustomObjectC c
) {}
public record UpdateObjectRequest (
CustomObjectA a,
CustomObjectB b
) {}
// 冗余的验证方法
public void validateRequest(CreateObjectRequest createObjectRequest) {
// 包含大量相同的验证逻辑
// ...
}
public void validateRequest(UpdateObjectRequest updateObjectRequest) {
// 几乎完全相同的验证逻辑
// ...
}这种方法虽然功能上可行,但会导致大量的代码重复("long body"),降低了代码的可维护性。一旦验证逻辑需要修改,开发者必须在多个地方进行同步更新,极易引入错误。
核心解决方案:抽象父类与多态
为了解决上述代码冗余问题,我们可以利用Java的面向对象特性——抽象类和多态。核心思想是识别出所有相关对象(如 CreateObjectRequest 和 UpdateObjectRequest)的共同字段,并将这些共同字段抽象到一个父类中。然后,让所有具体请求类继承这个抽象父类。这样,验证方法就可以统一接收这个抽象父类作为参数,从而实现一套逻辑处理多种类型。
1. 定义抽象父类
首先,创建一个抽象父类,例如 ObjectRequest,它包含所有子类共有的字段。如果这些字段本身也是自定义对象,也应确保它们被正确定义。
// 假设 CustomObjectA, CustomObjectB, CustomObjectC 已定义
public class CustomObjectA {}
public class CustomObjectB {}
public class CustomObjectC {}
public abstract class ObjectRequest {
// 包含所有子类共有的字段
protected CustomObjectA a;
protected CustomObjectB b;
// 构造函数、getter/setter(如果需要)
public ObjectRequest(CustomObjectA a, CustomObjectB b) {
this.a = a;
this.b = b;
}
public CustomObjectA getA() { return a; }
public CustomObjectB getB() { return b; }
// ... 其他通用方法
}2. 具体请求类继承抽象父类
接下来,让 CreateObjectRequest 和 UpdateObjectRequest 不再是独立的记录,而是继承 ObjectRequest。它们可以根据自身特有的字段进行扩展。
立即学习“Java免费学习笔记(深入)”;
// CreateObjectRequest 继承 ObjectRequest,并添加特有字段
public class CreateObjectRequest extends ObjectRequest {
private CustomObjectC c;
public CreateObjectRequest(CustomObjectA a, CustomObjectB b, CustomObjectC c) {
super(a, b);
this.c = c;
}
public CustomObjectC getC() { return c; }
// ...
}
// UpdateObjectRequest 继承 ObjectRequest,如果它没有特有字段,可以保持简洁
public class UpdateObjectRequest extends ObjectRequest {
public UpdateObjectRequest(CustomObjectA a, CustomObjectB b) {
super(a, b);
}
// ...
}注意:Java Records 默认是 final 的,不能被继承。如果需要使用继承,则不能使用 record 关键字,而应使用传统的 class。上述示例已调整为 class。
3. 统一验证方法
现在,验证方法只需要接受抽象父类 ObjectRequest 作为参数。由于多态性,无论传入的是 CreateObjectRequest 还是 UpdateObjectRequest 的实例,都可以被这个方法处理。
public class RequestValidator {
public void validateRequest(ObjectRequest objectRequest) {
// 在这里实现通用的验证逻辑
if (objectRequest.getA() == null) {
throw new IllegalArgumentException("CustomObjectA cannot be null.");
}
if (objectRequest.getB() == null) {
throw new IllegalArgumentException("CustomObjectB cannot be null.");
}
// ... 其他针对通用字段的验证
// 如果需要处理特定子类的验证逻辑,可以通过 instanceof 进行判断,
// 但应尽量避免,优先考虑将差异化逻辑下沉到子类或使用策略模式。
if (objectRequest instanceof CreateObjectRequest createRequest) {
if (createRequest.getC() == null) {
throw new IllegalArgumentException("CustomObjectC is required for creation.");
}
// ... 针对 CreateObjectRequest 的特有验证
}
// else if (objectRequest instanceof UpdateObjectRequest updateRequest) { ... }
}
}优势与最佳实践
- 减少代码重复(DRY原则):这是最直接的优势。通用逻辑只需编写一次,极大地减少了代码量和维护成本。
- 提高可维护性:所有通用验证逻辑都集中在一个地方。当需求变更时,只需修改 validateRequest(ObjectRequest) 方法,而不是分散在多个重载方法中。
- 增强可扩展性:如果未来需要引入新的请求类型(如 DeleteObjectRequest),只要它继承 ObjectRequest 并包含共同字段,就可以直接复用现有的验证逻辑,无需修改 RequestValidator。
- 清晰的类型层级:通过继承,明确了不同请求对象之间的关系,提升了代码的可读性和设计感。
注意事项:
- 适用性判断:此方案最适用于那些确实共享大量公共属性和行为的类。如果类之间只有一两个字段相同,而大部分逻辑都不同,那么引入抽象父类可能反而会增加不必要的复杂性。
- 特有逻辑处理:如果子类除了通用验证外,还有自己独特的验证逻辑,可以在父类验证方法内部通过 instanceof 运算符进行类型判断,然后执行特定逻辑。然而,过度使用 instanceof 可能表明设计上仍有改进空间,可以考虑将差异化逻辑推迟到子类内部(如在子类中覆盖一个 validateSpecifics() 方法)或使用策略模式。
- 接口与泛型:在某些情况下,接口(定义行为契约)或泛型(提供类型参数化)也可能是替代方案。但对于本例中关注“共享字段”并进行“统一处理”的场景,抽象父类往往是最直接和有效的解决方案。
总结
通过引入抽象父类并利用Java的多态机制,我们可以优雅地解决不同参数类型但逻辑相同的通用方法所导致的冗余问题。这种方法不仅减少了代码重复,提高了可维护性和可扩展性,也促使我们遵循更好的面向对象设计原则。在设计系统时,识别并抽象出对象间的共同特性是构建健壮、灵活代码的关键一步。










