オブジェクト指向とは、「データ」と「そのデータを扱う処理」をひとまとめにした「オブジェクト」を組み合わせて、プログラムを作る考え方です。たとえば「銀行口座」なら、残高というデータと、入金・出金という処理を1つにまとめ、残高は入金・出金の処理を通してしか変えられないようにします。

オブジェクト指向の説明は、「車」や「動物」のたとえ話で終わってしまうことが多く、実際のコードでどう役立つのかがわかりにくいのが難点です。この記事では、JavaとPythonの短いコードで、クラスとインスタンス、そしてオブジェクト指向の3つの柱(カプセル化・継承・ポリモーフィズム)を解説し、実務での使いどころと、使いすぎによる失敗までを紹介します。

オブジェクト指向が解決する問題

オブジェクト指向を使わずに、銀行口座の残高を扱うプログラムを考えます。

balance = 1000

def withdraw(amount):
    global balance
    balance -= amount

withdraw(300)
balance = -99999   # どこからでも、残高を自由に書き換えられてしまう

小さなプログラムなら問題ありませんが、規模が大きくなると、「どこで、誰が、残高を書き換えたのか」を追えなくなります。残高がマイナスになる、ありえない値が入る、といった不具合の原因を探すのに、プログラム全体を調べる必要が出てきます。

オブジェクト指向は、データを、それを正しく扱う処理と一緒に閉じ込め、決められた方法でしか変更できないようにすることで、この問題を解決します。

クラスとインスタンス

用語 意味 たとえ
クラス オブジェクトの設計図 口座の申込書のひな形
インスタンス(オブジェクト) クラスから作った実体 山田さんの口座、佐藤さんの口座
フィールド(属性) オブジェクトが持つデータ 残高
メソッド オブジェクトが持つ処理 入金する、出金する
// Java
public class BankAccount {
    private int balance;                      // フィールド

    public void deposit(int amount) {         // メソッド
        if (amount <= 0) {
            throw new IllegalArgumentException("入金額は1円以上");
        }
        balance += amount;
    }

    public void withdraw(int amount) {
        if (amount > balance) {
            throw new IllegalStateException("残高不足");
        }
        balance -= amount;
    }

    public int getBalance() {
        return balance;
    }
}

BankAccount yamada = new BankAccount();   // インスタンスを作る
yamada.deposit(1000);
yamada.withdraw(300);
System.out.println(yamada.getBalance());  // 700
yamada.withdraw(5000);                    // 例外:残高不足

1つのクラスから、何個でもインスタンスを作れます。山田さんの口座と佐藤さんの口座は、同じ設計図から作られた、別々の残高を持つ実体です。

3つの柱1:カプセル化

カプセル化とは、データを外から直接触れないように隠し、決められたメソッドを通してだけ操作できるようにすることです。

上のコードでは、balance に private を付けているため、クラスの外から yamada.balance = -99999 と書くことはできません。残高を変える方法は deposit と withdraw だけで、どちらも不正な値をはじくチェックを持っています。

カプセル化しない カプセル化する
どこからでもデータを書き換えられる 決められたメソッドからしか変えられない
不正な値のチェックを、使う側のすべてで行う必要がある チェックはクラスの中の1か所で済む
不具合の原因を、プログラム全体から探す 原因の候補は、そのクラスのメソッドに絞られる

「すべてのフィールドにgetterとsetterを作る」のは、カプセル化ではありません。setterで何でも設定できるなら、直接書き換えられるのと変わらないからです。「どんな操作を許すか」をメソッドとして設計するのが、カプセル化の本質です。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「オブジェクト指向とは?コードでわかる基本と使いどころ」

3つの柱2:ポリモーフィズム(多態性)

ポリモーフィズムとは、同じ呼び出し方で、オブジェクトの種類に応じた違う処理が実行されることです。実務で最も効果を発揮するのが、この性質です。

支払い方法ごとに手数料が違うECサイトを例にします。

// 支払い方法に共通する「約束(インターフェース)」
interface Payment {
    int fee(int amount);
    String name();
}

