オブジェクト指向

目次

概要

データと振る舞いを、変更単位としてまとめる

オブジェクト指向は、現実世界を無理にクラスへ写すための考え方ではありません。状態と振る舞いをまとめ、責務の境界を明確にし、変更の影響範囲を局所化するための設計技法です。

要点

オブジェクト指向の中心は継承ではなく、責務の分割と隠蔽です。どのデータをどの操作と一緒に置くかが本質です。

この章で重視すること

  • カプセル化を最優先で理解する
  • 継承よりも合成を基本に考える
  • オブジェクトは「役割の単位」であって「名詞の一覧」ではないと捉える

中心概念

  • カプセル化 内部表現を隠して外部契約を見せる
  • 抽象化 必要な操作だけを表に出す
  • 多態性 呼び出し側が具体実装に依存しすぎない

クラスとオブジェクト

クラスは、状態と操作の型を定義します。オブジェクトは、その型にもとづく実体です。大事なのは、オブジェクトがただのデータ置き場ではなく、自分の状態を守る責務を持つことです。

たとえば Accountbalance を公開し、外側から自由に増減できるなら、振る舞いは外へ漏れています。depositwithdraw のような操作を通して不変条件を守る方が、オブジェクトとして自然です。

class Account {
  private balance = 0;

  deposit(amount: number) {
    if (amount <= 0) throw new Error("amount must be positive");
    this.balance += amount;
  }
}

カプセル化

カプセル化はprivateを付けることだけではありません。外部が知るべき契約を小さくし、内部表現を変えられる余地を残すことです。

良いカプセル化では、次が成り立ちます。

  • 内部データ構造を変えても呼び出し側が壊れない
  • 不正な状態を外から作れない
  • 操作名が業務上の意味を持つ

責務駆動で考える

オブジェクト指向では、まずクラス名を名詞から拾うより、「誰が何を知っていて、誰が何を決めるべきか」を考える方が安定します。

悪い兆候:
  OrderServiceがOrderの内部状態を細かく読み、外で計算して戻す

よい方向:
  Order自身が注文として守るべき不変条件を持つ
  Serviceはユースケースの調整役に寄せる

責務が自然に置けると、getterの連鎖や巨大なservice classが減ります。

値オブジェクト

値オブジェクトは、同一性より値そのものに意味があるオブジェクトです。金額、メールアドレス、期間、座標などが典型です。

値オブジェクトにすると、プリミティブ型だけで表していた制約を閉じ込められます。

class EmailAddress {
  constructor(readonly value: string) {
    if (!value.includes("@")) throw new Error("invalid email");
  }
}

継承の使いどころ

継承は便利ですが、親子関係が固定されやすく、変更に弱くなることがあります。共通化のためにすぐ継承へ飛びつくより、委譲やinterfaceの方が柔軟なことが多いです。

合成

合成は、別のオブジェクトを内部に持って振る舞いを組み合わせる方法です。継承より依存を小さく保ちやすく、実行時に差し替えやすいという利点があります。

flowchart LR A["OrderService"] --> B["PaymentGateway"] A --> C["Inventory"] A --> D["Notifier"]

OrderService がすべてを継承階層で持つより、必要な協力者を明示的に持つ方が責務が見えやすくなります。

多態性の使いどころ

多態性は、if文をなくす魔法ではありません。変わりやすい分岐があり、呼び出し側が具体型を知らなくてよいときに効きます。

状況 多態性が効くか
支払い方法が増える 効きやすい
一時的な2分岐だけ 過剰になりやすい
外部サービスごとの差し替え 効きやすい
単純な値の分類 enumやmatchで十分なことが多い

継承でなくinterfaceと合成で表すと、変更に強くなりやすいです。

Tell, Don’t Ask

Tell, Don’t Askは、オブジェクトの内部状態を外へ聞き出して判断するより、オブジェクトへ命令する形に寄せる考え方です。

// 状態を聞いて外で判断する
if (order.status === "draft") {
  order.status = "confirmed";
}

// 振る舞いとして閉じ込める
order.confirm();

後者では、確認可能な状態か、在庫引当が必要か、二重確認をどう防ぐかを Order の近くに置けます。

SOLIDとの関係

