跳到正文
cccskills
免费在 GitHub 公开

java-oop-solid-design

Use when modelling a rich domain in Java - SOLID principles, encapsulated entities that enforce their own invariants, and value objects over primitives

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md4.0 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Java OOP & SOLID Design

Overview

Java is chosen precisely when the domain is rich (see backend-language-decision). That value is only realized if you model it with real objects that protect their invariants — not anemic data bags with a pile of setters and logic scattered in services.

Core principle: Objects own their invariants. If a rule about an entity can be violated from outside the entity, the model is broken.

SOLID, concretely

  • S — Single Responsibility: one reason to change per class. A class that parses HTTP, applies business rules, and writes SQL is three classes.
  • O — Open/Closed: extend behavior via new types/strategies, not by editing a growing switch. New payment method → new PaymentMethod implementation, not another case.
  • L — Liskov: a subtype must honor the supertype's contract. If Square extends Rectangle breaks setWidth, the hierarchy is wrong — prefer composition.
  • I — Interface Segregation: many small role interfaces over one fat one. A caller that needs read shouldn't depend on write.
  • D — Dependency Inversion: services depend on interfaces (ports), not concrete adapters. Inject the interface via the constructor.

Encapsulation over anemic models

// ❌ anemic: invariant lives nowhere, anyone can break it
class Task {
    public String title;      // no bound enforced
    public Status status;
}
task.title = "x".repeat(500); // invalid state, no guard

// ✅ rich: the entity enforces its own rules
public final class Task {
    private final TaskId id;
    private String title;
    private Status status;

    private Task(TaskId id, String title) { this.id = id; this.title = title; }

    public static Task create(String title) {
        if (title == null || title.isBlank() || title.length() > 200)
            throw new InvalidTaskTitle(title);
        return new Task(TaskId.newId(), title);
    }

    public void rename(String title) { /* same guard, reused */ }
    public void complete() {
        if (status == Status.DONE) throw new IllegalTransition("already done");
        this.status = Status.DONE;
    }
    // getters, no public setters
}

Value objects over primitives

Wrap meaningful primitives: TaskId, Email, Money — not raw String/long/BigDecimal. A TaskId can't be accidentally passed where a UserId is expected, and validation lives in one place. Java record makes these cheap.

Prefer

  • record for immutable value objects and DTOs.
  • sealed interfaces + pattern matching for closed hierarchies (states, commands).
  • Immutability by default; expose behavior methods, not setters.
  • Throw domain exceptions (InvalidTaskTitle) mapped to HTTP status at the boundary — not raw IllegalArgumentException leaking out.

Prefer a sealed interface + exhaustive switch pattern matching (final since Java 21) for a closed set of states or commands over a type enum plus if/instanceof chains — the compiler then refuses to let a new case go unhandled.

Common Mistakes

  • Public setters that let callers build invalid state.
  • Business rules in the service that should be on the entity.
  • String/long everywhere instead of value objects.
  • A god-service with dozens of methods and no domain objects.
  • Lombok @Data/@EqualsAndHashCode on a JPA entity — it pulls in lazy associations (forcing a load, or crashing outside the session) and generates a mutable, settable id that breaks equals/hashCode once the entity is persisted. Write equals/hashCode on the id only, or skip Lombok on entities.

Red Flags

  • An entity with only getters/setters and no behavior.
  • The same validation copy-pasted in two services → it belongs on the entity/value object.
  • A switch over a type enum that grows every feature → missing polymorphism.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1172026年10月21日 更新

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1172026年10月21日 更新

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1172026年10月21日 更新

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1172026年10月21日 更新

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1172026年10月21日 更新

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1172026年10月21日 更新

查看 makifbaysal 的全部技能

报告此技能的问题