Java | オブジェクト指向:集約と関連

Java Java
スポンサーリンク

集約と関連とは何か

オブジェクト指向で「集約」と「関連」は、クラスとクラスの“つながり方”を表す言葉です。
ざっくり言うと、

  • 関連:ただの「知り合い」関係(A が B を知っている・参照している)
  • 集約:あるオブジェクトが「まとまりの単位」として他のオブジェクトを束ねる関係(全体と部分)

どちらも「あるクラスが別のクラスをフィールドに持つ」形で表現されますが、
どこまでを「ひとまとまり」と見なすか、ライフサイクルをどう扱うか、という設計の意味が変わってきます。


関連(Association)とは

「A は B を知っている」くらいのゆるい関係

関連は一番広い概念で、「あるクラスが別のクラスへの参照を持っている」程度の関係です。
お互いのライフサイクルは独立していて、「片方が消えたらもう片方も消える」ような強い意味はありません。

final class User {
    private final String name;
    private final Address address;  // User は Address を“知っている”

    User(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    String name() { return name; }
    Address address() { return address; }
}

final class Address {
    private final String city;

    Address(String city) {
        this.city = city;
    }

    String city() { return city; }
}
Java

User は Address を参照していますが、
Address 自体は Address だけで単独のオブジェクトとして意味を持ちます。
User がいなくなっても、Address が他の誰かに使われ続けることもありえます。

ここでは「User と Address は関連している」と言えますが、
User にとって Address は“必ずしも構造上の一部(パーツ)”とは限りません。


集約(Aggregation)とは

「ひとまとまり」を表す全体と部分の関係

集約は「全体(Aggregate Root)と、その配下にある部分オブジェクトのまとまり」を表す関係です。
単なる“知り合い”ではなく、「これらはひとつの一貫した塊として扱うべき」という意味が付きます。

代表的な例として「注文(Order)と注文明細(OrderLine)」があります。

import java.util.List;

final class Order {

    private final List<OrderLine> lines;  // Order は OrderLine を「自分の一部」として持つ

    Order(List<OrderLine> lines) {
        this.lines = List.copyOf(lines);  // 不変コピーで一貫性を守る
    }

    int totalPrice() {
        return lines.stream().mapToInt(OrderLine::linePrice).sum();
    }

    List<OrderLine> lines() {
        return lines;
    }
}

final class OrderLine {

    private final String itemName;
    private final int unitPrice;
    private final int quantity;

    OrderLine(String itemName, int unitPrice, int quantity) {
        this.itemName = itemName;
        this.unitPrice = unitPrice;
        this.quantity = quantity;
    }

    int linePrice() {
        return unitPrice * quantity;
    }
}
Java

ここでのポイントは、

  • Order と OrderLine をセットで扱う(Order の合計金額には、全ての OrderLine が関わる)
  • Order の外側から、勝手に OrderLine をいじらせないほうが、安全(一貫性を守れる)

といった「まとまりとしての意味」があることです。


集約とライフサイクル(重要な深掘り)

集約の単位で「整合性を守る」

集約の一番大事な役割は
「このグループの中は、常に整合した状態を保ちたい」という境界を作ることです。

Order を例にすると、

  • Order の合計金額
  • OrderLine の数
  • 各 OrderLine の数量や単価

などは、バラバラに変更されるとおかしくなる可能性があります。

そこで、

  • Order は自分の配下にある OrderLine を「自分の管理下の一部」として扱う
  • Order のメソッド経由でしか OrderLine を追加・削除・変更できないようにする

といった形で、「一貫性を守る責任」を Order に集中させます。

final class Order {

    private final List<OrderLine> lines = new java.util.ArrayList<>();

    void addLine(String itemName, int unitPrice, int quantity) {
        lines.add(new OrderLine(itemName, unitPrice, quantity));
    }

    int totalPrice() {
        return lines.stream().mapToInt(OrderLine::linePrice).sum();
    }
}
Java

Order の外側から勝手に List<OrderLine> をいじらせないようにすると、
Order が「集約のルート(代表)」として、状態の整合性を守りやすくなります。


関連として扱うか、集約として扱うかの判断

「本当に“構造上の一部”か?」を自分に問いかける

次のような視点で考えると、集約か単なる関連かが判断しやすくなります。