SOLIDはOOPの文脈で語られることが多い原則です。

  • Single Responsibility 変更理由を1つに近づける
  • Open/Closed 既存コードを壊さず拡張できる形にする
  • Liskov Substitution 派生型を基底型として扱っても意味が壊れない
  • Interface Segregation 使わない操作への依存を避ける
  • Dependency Inversion 具体実装ではなく抽象へ依存する

ただし原則は道具です。機械的に適用すると、抽象化だけが増えて読みにくくなります。

ドメインモデルとアクティブレコード

OOPでよく分かれるのが、ドメインロジックをどこに置くかです。

  • transaction script 手続きとして処理を並べる
  • domain model 業務概念に振る舞いを持たせる
  • active record データアクセスとモデルを近づける

小さいアプリケーションではtransaction scriptが分かりやすいこともあります。業務ルールが増えると、domain modelへ寄せる価値が出ます。

貧血ドメインモデル

貧血ドメインモデルとは、オブジェクトがデータだけを持ち、業務ロジックがservice層へ流れ出している状態です。

兆候:
  User, Order, Invoice はgetter/setterだけ
  OrderServiceにすべての業務条件が集まる
  不変条件が複数箇所に散らばる

すべてのアプリでrich domain modelが必要なわけではありませんが、業務ルールが増えてきたら、データの近くに振る舞いを戻せないか検討します。

境界づけとOOP

OOPのクラス境界は、DDDの境界づけられたコンテキストやアプリケーション層の境界とも関係します。クラス単体をきれいにしても、コンテキストをまたいで同じモデルを使い回すと意味が壊れます。

たとえば「顧客」は、請求、サポート、販売で別の関心を持ちます。同じclassを共有するより、文脈ごとに必要なモデルを分ける方が変更に強いことがあります。

テストしやすいOOP

テストしやすいOOPでは、外部I/Oと業務判断が分かれています。

  • domain objectは不変条件を守る
  • application serviceはユースケースを調整する
  • repositoryやgatewayは外部接続を隠す
  • clockやID生成は注入できるようにする

この分離があると、業務ルールのテストはDBやHTTPなしで実行できます。

実務での判断

OOPを使うときは、次を確認します。

  • そのクラスは何の責務を持つか
  • 外から不正な状態を作れないか
  • 変更理由が混ざっていないか
  • 継承より合成で表せないか
  • テストで自然に使えるAPI

よくある誤解

  • クラスを作ればOOPではない
  • 何でも継承で表せばよいわけではない
  • getter / setterだらけの設計は、データの袋に近づきやすい

APIとしてのクラス

クラスは内部実装の置き場ではなく、利用者に見せるAPIでもあります。よいクラスは、内部状態を隠すだけでなく、正しい使い方へ自然に導きます。

確認する観点:

  • constructorで不正な状態を作れないか
  • public methodが多すぎないか
  • setterで不変条件を破れないか
  • method名が業務の言葉になっているか
  • 呼び出し順序に暗黙の前提がないか

たとえば order.setStatus("paid") より、order.markPaid(payment) の方が、何が起きるのか、どの情報が必要なのかを表しやすくなります。OOPの読みやすさは、クラス数の少なさではなく、操作の意図がAPIに現れているかで決まります。

Javaのオブジェクト指向モデル詳細

Java仮想マシンのオブジェクトメモリ構造

Javaオブジェクトは、JVM(Java Virtual Machine)メモリ上でどのように配置されるかが、性能とメモリ効率を左右します。

NOTE

オブジェクトのメモリレイアウトはJVM実装に依存するが、一般的には「ヘッダ + フィールド + パディング」の構造を持つ。

Java SE 21でのオブジェクトヘッダ

[Mark Word    ] - ロック情報、GC世代情報(8-16 bytes)
[Klass Pointer] - クラスオブジェクトへの参照(4-8 bytes)
[Field Values ] - インスタンス変数
[Padding      ] - 8バイト境界アライメント

Javaのequals() / hashCode() / toString()

Object基底クラスのメソッド契約

Javaではすべてのクラスが Object から継承します。JavaSE21ドキュメント(docs.oracle.com)での定義:

public boolean equals(Object obj) {
    return (this == obj);  // デフォルトは参照比較
}

public int hashCode() {
    // default: System.identityHashCode(this)
}

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

重要な契約:

  • equals(a, a) は常にtrue
  • a.equals(b) ならば a.hashCode() == b.hashCode()
  • HashMapやHashSetはhashCode()の契約を前提

####値オブジェクトの実装例

public class EmailAddress {
    private final String value;
    
    public EmailAddress(String value) {
        if (!value.contains("@")) 
            throw new IllegalArgumentException("invalid email");
        this.value = value;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof EmailAddress)) return false;
        return value.equals(((EmailAddress) obj).value);
    }
    
    @Override
    public int hashCode() {
        return value.hashCode();
    }
}

このパターンにより、不変条件がコンパイル時・実行時に保証されます。

インターフェース(Interface)と抽象クラス(Abstract Class)の使い分け

Java言語仕様(Java Language Specification, JavaSE21, docs.oracle.com)では:

特性 Interface Abstract Class
状態 定数のみ フィールド可
コンストラクタ なし あり
多重継承 可(複数interface) 不可(単一abstract)
アクセス修飾子 publicのみ(暗黙的) private/protected可
用途 契約・能力 共通実装

interface例: 戦略パターン

public interface PaymentStrategy {
    void pay(double amount);
    boolean supportsRefund();
}

public class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;
    
    @Override
    public void pay(double amount) {
        // 実装
    }
    
    @Override
    public boolean supportsRefund() {
        return true;
    }
}

このようにすると、新しい支払い手段を足しても既存コードが変わりません(Open/Closed原則)。

多態性とリスコフ置換原則(Liskov Substitution Principle)

リスコフ置換原則とは、派生型が基底型の代わりに使える(動作が保証される)ということです。

破られた例:

public class Bird {
    public void fly() { /* ... */ }
}

public class Penguin extends Bird {  // 誤り: ペンギンは飛べない
    @Override
    public void fly() {
        throw new UnsupportedOperationException();
    }
}

// 呼び出し側
public void launch(Bird b) {
    b.fly();  // Penguinが渡されると例外
}

改善案:

public interface Animal { }
public interface FlyingBird extends Animal { void fly(); }
public interface SwimmingBird extends Animal { void swim(); }

public class Sparrow implements FlyingBird { /* ... */ }
public class Penguin implements SwimmingBird { /* ... */ }

責務の境界が明確になり、予期しない例外がなくなります。

ジェネリクス(Generics)と型安全性

Java 5.0で導入されたジェネリクスは、コンパイル時の型検査を可能にします。

// 型安全ではない(昔のやり方)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);  // キャスト必須

// 型安全(Java 5+)
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);  // キャスト不要

型消去(Type Erasure)により、実行時には List<String>List<Object>List に統一されますが、コンパイル時の安全性と可読性が向上します。

関数型インターフェース(Functional Interface)とLambda式

Java 8以降、関数型インターフェース(単一抽象メソッドを持つinterface)はLambda式で実装できます。

@FunctionalInterface
public interface PaymentProcessor {
    boolean process(double amount);
}

// 従来の実装
PaymentProcessor processor = new PaymentProcessor() {
    @Override
    public boolean process(double amount) {
        return amount > 0;
    }
};

// Lambda式(Java 8+)
PaymentProcessor processor = (amount) -> amount > 0;

この機能により、コールバックやStrategy パターンをより簡潔に書けます。

セリアライゼーション(Serialization)とoSerialversionUID

オブジェクトを外部に保存・送信する際、Javaは Serializable インターフェースを使います(JavaSE 21, docs.oracle.com)。

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    private transient String password;  // シリアライズ対象外
    
    // ...
}

serialVersionUID が変わると逆デシリアライズで失敗するため、クラス進化時は慎重に。

Pythonのオブジェクトモデル

Python の everything is object

Pythonではすべてがオブジェクト(Python 3, docs.python.org)。型もメタクラスのインスタンス。

class User:
    def __init__(self, name):
        self.name = name

u = User("Alice")
print(type(u))          # <class 'User'>
print(type(User))       # <class 'type'>
print(type(type))       # <class 'type'>

プロトコル(Protocol)と構造的部分型

