继承(is-a) 组合(has-a)
───────────── ─────────────
JavaSubmission Submission
is a has a CodeSnippet
Submission + has a UserRef
+ has a JudgeResult
多态:JudgeEngine 接口
/ \
JavaJudge PythonJudge
同一个 judge() 不同行为
跟着做
步骤 1:区分继承与组合
// 继承:只有当"子类完全能替代父类"时才用
public abstract class CfUser {
private final Long id;
private final String nickname;
private final LocalDateTime registerAt;
public abstract boolean canAccess(CfLesson lesson);
}
public class FreeUser extends CfUser {
@Override
public boolean canAccess(CfLesson lesson) {
return lesson.isFree();
}
}
public class VipUser extends CfUser {
private final LocalDateTime vipExpireAt;
@Override
public boolean canAccess(CfLesson lesson) {
if (vipExpireAt.isBefore(LocalDateTime.now())) return false;
return true; // VIP 可看全部
}
}
// 组合:Submission 有多个部分,不是"是一种"的关系
public record SubmissionMeta(long userId, long lessonId, LocalDateTime submittedAt) {}
public class Submission {
private final Long id;
private final SubmissionMeta meta;
private CodeSnippet code;
private JudgeResult result;
private SubmissionStatus status;
public void assignResult(JudgeResult result) {
this.result = result;
this.status = result.isAccepted() ? SubmissionStatus.ACCEPTED : SubmissionStatus.REJECTED;
}
}
步骤 2:用 sealed 限制继承
Java 17 的 sealed 让继承白名单化:
public abstract sealed class Lesson
permits FreeLesson, VipLesson, TeamLesson {
private final Long id;
private final String title;
public abstract boolean isAccessibleBy(CfUser user);
}
public final class FreeLesson extends Lesson {
@Override public boolean isAccessibleBy(CfUser user) { return true; }
}
public final class VipLesson extends Lesson {
@Override public boolean isAccessibleBy(CfUser user) { return user.isVip(); }
}
public final class TeamLesson extends Lesson {
@Override public boolean isAccessibleBy(CfUser user) { return user.inSameTeam(this.teamId()); }
}
编译器确保只有这三类 Lesson,别处不能随意 extends Lesson,继承树永远可控。
步骤 3:多态实战——Validator 链
public interface SubmissionValidator {
ValidationResult validate(Submission submission);
}
@Component
public class SyntaxValidator implements SubmissionValidator {
@Override
public ValidationResult validate(Submission s) {
return s.getCode().isBlank() ? ValidationResult.fail("代码不能为空") : ValidationResult.ok();
}
}
@Component
public class LengthValidator implements SubmissionValidator {
@Override
public ValidationResult validate(Submission s) {
return s.getCode().length() > 100_000
? ValidationResult.fail("代码过长")
: ValidationResult.ok();
}
}
@Service
public class SubmissionService {
private final List<SubmissionValidator> validators;
public SubmissionService(List<SubmissionValidator> validators) {
this.validators = validators;
}
public void submit(Submission submission) {
for (SubmissionValidator v : validators) {
ValidationResult r = v.validate(submission);
if (!r.ok()) throw new IllegalArgumentException(r.reason());
}
// ... 入库
}
}
Spring 自动注入所有 SubmissionValidator 实现,加新校验规则 = 新建一个 @Component,不改动 SubmissionService。
步骤 4:record 做值对象
判题结果是不变的值:
public record JudgeResult(boolean accepted, long timeMs, long memoryKb, String output) {
public boolean isAccepted() { return accepted; }
}
record 自动生成 equals、hashCode、toString、不可变字段,天然适合值对象。