- JAVA
- 技術 💻
オブジェクト指向とは?コードでわかる基本と使いどころ
オブジェクト指向とは、「データ」と「そのデータを扱う処理」をひとまとめにした「オブジェクト」を組み合わせて、プログラムを作る考え方です。たとえば「銀行口座」なら、残高というデータと、入金・出金という処理を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期連続の黒字決算。