💭

オブジェクト指向って何?初心者向けに超ざっくり解説

2025/03/05に公開
2

プログラミングを学んでいると必ず出てくる「オブジェクト指向」という言葉。なんとなく難しそうで、初心者の方は尻込みしてしまうかもしれません。この記事では、オブジェクト指向プログラミングの基本的な考え方を、できるだけわかりやすく、「超ざっくり」と解説していきます。

オブジェクト指向とは?

簡単に言うと…

オブジェクト指向プログラミング(Object-Oriented Programming、略してOOP)とは、プログラムを「モノ(オブジェクト)」の集まりとして考えるプログラミングの方法です。

例えば、車を管理するプログラムを作るとしましょう。オブジェクト指向では、「車」というモノ(オブジェクト)を作り、その車が持つ「特徴(プロパティ)」と「できること(メソッド)」を定義します。

現実世界との対応

オブジェクト指向の考え方は、実は私たちの日常生活の考え方に近いんです。例えば:

  • というオブジェクトには、名前年齢犬種などの特徴(プロパティ)があり、吠える走る寝るなどの行動(メソッド)ができます。
  • スマートフォンというオブジェクトには、メーカーモデル画面サイズなどの特徴があり、電話をかけるメールを送る写真を撮るなどの機能があります。

オブジェクト指向プログラミングでは、このような「モノ」とその「特徴」「機能」をプログラムの中で表現します。

オブジェクト指向の4つの柱

オブジェクト指向プログラミングには、4つの重要な概念(柱)があります。これらを理解すると、オブジェクト指向の本質がわかります。

1. カプセル化(Encapsulation)

カプセル化とは、データ(プロパティ)と機能(メソッド)をひとまとめにして、外部からの不正なアクセスから守ることです。

例えば、銀行口座を考えてみましょう:

class BankAccount {
  // プライベート変数(外部から直接アクセスできない)
  #balance = 0;
  
  // 公開メソッド(外部からアクセスできる)
  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
      return true;
    }
    return false;
  }
  
  withdraw(amount) {
    if (amount > 0 && this.#balance >= amount) {
      this.#balance -= amount;
      return true;
    }
    return false;
  }
  
  getBalance() {
    return this.#balance;
  }
}

// 使用例
const myAccount = new BankAccount();
myAccount.deposit(1000);
console.log(myAccount.getBalance()); // 1000
myAccount.withdraw(500);
console.log(myAccount.getBalance()); // 500

// 直接アクセスはできない
// console.log(myAccount.#balance); // エラー!

この例では、口座残高(#balance)は直接変更できないようにプライベート変数になっています。残高を変更するには、deposit(入金)やwithdraw(出金)のメソッドを使う必要があり、これらのメソッドは金額が正しいかなどのチェックを行います。

2. 継承(Inheritance)

継承とは、既存のクラス(親クラス)の特徴や機能を引き継いで、新しいクラス(子クラス)を作ることです。

例えば、「動物」という親クラスから「犬」や「猫」という子クラスを作る場合:

// 親クラス
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  eat() {
    return `${this.name}が食事をしています`;
  }
  
  sleep() {
    return `${this.name}が眠っています`;
  }
}

// 子クラス(Animalを継承)
class Dog extends Animal {
  bark() {
    return `${this.name}がワンワン吠えています`;
  }
}

// 子クラス(Animalを継承)
class Cat extends Animal {
  meow() {
    return `${this.name}がニャーと鳴いています`;
  }
}

// 使用例
const dog = new Dog("ポチ");
console.log(dog.eat());   // ポチが食事をしています
console.log(dog.sleep()); // ポチが眠っています
console.log(dog.bark());  // ポチがワンワン吠えています

const cat = new Cat("タマ");
console.log(cat.eat());   // タマが食事をしています
console.log(cat.sleep()); // タマが眠っています
console.log(cat.meow());  // タマがニャーと鳴いています

この例では、DogCatAnimalクラスを継承しているため、eatsleepのメソッドを改めて定義しなくても使うことができます。さらに、それぞれ独自のメソッド(barkmeow)を追加しています。

