Java | オブジェクト指向:オブジェクト指向とは何か

Java Java
スポンサーリンク

オブジェクト指向の全体像

オブジェクト指向は「もの(オブジェクト)に状態と振る舞いを持たせ、役割ごとに分けて協調させる」考え方です。ものの設計図がクラスで、実体がオブジェクト。複雑さは“責務の分割”で小さくし、拡張は“型を増やす”ことで安全に実現します。Java はこの設計に最適化されていて、壊れにくさ(整合性)と変化への強さ(拡張容易性)を両立できます。


4つの柱(重要ポイントの深掘り)

カプセル化(中身を守り、安全な窓口だけ公開)

フィールドを private にし、検証付きメソッド経由でだけ状態を変えます。整合性が崩れない“安全な通路”を作るのが要点です。

public final class BankAccount {
    private int balance;

    public BankAccount(int initial) {
        if (initial < 0) throw new IllegalArgumentException("negative");
        balance = initial;
    }
    public int balance() { return balance; }
    public void deposit(int amount) {
        if (amount <= 0) throw new IllegalArgumentException("amount>0");
        balance += amount;
    }
    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        return true;
    }
}
Java

「直接代入禁止」「検証つき窓口」の二点で、壊れないオブジェクトに育てます。

抽象化(本質だけを約束して、実装は隠す)

“できること”をインターフェースで約束し、具体手段は実装に任せます。差し替えとテストが容易になります。

public interface PaymentGateway { boolean charge(int amount, String token); }

public final class StripeGateway implements PaymentGateway {
    @Override public boolean charge(int amount, String token) { /* 実装 */ return true; }
}
public final class FakeGateway implements PaymentGateway {
    @Override public boolean charge(int amount, String token) { return amount < 10_000; }
}
Java

呼び手は「課金できるか」だけに集中し、裏側の手段に縛られません。

継承(共通を上にまとめ、差分だけ持つ)

共通の性質・振る舞いが自然に一致するなら親子でまとめます。ただし“安易な共通化”は複雑化の元。まずは妥当性を見極めます。

public abstract class Shape { public abstract double area(); }
public final class Circle extends Shape {
    private final double r; public Circle(double r) { this.r = r; }
    @Override public double area() { return Math.PI * r * r; }
}
public final class Rect extends Shape {
    private final double w, h; public Rect(double w, double h) { this.w = w; this.h = h; }
    @Override public double area() { return w * h; }
}
Java

「同じメソッドを、型ごとに適切に実装できる」場面に絞って使います。

多態性(同じ呼び出しが、型ごとに正しく切り替わる)

呼び手は area() を呼ぶだけ。どの型でも“その型らしい”結果が返ります。分岐の嵐を型へ追い出し、拡張は“型を増やす”で完結させます。

Shape s = new Circle(10);
System.out.println(s.area()); // Circle版
s = new Rect(3, 4);
System.out.println(s.area()); // Rect版
Java

クラスとオブジェクトの基本(例題で理解する)

設計図(クラス)と実体(オブジェクト)

クラスに「整合性ルール」を封じ、オブジェクトはルールに守られて動きます。

public final class User {
    private final String id;
    private String name;

    public User(String id, String name) {
        if (id == null || id.isBlank()) throw new IllegalArgumentException("id");
        this.id = id;
        this.name = name == null ? "" : name.trim();
    }
    public String id() { return id; }
    public String name() { return name; }
    public void rename(String newName) {
        if (newName == null || newName.isBlank()) return;
        name = newName.trim();
    }
}

User u = new User("U-1", " Taro ");
u.rename("Taro Yamada");
System.out.println(u.name()); // 整形済み
Java

協調(役割ごとの小さなオブジェクトが連携)

責務を分けるほど読みやすく、変更に強くなります。

public record Item(String name, int price) {}

public final class Cart {
    private final java.util.List<Item> items = new java.util.ArrayList<>();
    public void add(Item it) { if (it != null) items.add(it); }
    public int total() { return items.stream().mapToInt(Item::price).sum(); }
}

var cart = new Cart();
cart.add(new Item("Pen", 120));
cart.add(new Item("Note", 350));
System.out.println(cart.total()); // 470
Java

設計の型と実践のコツ

責務を一つに絞る(単一責務)

クラスは「一つの理由でだけ変わる」ように設計。入力検証・計算・I/O を混在させないことで、テストが容易になります。

不変を好む(変更は新インスタンス)

副作用を減らし、並行性やテストに強くします。

public record Money(int amount, String currency) {
    public Money add(int delta) { return new Money(amount + delta, currency); }
}
Java

委譲で柔軟に(継承に頼りすぎない)

インターフェースで契約を表し、実装は注入して差し替えられるように。

public interface Discount { int apply(int subtotal); }
public final class RateDiscount implements Discount {
    private final double rate; public RateDiscount(double rate) { this.rate = rate; }
    @Override public int apply(int subtotal) { return (int) Math.round(subtotal * (1 - rate)); }
}
public final class OrderService {
    private final Discount discount; public OrderService(Discount discount) { this.discount = discount; }
    public int total(int subtotal) { return discount.apply(subtotal); }
}
Java

例題で体験するオブジェクト指向

例 1: 図形の面積(多態性で拡張に強く)

public interface Shape2 { double area(); }
public record Circle2(double r) implements Shape2 { public double area() { return Math.PI * r * r; } }
public record Rect2(double w, double h) implements Shape2 { public double area() { return w * h; } }

public final class ShapePrinter {
    public static void print(Shape2 s) { System.out.printf("area=%.2f%n", s.area()); }
}

ShapePrinter.print(new Circle2(10));
ShapePrinter.print(new Rect2(3, 4));
Java

新しい図形を足しても ShapePrinter は変更不要。分岐を型に追い出した成果です。

例 2: 口座と取引(カプセル化+型で安全化)

public final class Account {
    private int balance;
    public Account(int initial) { if (initial < 0) throw new IllegalArgumentException(); balance = initial; }
    public int balance() { return balance; }
    public void apply(Transaction tx) { balance = tx.applyTo(balance); }
}
public sealed interface Transaction permits DepositTx, WithdrawTx { int applyTo(int balance); }
public record DepositTx(int amount) implements Transaction {
    public int applyTo(int balance) {
        if (amount <= 0) throw new IllegalArgumentException(); return balance + amount;
    }
}
public record WithdrawTx(int amount) implements Transaction {
    public int applyTo(int balance) {
        if (amount <= 0 || amount > balance) throw new IllegalArgumentException(); return balance - amount;
    }
}

var acc = new Account(1000);
acc.apply(new DepositTx(300));
acc.apply(new WithdrawTx(500));
System.out.println(acc.balance()); // 800
Java

「取引の整合性」を型に閉じ込め、Account は適用だけに集中できます。


仕上げのアドバイス(重要部分のまとめ)

オブジェクト指向は「ものの性質と振る舞いを責務で分け、協調させる」設計術。カプセル化で整合性を守り、抽象化で契約を明確にし、継承は慎重に、ポリモーフィズムで分岐を型へ追い出す。クラスは一責務に絞り、不変を好み、委譲で柔軟に差し替える——この型が身につくと、読みやすく変更に強いコードに必ず近づきます。

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