Pythonでは「ダックタイピング」により、名前よりも「インターフェース(メソッド存在)」で判定します。

from typing import Protocol

class PaymentGateway(Protocol):
    def authorize(self, amount: float) -> bool: ...
    def capture(self, transaction_id: str) -> bool: ...

def process_order(gateway: PaymentGateway, amount: float):
    if gateway.authorize(amount):
        # ...

任意のクラスがこのメソッドを持てば、PaymentGatewayとして使えます。

@propertyと遅延初期化

class User:
    def __init__(self, name):
        self._name = name
        self._email = None
    
    @property
    def name(self):
        return self._name
    
    @property
    def email(self):
        if self._email is None:
            self._email = f"{self._name.lower()}@example.com"
        return self._email

Pythonではgetterを明示的に呼ぶ必要がなく、user.name_nameにアクセス可能。

メタクラス(Metaclass)による動的クラス生成

class SingletonMeta(type):
    _instances = {}
    
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    pass

db1 = Database()
db2 = Database()
print(db1 is db2)  # True

メタクラスでクラスそのものを制御できます。

C# の オブジェクト指向機能

NullableReference Types(C# 8.0+)

C# 8.0で導入されたnullable reference typeは、null参照例外(Null Reference Exception)を減らします(learn.microsoft.com)。

#nullable enable

public class User {
    public string Name { get; set; }  // null不許可(デフォルト)
    public string? Email { get; set; }  // null许可
}

var user = new User { Name = null };  // 警告
var email = user.Email?.Length;       // null-coalescing operator

プロパティ(Properties)と自動プロパティ

// 昔のやり方
private int _age;
public int Age {
    get { return _age; }
    set { _age = value; }
}

// 自動プロパティ(C# 3.0+)
public int Age { get; set; }

// 初期化のみ許可(C# 6.0+)
public string Name { get; init; }

イベント(Events)とデリゲート(Delegates)

public delegate void OrderShippedEventHandler(object sender, OrderShippedEventArgs e);

public class Order {
    public event OrderShippedEventHandler? OnShipped;
    
    public void Ship() {
        OnShipped?.Invoke(this, new OrderShippedEventArgs { ... });
    }
}

イベントパターンはPub/Subを型安全に実装できます。

リレクション(Reflection)とAttribution

var userType = typeof(User);
var properties = userType.GetProperties();

foreach (var prop in properties) {
    var attr = prop.GetCustomAttribute<RequiredAttribute>();
    if (attr != null) {
        Console.WriteLine({{CONTENT}}quot;{prop.Name} is required");
    }
}

リレクションでクラス情報を動的に検査し、フレームワークを構築します。

オブジェクト指向設計のアンチパターンと改善

良いOOD は発展性と保守性を実現します。一方、悪いOOD は「スパゲッティコード」化します。

God Object アンチパターン

# Bad: 1つのクラスが何でもやる
class User:
    def create_account(self): ...
    def send_email(self): ...
    def log_to_database(self): ...
    def validate_credit_card(self): ...
    def calculate_taxes(self): ...
    def render_ui(self): ...

単一責任の原則(SRP)に違反。変更理由が6つもあります。

# Good: 責任を分離
class User:
    name: str
    email: str

class UserRepository:
    def save(self, user: User): ...

class EmailService:
    def send(self, to: str, subject: str, body: str): ...

class TaxCalculator:
    def calculate_income_tax(self, income: float) -> float: ...

各クラスの変更理由は1つだけ。

Liskov Substitution Principle (LSP)

派生クラスは親クラスの契約を守らねばなりません:

# Bad: LSP 違反
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def set_width(self, w):
        self.width = w
    
    def set_height(self, h):
        self.height = h

class Square(Rectangle):
    def set_width(self, w):
        # 正方形は幅と高さが同じ
        self.width = w
        self.height = w  # 予期しない副作用!

# 使用側の予想が裏切られる
def process_rect(rect: Rectangle):
    rect.set_width(5)
    rect.set_height(10)
    assert rect.width == 5 and rect.height == 10  # Square で失敗

正しい設計:

class Shape:
    def area(self) -> float: ...

class Rectangle(Shape):
    def area(self):
        return self.width * self.height

class Square(Shape):
    def area(self):
        return self.side ** 2

型関係ではなく、振る舞いの契約を重視。

Interface Segregation Principle (ISP)

大きなインターフェースを分割:

# Bad: 関連のない責務を混在
class DatabaseConnection:
    def connect(self): ...
    def query(self, sql): ...
    def cache_set(self, key, value): ...
    def publish_message(self, topic, msg): ...

# クライアントは不要なメソッドも実装必須
class PostgresConnection(DatabaseConnection):
    def cache_set(self, ...): pass  # キャッシュ不要だが実装必須
# Good: インターフェース分割
class Connection(Protocol):
    def query(self, sql) -> List[Row]: ...

class CacheClient(Protocol):
    def set(self, key: str, value: Any) -> None: ...

class MessageBroker(Protocol):
    def publish(self, topic: str, msg: str) -> None: ...

class PostgresConnection:
    def query(self, sql) -> List[Row]: ...

class RedisCache:
    def set(self, key: str, value: Any) -> None: ...

必要なインターフェースだけ実装。

ジェネリック と パラメトリック多型

Java / C# のジェネリックにより、型安全なコレクションを実装:

// Java: 型パラメータ <T>
public class Box<T> {
    private T value;
    
    public void put(T item) {
        this.value = item;
    }
    
    public T get() {
        return value;
    }
}

// 使用
Box<String> stringBox = new Box<>();
stringBox.put("Hello");
String s = stringBox.get();  // キャストが自動(型安全)

Box<Integer> intBox = new Box<>();
intBox.put(42);
int i = intBox.get();

型消去(Type Erasure):コンパイル時のみ存在、実行時は消去

Box<String> stringBox = new Box<>();
Box<Integer> intBox = new Box<>();

// 実行時は両方 Box.class(同一)
stringBox.getClass() == intBox.getClass();  // true

このため、実行時に型パラメータにはアクセスできません:

// コンパイルエラー
if (obj instanceof Box<String>) { }  // <String> は実行時に不可視

メモリモデルと並行性

Java Memory Model (JMM) は、マルチスレッド環境での可視性を規定します:

Happens-Before 関係

操作 A が B より先に完了することを保証:

class Worker {
    private static int counter = 0;
    
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> counter = 1);
        Thread t2 = new Thread(() -> System.out.println(counter));
        
        t1.start();
        t1.join();  // Happens-Before: t1 の完了 → t2 の開始
        t2.start();
        
        // counter は 1 を確実に見る(0 ではない)
    }
}