3. ポリモーフィズム(Polymorphism)

ポリモーフィズムとは、異なるオブジェクトに対して共通のインターフェース(メソッド)を提供し、統一的な方法でアクセスできるようにすることです。これにより、コードの再利用性が高まり、より柔軟なプログラミングが可能になります。

例えば、さまざまな形状の面積を計算する場合:

// 親クラス(共通のインターフェースを定義)
class Shape {
  calculateArea() {
    // 基本的な実装(子クラスで具体的な計算方法を提供)
    return 0;
  }
}

// 子クラス
class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }
  
  calculateArea() {
    return Math.PI * this.radius * this.radius;
  }
}

// 子クラス
class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }
  
  calculateArea() {
    return this.width * this.height;
  }
}

// 使用例
const shapes = [
  new Circle(5),
  new Rectangle(4, 6)
];

// 共通のインターフェース(calculateArea)を通じて
// 各図形の面積を計算できる
shapes.forEach(shape => {
  console.log(`面積: ${shape.calculateArea()}`);
});
// 出力:
// 面積: 78.53981633974483
// 面積: 24

この例では、ShapeクラスがcalculateAreaという共通のインターフェースを定義し、CircleRectangleがそれぞれ独自の計算方法を提供しています。利用する側は、具体的なクラスの違いを意識することなく、同じインターフェースを通じて面積を計算できます。

これがポリモーフィズムの本質で、共通のインターフェースを通じて異なるオブジェクトを統一的に扱えることが重要なポイントです。

4. 抽象化(Abstraction)

抽象化とは、複雑なシステムの詳細を隠し、本質的な部分だけを表現することです。

例えば、車を運転する場合、エンジンの内部構造を知らなくても、アクセルを踏めば進み、ブレーキを踏めば止まることを知っていれば十分です。

// 抽象クラス(直接インスタンス化はできない)
class Vehicle {
  constructor(make, model) {
    this.make = make;
    this.model = model;
    
    // このクラスが直接インスタンス化されないようにする
    if (this.constructor === Vehicle) {
      throw new Error("Vehicle クラスは抽象クラスです");
    }
  }
  
  // 抽象メソッド(子クラスで実装する必要がある)
  start() {
    throw new Error("start メソッドを実装してください");
  }
  
  stop() {
    throw new Error("stop メソッドを実装してください");
  }
  
  // 共通の機能
  getInfo() {
    return `${this.make} ${this.model}`;
  }
}

// 具象クラス
class Car extends Vehicle {
  start() {
    return `${this.getInfo()} のエンジンを始動しました`;
  }
  
  stop() {
    return `${this.getInfo()} のエンジンを停止しました`;
  }
}

// 使用例
const myCar = new Car("Toyota", "Corolla");
console.log(myCar.start()); // Toyota Corolla のエンジンを始動しました
console.log(myCar.stop());  // Toyota Corolla のエンジンを停止しました

この例では、Vehicleクラスは抽象クラスとして機能し、startstopの具体的な実装は子クラスに任せています。利用者は車を「始動」したり「停止」したりできますが、その内部の仕組みを知る必要はありません。

オブジェクト指向のメリット

オブジェクト指向プログラミングには、以下のようなメリットがあります:

1. コードの再利用性が高まる

継承を使うことで、既存のコードを再利用して新しい機能を追加できます。同じコードを何度も書く必要がなくなります。

2. メンテナンスが容易になる

カプセル化によって、プログラムの一部を変更しても、他の部分に影響を与えにくくなります。これにより、バグの修正や機能の追加が容易になります。

3. 現実世界のモデル化が自然にできる

オブジェクト指向は現実世界の考え方に近いため、現実の問題をプログラムに落とし込みやすくなります。

4. チーム開発に適している

大規模なプロジェクトでは、機能ごとにクラスを分担して開発できるため、チーム開発に適しています。

実践例:シンプルな図書館システム

オブジェクト指向の考え方を使った、シンプルな図書館システムの例を見てみましょう:

// 本を表すクラス
class Book {
  constructor(title, author, isbn) {
    this.title = title;
    this.author = author;
    this.isbn = isbn;
    this.isAvailable = true;
  }
  