  • 片方のオブジェクトが中心で、もう片方は「その一部」としてしか意味を持たないか
  • 「このまとまりの中の整合性」を、誰か1つのクラスに責任集中させたいか
  • 保存や再構築の単位も“そのまとまり”で考えたいか

例えば「ユーザとパスワード履歴」なら、多くの場合「ユーザの集約」に含まれます。
一方、「ユーザと所属部門」なら、どちらもそれぞれ独立した意味を持つので、単なる関連として扱うことも多いです。

final class Department {
    private final String name;
    Department(String name){ this.name = name; }
}

final class Employee {
    private final String name;
    private final Department department;   // 関連(Employee は Department を参照)
    Employee(String name, Department dept){
        this.name = name;
        this.department = dept;
    }
}
Java

Department は Employee の「構造上の一部」というよりは、
独立して存在しうる“別の概念”ですよね。


実装レベルではどちらも「フィールド参照」

コード上は同じに見えるからこそ“意味づけ”が大事

ここが初心者に一番ややこしいところですが、
Java のコードだけ見ると、「集約」も「関連」も単に「フィールドに持っているだけ」に見えます。

final class A {
    private B b;  // これが集約なのか関連なのかは、コードだけでは断定できない
}
Java

違いは「設計上の意味づけ」「責任の置き方」です。

  • 集約なら:「A が B をまとめて管理し、一貫性を守る責任を持つ」
  • 単なる関連なら:「A は B を知っているけど、B のライフサイクルや整合性は別」

というつもりで設計します。

その意図をコードに反映するには、

  • B のコレクションを外部にそのまま返さない(コピーを返す)
  • B をいじるメソッドを A に用意し、外から直接 B を変更させない
  • B 自体を new するのは A 内に限定し、外で勝手に作らせない

などのルールを徹底します。


例題:集約を意識した設計と関連だけの設計

集約を意識した「ショッピングカート」

final class Cart {
    private final java.util.List<CartItem> items = new java.util.ArrayList<>();

    void add(String productId, int unitPrice, int quantity) {
        items.add(new CartItem(productId, unitPrice, quantity));
    }

    int totalPrice() {
        return items.stream().mapToInt(CartItem::linePrice).sum();
    }

    java.util.List<CartItem> itemsView() {
        return java.util.List.copyOf(items);   // 防御的コピー
    }
}

final class CartItem {
    private final String productId;
    private final int unitPrice;
    private final int quantity;

    CartItem(String productId, int unitPrice, int quantity) {
        this.productId = productId;
        this.unitPrice = unitPrice;
        this.quantity = quantity;
    }

    int linePrice() { return unitPrice * quantity; }
}
Java

ここでは Cart が集約のルートです。
CartItem をどう追加・削除するか、合計金額をどう計算するかは Cart の責任です。

外部には itemsView() で「読み取り専用ビュー」だけを渡し、
中身のリストを直接いじれないようにしているところに「集約としての意図」が現れています。

単なる関連としての「ユーザと会社」

final class Company {
    private final String name;
    Company(String name) { this.name = name; }
}

final class User {
    private final String name;
    private final Company company;  // ユーザは自分の会社を知っている(関連)

    User(String name, Company company) {
        this.name = name;
        this.company = company;
    }
}
Java

ここでは Company は User の一部というより、
「User がどの会社に属しているか」という“関係”を表しているだけです。
Company 自体は Company として単独で意味を持ちます。


まとめ:集約と関連を設計でどう使い分けるか(重要)

関連は「A は B を知っている」という、ゆるい関係です。
集約は「A が B をまとめて管理し、一貫性を守る責任を持つ全体と部分の関係」です。

実装レベルではどちらも「フィールド参照」で表現されますが、
設計上は次の点が大きな違いになります。

  • 整合性とルールを、どのクラスに集中させるか
  • 保存や更新の単位(トランザクションの単位)をどこで切るか
  • ライフサイクル(作る・変更する・消す)をどの塊で考えるか

タイトルとURLをコピーしました