MENU

【初心者向け】プログラミングの継承とは?オブジェクト指向の基本をわかりやすく解説

目次

継承って何?まずは基本を理解しよう

継承(Inheritance)は、オブジェクト指向プログラミング(OOP)の中心的な概念の一つです。

一言で言うと 「既にあるクラスの機能や性質を引き継いで、新しいクラスを作る仕組み」です。

プログラミング初心者の方には難しく聞こえるかもしれませんが、実は私たちの身の回りにも同じような考え方がたくさんあります。

日常生活で理解する継承の考え方

例1:生物の分類

動物(最も基本的な分類)

  • 共通の特徴:生きている、動ける、食べる、呼吸する

哺乳類(動物の特徴を引き継ぐ)

  • 動物の特徴を全て持っている
  • さらに追加の特徴:体温を保つ、母乳で子を育てる、毛がある

犬(哺乳類の特徴を引き継ぐ)

  • 哺乳類の特徴を全て持っている
  • さらに独自の特徴:吠える、尻尾を振る、嗅覚が鋭い

このように、上位の特徴を受け継ぎながら、さらに独自の特徴を追加していく。これが継承の基本的な考え方です。

例2:家電製品

電化製品(基本カテゴリー)

  • 共通機能:電源ON/OFF、電力を使う、コンセントに繋ぐ

テレビ(電化製品の機能を引き継ぐ)

  • 電化製品の機能を全て持つ
  • 追加機能:映像を映す、音を出す、チャンネルを変える

スマートテレビ(テレビの機能を引き継ぐ)

  • テレビの機能を全て持つ
  • 追加機能:インターネットに接続、アプリを使う、動画配信を見る

このように階層的に機能が追加されていく仕組みが、プログラミングの継承なのです。

なぜ継承が必要なの?

プログラミングでは、似たような機能を持つクラスをたくさん作ることがあります。

継承を使わない場合の問題

// 犬のクラス
class Dog {
    constructor(name) {
        this.name = name;
    }
    eat() { console.log('食事をする'); }
    sleep() { console.log('眠る'); }
    bark() { console.log('ワンワン'); }
}

// 猫のクラス
class Cat {
    constructor(name) {
        this.name = name;
    }
    eat() { console.log('食事をする'); }  // 重複!
    sleep() { console.log('眠る'); }     // 重複!
    meow() { console.log('ニャー'); }
}

// 鳥のクラス
class Bird {
    constructor(name) {
        this.name = name;
    }
    eat() { console.log('食事をする'); }  // 重複!
    sleep() { console.log('眠る'); }     // 重複!
    fly() { console.log('飛ぶ'); }
}

全てのクラスで eat()sleep() を何度も書いています。これは非効率ですね。

継承を使った解決方法

// 親クラス:共通機能をまとめる
class Animal {
    constructor(name) {
        this.name = name;
    }
    eat() { console.log(`${this.name}が食事をする`); }
    sleep() { console.log(`${this.name}が眠る`); }
}

// 子クラス:独自の機能だけを追加
class Dog extends Animal {
    bark() { console.log('ワンワン'); }
}

class Cat extends Animal {
    meow() { console.log('ニャー'); }
}

class Bird extends Animal {
    fly() { console.log('飛ぶ'); }
}

// 使い方
const myDog = new Dog('ポチ');
myDog.eat();   // ポチが食事をする(親から継承)
myDog.sleep(); // ポチが眠る(親から継承)
myDog.bark();  // ワンワン(独自のメソッド)

共通部分は一度だけ書けばOK。コードがすっきりしました!

継承の基本用語を覚えよう

プログラミングの継承を理解するために、重要な用語を押さえましょう。

親クラス(スーパークラス、基底クラス)

継承元となるクラスのこと。共通の機能をまとめたクラスです。

別名

  • スーパークラス(Superclass)
  • 基底クラス(Base class)
  • 基本クラス

子クラス(サブクラス、派生クラス)

親クラスを継承して作られるクラス。親の機能を引き継ぎ、独自の機能を追加できます。

別名

  • サブクラス(Subclass)
  • 派生クラス(Derived class)

extends キーワード

「〜を継承する」という意味のキーワード。多くの言語で使われます。

class Dog extends Animal {
    // DogはAnimalを継承する
}

super キーワード

親クラスのメソッドやコンストラクタを呼び出すためのキーワード。

class Dog extends Animal {
    constructor(name, breed) {
        super(name);  // 親のコンストラクタを呼ぶ
        this.breed = breed;
    }
}

オーバーライド(上書き)

親クラスのメソッドを、子クラスで書き換えること。

class Animal {
    speak() {
        console.log('何か音を出す');
    }
}

class Dog extends Animal {
    speak() {  // 親のメソッドを上書き
        console.log('ワンワン');
    }
}

実践的なコード例で理解を深めよう

例1:従業員管理システム

// 親クラス:従業員の共通情報
class Employee {
    constructor(name, id, baseSalary) {
        this.name = name;
        this.id = id;
        this.baseSalary = baseSalary;
    }
    