  checkout() {
    if (this.isAvailable) {
      this.isAvailable = false;
      return true;
    }
    return false;
  }
  
  returnBook() {
    this.isAvailable = true;
  }
  
  getInfo() {
    return `${this.title} by ${this.author} (ISBN: ${this.isbn})`;
  }
}

// 図書館を表すクラス
class Library {
  constructor(name) {
    this.name = name;
    this.books = [];
  }
  
  addBook(book) {
    this.books.push(book);
  }
  
  findBookByISBN(isbn) {
    return this.books.find(book => book.isbn === isbn);
  }
  
  checkoutBook(isbn) {
    const book = this.findBookByISBN(isbn);
    if (book) {
      if (book.checkout()) {
        return `${book.getInfo()} を貸し出しました`;
      } else {
        return `${book.getInfo()} は現在貸出中です`;
      }
    }
    return `ISBN: ${isbn} の本は見つかりませんでした`;
  }
  
  returnBook(isbn) {
    const book = this.findBookByISBN(isbn);
    if (book) {
      book.returnBook();
      return `${book.getInfo()} が返却されました`;
    }
    return `ISBN: ${isbn} の本は見つかりませんでした`;
  }
  
  getAvailableBooks() {
    return this.books.filter(book => book.isAvailable);
  }
}

// 利用者を表すクラス
class User {
  constructor(name, id) {
    this.name = name;
    this.id = id;
    this.borrowedBooks = [];
  }
  
  borrowBook(library, isbn) {
    const result = library.checkoutBook(isbn);
    if (result.includes("貸し出しました")) {
      this.borrowedBooks.push(isbn);
    }
    return result;
  }
  
  returnBook(library, isbn) {
    const index = this.borrowedBooks.indexOf(isbn);
    if (index !== -1) {
      const result = library.returnBook(isbn);
      this.borrowedBooks.splice(index, 1);
      return result;
    }
    return `あなたはISBN: ${isbn} の本を借りていません`;
  }
}

// 使用例
// 図書館の作成
const cityLibrary = new Library("市立図書館");

// 本の追加
cityLibrary.addBook(new Book("JavaScript入門", "山田太郎", "123456"));
cityLibrary.addBook(new Book("Pythonプログラミング", "佐藤花子", "234567"));
cityLibrary.addBook(new Book("データベースの基礎", "鈴木一郎", "345678"));

// 利用者の作成
const user1 = new User("田中次郎", "U001");
const user2 = new User("伊藤三郎", "U002");

// 本の貸し出しと返却
console.log(user1.borrowBook(cityLibrary, "123456"));
// JavaScript入門 by 山田太郎 (ISBN: 123456) を貸し出しました

console.log(user2.borrowBook(cityLibrary, "123456"));
// JavaScript入門 by 山田太郎 (ISBN: 123456) は現在貸出中です

console.log(user1.returnBook(cityLibrary, "123456"));
// JavaScript入門 by 山田太郎 (ISBN: 123456) が返却されました

console.log(user2.borrowBook(cityLibrary, "123456"));
// JavaScript入門 by 山田太郎 (ISBN: 123456) を貸し出しました

// 利用可能な本のリスト
const availableBooks = cityLibrary.getAvailableBooks();
console.log("利用可能な本:");
availableBooks.forEach(book => console.log(book.getInfo()));
// 利用可能な本:
// Pythonプログラミング by 佐藤花子 (ISBN: 234567)
// データベースの基礎 by 鈴木一郎 (ISBN: 345678)

この例では、Book(本)、Library(図書館)、User(利用者)という3つのクラスを定義しています。それぞれのクラスは、自分の役割に応じたプロパティとメソッドを持っています。

  • Bookクラスは本の情報と貸出状態を管理します。
  • Libraryクラスは本のコレクションと、貸出・返却の処理を管理します。
  • Userクラスは利用者の情報と、借りている本を管理します。

これらのクラスが連携することで、図書館システムとして機能します。

オブジェクト指向を学ぶためのステップ