class CreditCard implements Payment {
    public int fee(int amount) { return amount * 3 / 100; }   // 3%
    public String name() { return "クレジットカード"; }
}

class BankTransfer implements Payment {
    public int fee(int amount) { return 220; }                // 一律220円
    public String name() { return "銀行振込"; }
}

class ConvenienceStore implements Payment {
    public int fee(int amount) { return amount < 10000 ? 330 : 440; }
    public String name() { return "コンビニ払い"; }
}
List<Payment> payments = List.of(new CreditCard(), new BankTransfer(), new ConvenienceStore());
for (Payment p : payments) {
    System.out.println(p.name() + ": " + p.fee(5000) + "円");
}
// クレジットカード: 150円
// 銀行振込: 220円
// コンビニ払い: 330円

呼び出す側は、どの支払い方法かを気にせず、p.fee() と書くだけです。もしオブジェクト指向を使わなければ、次のような条件分岐が、手数料を計算するすべての場所に必要になります。

if (type.equals("credit")) { ... }
else if (type.equals("bank")) { ... }
else if (type.equals("store")) { ... }

新しい支払い方法(電子マネーなど)を追加するとき、ポリモーフィズムを使った設計なら、新しいクラスを1つ追加するだけで、呼び出す側のコードは変えずに済みます。条件分岐で書いた場合は、分岐があるすべての場所を探して修正する必要があり、修正漏れが不具合になります。

Pythonでも同じように書けます。

from abc import ABC, abstractmethod

class Payment(ABC):
    @abstractmethod
    def fee(self, amount: int) -> int: ...

class CreditCard(Payment):
    def fee(self, amount): return amount * 3 // 100

class BankTransfer(Payment):
    def fee(self, amount): return 220

for p in [CreditCard(), BankTransfer()]:
    print(type(p).__name__, p.fee(5000))
# CreditCard 150
# BankTransfer 220

3つの柱3:継承

継承とは、既存のクラスの性質を引き継いで、新しいクラスを作ることです。

class Animal {
    public String name;
    public void eat() { System.out.println(name + "が食べる"); }
}

class Dog extends Animal {             // Animalを継承
    public void bark() { System.out.println("ワン"); }
}

Dog は Animal の name と eat() をそのまま使え、bark() を追加で持ちます。共通の処理を1か所にまとめられる便利な仕組みですが、実務では使いすぎによる問題がよく知られています。

継承の使いすぎで起きること

  • 親クラスを変更すると、すべての子クラスに影響する。親の小さな修正が、思わぬ場所の不具合になる
  • 「〜の一種である(is-a)」関係でないものまで継承すると、不要な機能まで引き継いでしまう
  • 継承が何段も重なると、処理がどのクラスに書かれているのかを追いにくくなる

継承より「組み合わせ(コンポジション)」

現在の設計では、「共通の処理を使いたいだけなら、継承ではなく、そのオブジェクトを部品として持つ」という考え方がよく推奨されます。

class Engine:
    def start(self): return "エンジン始動"

class Car:
    def __init__(self, engine):
        self.engine = engine            # 継承ではなく、部品として持つ

    def drive(self):
        return self.engine.start() + " → 走行"

print(Car(Engine()).drive())   # エンジン始動 → 走行

「車は、エンジンの一種」ではなく「車は、エンジンを持っている(has-a)」です。部品として持つ形にすると、電気モーターのような別の部品に差し替えることも容易になります。

継承(is-a) 組み合わせ(has-a)
関係 〜の一種である 〜を持っている
結びつき 強い(親の変更の影響を受ける) 弱い(部品を差し替えられる)
向いている場面 明確な「種類」の関係、フレームワークの決まり 機能を再利用したい場合の多く

実務で役立つ設計の原則:SOLIDの入口

オブジェクト指向の設計には、「SOLID」と呼ばれる5つの原則がよく知られています。最初は、次の2つを意識するだけでも、コードの質が大きく変わります。

