本文へ移動
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(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Java OOP & SOLID Design

Overview

Java is chosen precisely when the domain is rich (see java-vs-go-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.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

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

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1122026年10月10日 更新

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/tasktrooper1122026年10月10日 更新

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

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1122026年10月10日 更新

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/tasktrooper1122026年10月10日 更新

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/tasktrooper1122026年10月10日 更新

Use when deciding whether a request needs an analiz task before implementation - the conditions that require the architect's analysis versus going straight to implementation

日本語の概要は準備中です。原文の説明を表示しています。

makifbaysal/tasktrooper1122026年10月10日 更新

makifbaysal のスキルをすべて見る

このスキルの問題を報告する