オブジェクト指向プログラミングを学ぶには、以下のステップを踏むとよいでしょう:

1. クラスとオブジェクトの基本を理解する

まずは、クラスとオブジェクトの関係を理解しましょう。クラスは設計図、オブジェクトはその設計図から作られた実体です。

2. カプセル化の概念を学ぶ

データと機能をひとまとめにして、外部からの不正なアクセスから守る方法を学びましょう。

3. 継承とポリモーフィズムを理解する

既存のクラスを拡張して新しいクラスを作る方法と、同じインターフェースで異なる動作を実現する方法を学びましょう。

4. 実際にコードを書いてみる

簡単なプログラムから始めて、徐々に複雑なシステムを作ってみましょう。実際にコードを書くことで、理解が深まります。

5. デザインパターンを学ぶ

オブジェクト指向プログラミングでよく使われる設計パターンを学ぶと、より効率的にプログラムを設計できるようになります。

よくある質問

Q: オブジェクト指向は難しいですか?

A: 最初は概念を理解するのに時間がかかるかもしれませんが、基本を押さえれば徐々に理解できるようになります。現実世界の物事を考えるように、プログラムを考えられるようになると、むしろ直感的にコードが書けるようになります。

Q: オブジェクト指向以外のプログラミングパラダイムはありますか?

A: はい、主なものとして以下があります:

  • 手続き型プログラミング:処理の手順を順番に記述する方法
  • 関数型プログラミング:関数を組み合わせてプログラムを構築する方法
  • 論理型プログラミング:論理的な規則に基づいてプログラムを記述する方法

Q: どのプログラミング言語がオブジェクト指向に適していますか?

A: Java、C++、C#、Python、Ruby、JavaScript(ES6以降)など、多くの現代的なプログラミング言語はオブジェクト指向プログラミングをサポートしています。初心者にはPythonやJavaScriptがおすすめです。

Q: オブジェクト指向は小規模なプロジェクトでも役立ちますか?

A: 小規模なプロジェクトでは、オブジェクト指向の恩恵を十分に受けられないこともありますが、コードの整理や再利用性の向上には役立ちます。また、将来的に規模が大きくなった場合にも対応しやすくなります。

まとめ

この記事では、オブジェクト指向プログラミングの基本的な考え方を「超ざっくり」と解説しました。

オブジェクト指向プログラミングは、プログラムを「モノ(オブジェクト)」の集まりとして考える方法で、以下の4つの柱があります:

  1. カプセル化:データと機能をひとまとめにして保護する
  2. 継承:既存のクラスの特徴や機能を引き継いで新しいクラスを作る
  3. ポリモーフィズム:同じインターフェースでも異なる動作を実現する
  4. 抽象化:複雑な詳細を隠し、本質的な部分だけを表現する

オブジェクト指向プログラミングを使うことで、コードの再利用性が高まり、メンテナンスが容易になり、現実世界のモデル化が自然にできるようになります。

プログラミングの学習を進める中で、オブジェクト指向の考え方を少しずつ取り入れていくと、より効率的で保守性の高いコードが書けるようになるでしょう。

Discussion

junerjuner

ポリモーフィズムとは、同じインターフェース(メソッド名)でも、オブジェクトによって異なる動作をすることです。

異なる動作の必要は無くて 一元アクセスできる共通接点の提供(ここだと関数)をそれぞれ提供することでは……?

https://ja.wikipedia.org/wiki/ポリモーフィズム

宝松@システムエンジニア宝松@システムエンジニア

ご指摘ありがとうございます!✨ 確かに「異なる動作をすること」という説明だと、「振る舞いが違わなきゃいけないの?」と誤解される可能性がありますね!😲💡

本質的には 「共通のインターフェースを提供し、一元的にアクセスできること」 がポリモーフィズムのポイントですよね!「異なる動作をすること」は結果的に多くの場合そうなるけど、それ自体がポリモーフィズムの定義ではない……🤔💭

ご指摘のおかげで、より伝わりやすい表現を考えるきっかけになりました!感謝です🙏✨

(今後の記事の改善にも活かします!🙌)