    displayInfo() {
        console.log(`名前: ${this.name}`);
        console.log(`社員ID: ${this.id}`);
    }
    
    calculateSalary() {
        return this.baseSalary;
    }
}

// 子クラス1:正社員
class FullTimeEmployee extends Employee {
    constructor(name, id, baseSalary, bonus) {
        super(name, id, baseSalary);
        this.bonus = bonus;
    }
    
    // 給与計算をオーバーライド
    calculateSalary() {
        return this.baseSalary + this.bonus;
    }
}

// 子クラス2:契約社員
class ContractEmployee extends Employee {
    constructor(name, id, hourlyRate, hoursWorked) {
        super(name, id, 0);
        this.hourlyRate = hourlyRate;
        this.hoursWorked = hoursWorked;
    }
    
    // 給与計算をオーバーライド
    calculateSalary() {
        return this.hourlyRate * this.hoursWorked;
    }
}

// 使い方
const fullTimer = new FullTimeEmployee('田中太郎', 1, 300000, 50000);
console.log(fullTimer.calculateSalary());  // 350000

const contractor = new ContractEmployee('佐藤花子', 2, 2000, 160);
console.log(contractor.calculateSalary());  // 320000

例2:図形の面積計算

// 親クラス:図形の共通機能
class Shape {
    constructor(color) {
        this.color = color;
    }
    
    displayColor() {
        console.log(`色: ${this.color}`);
    }
    
    // 子クラスで必ず実装してもらうメソッド
    calculateArea() {
        throw new Error('このメソッドは子クラスで実装してください');
    }
}

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

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

// 使い方
const circle = new Circle('赤', 5);
console.log(circle.calculateArea());  // 78.54...

const rectangle = new Rectangle('青', 10, 20);
console.log(rectangle.calculateArea());  // 200

継承のメリット:なぜ使うべきなのか

メリット1:コードの重複を減らせる

共通の機能を親クラスに一度だけ書けば、複数の子クラスで使えます。

数字で見る効果

  • 継承なし:3つのクラスで同じコードを3回書く → 100行 × 3 = 300行
  • 継承あり:親クラス1つ + 子クラス3つ → 100行 + (30行 × 3) = 190行

約36%のコード削減!

メリット2:保守性が向上する

共通機能を修正するとき、親クラスだけを変更すれば、全ての子クラスに反映されます。

具体例

// 親クラスの eat メソッドを修正
class Animal {
    eat() {
        console.log('食事を始めます'); // 変更
        console.log('もぐもぐ');       // 追加
    }
}

// 全ての子クラス(Dog、Cat、Bird)に自動的に反映される!

メリット3:現実世界の関係を表現できる

「is-a関係」(〜は〜である)を自然に表現できます。

  • 犬は動物である(Dog is an Animal)
  • 正社員は従業員である(FullTimeEmployee is an Employee)
  • 円は図形である(Circle is a Shape)

この関係性をコードで表現することで、プログラムが理解しやすくなります。

メリット4:拡張性が高い

新しい機能を追加したいとき、既存のコードを変更せずに、新しい子クラスを作るだけでOK。

// 新しい動物を追加するのも簡単
class Rabbit extends Animal {
    hop() {
        console.log('ぴょんぴょん跳ねる');
    }
}

// 既存のAnimal、Dog、Catクラスには一切触れない

メリット5:ポリモーフィズムが実現できる

異なる種類のオブジェクトを、統一的に扱えます。

const animals = [
    new Dog('ポチ'),
    new Cat('タマ'),
    new Bird('ピー')
];

// 全ての動物に対して同じメソッドを呼べる
animals.forEach(animal => {
    animal.eat();   // それぞれ親クラスのメソッドが実行される
    animal.sleep(); // 種類を気にせず統一的に扱える
});

継承のデメリットと注意点

継承は便利ですが、使いすぎると問題が起きることもあります。

デメリット1:親子の結びつきが強くなる(密結合)

親クラスを変更すると、意図せず子クラスが壊れることがあります。

class Parent {
    method1() {
        console.log('処理A');
    }
}

class Child extends Parent {
    method2() {
        this.method1(); // 親のメソッドに依存
    }
}

// 親クラスを変更すると...
class Parent {
    method1() {
        throw new Error('廃止されました'); // 子クラスが壊れる!
    }
}

デメリット2:継承階層が深くなりすぎる

階層が深くなると、コードの全体像が把握しづらくなります。

生物
 └ 動物
    └ 脊椎動物
       └ 哺乳類
          └ 肉食動物
             └ イヌ科
                └ オオカミ属
                   └ イヌ

このように深すぎる階層は避けるべきです。一般的には、3〜4階層程度が適切とされています。

デメリット3:不要な機能まで継承してしまう

親クラスの全てのメソッドが子クラスに引き継がれるため、不要な機能まで持つことになります。

class Bird extends Animal {
    // Animalクラスの swim() メソッドも継承してしまう
    // でも全ての鳥が泳げるわけではない...
}

デメリット4:柔軟性に欠ける

一度継承関係を決めると、後から変更するのが難しくなります。

継承 vs コンポジション:どちらを選ぶ?