join() は Happens-Before を生成。その他:

  • synchronized ブロック
  • volatile 変数
  • AtomicInteger 等の原子操作

Race Condition の実例

// Race Condition: 非同期アクセス
class Counter {
    int count = 0;
    
    void increment() {
        count++;  // read-modify-write は原子的でない
    }
}

// 100スレッドが 1000回 increment
// 期待値: 100,000
// 実際値: 80,000~99,000 (毎回異なる)

修正:

class Counter {
    int count = 0;
    
    synchronized void increment() {  // 同期化
        count++;
    }
}
// または
class Counter {
    AtomicInteger count = new AtomicInteger(0);
    void increment() {
        count.incrementAndGet();
    }
}

メモリ可視性は、パフォーマンスと正確性のバランスです。

デザインパターンと SOLID 原則

Factory Pattern でオブジェクト生成を抽象化。Observer Pattern で変更通知。Strategy Pattern で実行時にアルゴリズムを選択。

SOLID 原則:

  • Single Responsibility:1クラス 1責任
  • Open-Closed:拡張に開き、修正に閉じる
  • Liskov Substitution:派生クラスは親の契約を守る
  • Interface Segregation:必要なインターフェースだけ実装
  • Dependency Inversion:具体的なクラスでなくインターフェースに依存

テスト駆動開発(TDD):テストを先に書き、実装を後追い。設計が明確化。

OOP デザインパターンの実装詳細

Factory パターン

オブジェクト生成を専門メソッドに委譲。生成ロジックの変更が 1 箇所に集中。

SimpleFactory: 条件分岐で型に応じたインスタンス生成。FactoryMethod: サブクラスで生成ロジック定義。AbstractFactory: 複数の関連オブジェクトファミリーを生成。