原則 意味 この記事の例
単一責任の原則(S) 1つのクラスが持つ責任(変更の理由)は1つにする 口座クラスは残高の管理だけを行い、画面表示やメール送信は別のクラスにする
開放閉鎖の原則(O) 機能の追加には開いていて、既存のコードの修正には閉じている 支払い方法の追加は、新しいクラスを足すだけで、既存のコードを変えない

残りの3つ(リスコフの置換原則・インターフェース分離の原則・依存性逆転の原則)は、これら2つに慣れてから学ぶと理解しやすくなります。

オブジェクト指向の言語と、よくある誤解

言語 オブジェクト指向との関係
Java、C# クラスが中心。オブジェクト指向を前提とした言語
Python、Ruby、PHP、JavaScript、TypeScript オブジェクト指向で書けるが、関数中心の書き方もできる
Go クラスはないが、構造体とインターフェースで同様の設計ができる
C オブジェクト指向の機能はない

よくある誤解

  • 「クラスを使っていれば、オブジェクト指向」ではない:データと処理が分かれたまま、クラスを入れ物として使っているだけなら、効果は得られません
  • 「すべてをクラスにすべき」ではない:小さなスクリプトや、データを変換するだけの処理は、関数で書いた方がわかりやすいこともあります
  • 「継承を使うのがオブジェクト指向らしい」ではない:実務では、インターフェースと組み合わせが中心です

オブジェクト指向が向かない場面

  • 数十行程度の小さなスクリプト:クラスを設計する手間の方が大きくなる
  • データを順に変換していく処理(集計、データ分析など):関数を組み合わせる書き方(関数型の考え方)の方が自然なことが多い
  • とにかく速さが最優先の処理:オブジェクトを大量に作ると、メモリや処理時間に影響することがある

オブジェクト指向は「変更に強いプログラム」を作るための道具です。長く保守し、機能を追加していくシステムでこそ、効果を発揮します。

よくある質問

Q. オブジェクト指向はなぜ難しいと言われるのですか? A. 小さなプログラムでは効果が実感しにくいからです。機能の追加や変更が重なる中規模以上のプログラムで、「条件分岐を探して直す」手間がなくなる経験をすると、価値がわかりやすくなります。

Q. インターフェースと抽象クラスの違いは何ですか? A. インターフェースは「どんなメソッドを持つか」という約束だけを定めます。抽象クラスは、約束に加えて共通の処理やフィールドも持てます。Javaでは、クラスが実装できるインターフェースは複数、継承できるクラスは1つだけです。迷ったらインターフェースを使うのが一般的です。

Q. AIがコードを書く時代にも、オブジェクト指向を学ぶ必要がありますか? A. あります。AIが生成したコードが、変更に強い構造になっているか、責任の分け方が適切かを判断するには、設計の考え方の理解が欠かせません。また、クラスの責任や約束(インターフェース)を明確に指示すると、AIが生成するコードの質も上がります。

Q. どの言語で学ぶのがおすすめですか? A. オブジェクト指向の考え方を体系的に学ぶなら、クラスとインターフェースが明確なJavaやC#がわかりやすいです。Pythonは書き方が柔軟なぶん、自由に書けてしまう点に注意が必要です。

まとめ

  • オブジェクト指向は、データと処理をまとめたオブジェクトでプログラムを作る考え方
  • カプセル化:データを隠し、決められた操作からだけ変更できるようにする(getter/setterを並べることではない)
  • ポリモーフィズム:同じ呼び出しで種類ごとの処理を実行。種類の追加がクラスの追加だけで済む
  • 継承は便利だが使いすぎに注意。機能の再利用は、組み合わせ(has-a)を優先する
  • まずは単一責任と開放閉鎖の2つの原則を意識する
  • 小さなスクリプトやデータ変換には、無理にクラスを使わなくてよい

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「オブジェクト指向とは?コードでわかる基本と使いどころ」

株式会社RJCでは、未経験から入社したメンバーも、設計の考え方を一歩ずつ身につけています。こうした知識を仲間と共有しながら、一緒に成長を楽しんでいます。

株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

成長のヒミツ、 ぜんぶ丸わかり