現代のプログラミングでは、「継承よりコンポジション(委譲)を優先すべき」という考え方が主流です。

継承を使うべき場合(is-a関係)

「〜は〜である」という関係が明確な場合に使います。

// 犬は動物である
class Dog extends Animal {
    // 継承が適切
}

コンポジションを使うべき場合(has-a関係)

「〜は〜を持つ」という関係の場合に使います。

// 車はエンジンを持つ(車はエンジンではない)
class Car {
    constructor() {
        this.engine = new Engine();  // コンポジションが適切
        this.wheels = new Wheels();
    }
    
    start() {
        this.engine.start();
    }
}

判断基準の例

関係適切な方法理由
犬と動物継承犬は動物である(is-a)
車とエンジンコンポジション車はエンジンを持つ(has-a)
正社員と従業員継承正社員は従業員である(is-a)
人と住所コンポジション人は住所を持つ(has-a)

アクセス修飾子と継承の関係

継承では、どのメンバー(プロパティやメソッド)を子クラスに公開するかを制御できます。

public:どこからでもアクセス可能

class Parent {
    publicMethod() {
        // 子クラスからも外部からもアクセスできる
    }
}

protected:子クラスからアクセス可能

class Parent {
    protected void protectedMethod() {
        // 子クラスからはアクセスできる
        // 外部からはアクセスできない
    }
}

private:親クラス内部のみ

class Parent {
    #privateMethod() {
        // 子クラスからもアクセスできない
        // 親クラス内部でのみ使用
    }
}

実用例

class BankAccount {
    #balance = 0;  // private: 外部から直接変更できない
    
    deposit(amount) {  // public: 誰でも使える
        this.#balance += amount;
    }
    
    getBalance() {  // public: 残高を確認できる
        return this.#balance;
    }
}

class SavingsAccount extends BankAccount {
    addInterest() {
        // this.#balance にはアクセスできない
        // getBalance() を使って取得する
        const balance = this.getBalance();
        this.deposit(balance * 0.01);  // 1%の利息
    }
}

主要プログラミング言語での継承

JavaScript / TypeScript

class Parent {
    constructor(name) {
        this.name = name;
    }
}

class Child extends Parent {
    constructor(name, age) {
        super(name);
        this.age = age;
    }
}

Java

public class Parent {
    protected String name;
    
    public Parent(String name) {
        this.name = name;
    }
}

public class Child extends Parent {
    private int age;
    
    public Child(String name, int age) {
        super(name);
        this.age = age;
    }
}

Python

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

class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age

C#

public class Parent {
    protected string name;
    
    public Parent(string name) {
        this.name = name;
    }
}

public class Child : Parent {
    private int age;
    
    public Child(string name, int age) : base(name) {
        this.age = age;
    }
}

よくある質問(FAQ)

Q1:多重継承はできますか?

A:言語によります

  • できる言語:C++、Python
  • できない言語:Java、C#、JavaScript

多くの言語では、複雑さを避けるために単一継承のみをサポートしています。代わりにインターフェースを使います。

Q2:継承の階層はどのくらいが適切?

A:3〜4階層程度が推奨されます

深すぎる継承は理解が難しくなります。5階層を超える場合は、設計を見直すことをおすすめします。

Q3:継承とインターフェースの違いは?

A:実装を持つかどうかが主な違いです

  • 継承:実装(メソッドの中身)を引き継ぐ
  • インターフェース:契約(何を実装すべきか)だけを定義

Q4:private なメンバーは継承されますか?

A:継承されますが、アクセスできません

子クラスにも存在しますが、直接アクセスはできません。親クラスの public または protected メソッドを通してのみ利用できます。

まとめ:継承を正しく使ってコードの質を高めよう

継承は、オブジェクト指向プログラミングの重要な概念です。

重要ポイントまとめ

  • 継承は既存クラスの機能を引き継ぐ仕組み
  • コードの重複を減らし、保守性を高める
  • is-a関係(〜は〜である)を表現する
  • オーバーライドで動作をカスタマイズできる
  • 深すぎる継承階層は避ける
  • 現代では「継承よりコンポジション」が推奨される
  • 適切な場面で使うことが重要

初心者へのアドバイス

  1. まずはシンプルな親子関係から始める
  2. 実際にコードを書いて試してみる
  3. 本当に継承が必要か、常に考える
  4. 最初は2〜3階層程度に抑える
  5. 慣れてきたらコンポジションも学ぶ

継承を適切に使うことで、より保守しやすく、拡張性の高いプログラムを作ることができます。ただし、万能な解決策ではないので、状況に応じて他の設計手法と組み合わせて使いましょう。

プログラミングの独学におすすめ
プログラミング言語の人気オンラインコース
独学でプログラミングを学習している方で、エラーなどが発生して効率よく勉強ができないと悩む方は多いはず。Udemyは、プロの講師が動画で実際のプログラムを動かしながら教えてくれるオンライン講座です。講座の価格は、セール期間中には専門書籍を1冊買うよりも安く済むことが多いです。新しく学びたいプログラミング言語がある方は、ぜひUdemyでオンライン講座を探してみてください。
目次