Observer パターン

Subject と複数の Observer の 1:n 関係。Subject 状態変化を全 Observer に通知。イベント駆動アーキテクチャ基盤。

pub/sub メッセージング、GUI イベントハンドリングで採用。

Decorator パターン

オブジェクトに動的に機能追加。継承より柔軟(機能組合わせが自由)。

Stream チェーン(java.io.InputStream に InputStream.InputFilter ラッパー)、Python の @decorator。

Strategy パターン

アルゴリズムをカプセル化して切り替え可能。条件分岐の排除。

支払い方法(Credit Card、PayPal、Bitcoin)の切り替え。ソートアルゴリズム(QuickSort、MergeSort)の切り替え。

SOLID 原則の実装

Single Responsibility: 1 クラス = 1 責務。Open/Closed: 拡張に開かれ、修正に閉じられ。Liskov Substitution: サブクラスが基底クラスの代わりに使用可能。Interface Segregation: 小さなインターフェース多数が大きなインターフェース 1 つより良い。Dependency Inversion: 抽象に依存、具象に依存しない。

クラス設計の アンチパターン

God Object: 1 つのクラスが過度な責務を持つ。Feature Envy: 他のクラスの内部詳細に依存。Circular Dependency: 循環参照。

対策: 責務分割、適切なカプセル化、インターフェース分離。

/tour-of-csharp/)

解説・補助

  • [The C++ Programmi

クラス継承と Liskov Substitution Principle

Liskov Substitution PrincipleLSP): 派生クラスは基底クラスの代わりに使用可能。契約(precondition、postcondition)の遵守。

違反例: Square が Rectangle から継承しても、width と height の独立変更を許さない(LSP 違反)。

インターフェースと抽象クラス

インターフェース: 契約のみ定義。実装なし。複数継承可能。

抽象クラス: 部分的実装 + 契約。状態(フィールド)を持つ。単一継承。

Template Method Pattern: 抽象クラスでアルゴリズムの骨組み定義。派生クラスで特定ステップ実装。

型システムと静的 vs 動的型付け

静的型付け(Java、C++): コンパイル時の型チェック。バグ早期発見。実行時オーバーヘッド軽減。

動的型付け(Python、Ruby): 実行時の型チェック。柔軟性向上。遅延を伴うリスク。

Duck Typing: オブジェクトの型より、その振る舞いで判定。Python の philosophy。

メモリ管理と GC

Manual management(C/C++): 開発者が mallocfree。性能最高、バグリスク高。

Garbage Collection(Java、C#): 自動メモリ解放。pause time がアプリケーションに影響。

Reference counting(weak reference): Objective-C、Swift の方法。循環参照を避ける必要。

実装パターン集

Singleton パターン

スレッドセーフな instance 1 つだけ保証。Global state 管理。

Java: enum Singleton { INSTANCE; }(最も安全)

Python: decorator または metaclass。

過度な使用は anti-pattern。Dependency Injection 推奨。

Builder パターン

複雑なオブジェクト構築。段階的に属性設定。

例:HTTP Request オブジェクト。Method → URL → Headers → Body。

Fluent interface で可読性向上。

Proxy パターン

オブジェクトへのアクセスを代理。遅延初期化、access control、logging。

例:Database connection proxy(接続遅延)、Security proxy(権限チェック)。

Chain of Responsibility

複数のオブジェクトが request を処理。順序に沿って伝播。

例:ロギングフレームワークの filter chain。

Command パターン

操作をオブジェクト化。undo/redo の実装が容易。

例:テキストエディタの各編集操作。

ng Language](https://www.stroustrup.com/4th.html)

関連技術とエコシステム

ここで紹介した各技術には、活発なコミュニティ・エコシステムが存在。 継続的な学習とアップデートを推奨。実装言語・フレームワークの選択は プロジェクト要件に基づいて判断。性能、保守性、開発速度のバランスが重要。

まとめ

オブジェクト指向は、責務をまとまりとして扱うための方法です。状態を守る境界、役割ごとの分割、変更しやすさを意識すると、表面的なクラス設計から一歩進めます。

参考文献

公式・標準