カテゴリ: Javaのオブジェクト指向 更新日: 2025/05/15

オブジェクト指向の設計原則(SOLID の基本を知ろう)

118
オブジェクト指向の設計原則(SOLID の基本を知ろう)

新人と先輩の会話形式で理解しよう

新人

「先輩、Javaの勉強をしてたら“SOLID原則”っていう言葉が出てきたんですが、なんだか難しそうで…」

先輩

「確かに最初はちょっと難しく感じるけど、SOLID原則はJavaのオブジェクト指向をうまく使って、プログラムをきれいに整理するためのルールなんだ。」

新人

「ルールって聞くとちょっと構えちゃいますね…でも、覚えておいた方がいいんですよね?」

先輩

「もちろん!特にJavaのようなオブジェクト指向言語では、SOLID原則を知っておくとクラス設計がぐっと上達するよ。まずはやさしく解説するから安心してね。」

1. オブジェクト指向の設計原則とは?(やさしく概要を解説)

1. オブジェクト指向の設計原則とは?(やさしく概要を解説)
1. オブジェクト指向の設計原則とは?(やさしく概要を解説)

Javaを学んでいくと、だんだんと「プログラムが大きくなる」「いろんなクラスが増える」「複雑になってくる」という場面に出会います。そんなときに、きれいに整理されたコードを書くために役立つのが「オブジェクト指向の設計原則」です。

これは、「こういうふうにクラスやメソッドを作ると、あとで修正や追加がしやすいよ」というルールのようなものです。特にJavaのようにオブジェクト指向の考え方が基本になっている言語では、こうした設計原則を意識することで、保守性の高いプログラムを作ることができます。

たとえば、「ひとつのクラスにたくさんの役割を持たせすぎない」とか、「将来の変更に強いようにクラスを作る」といった考え方があります。これを言い換えると、「他の人が読んでもわかりやすく、自分であとから直しやすいコード」にするということです。

オブジェクト指向の設計原則は、初心者のうちはすべてを覚えようとする必要はありません。でも、少しずつ理解していくと、クラスやメソッドをどう書けばいいかのヒントになります。そこで出てくるのが「SOLID原則」です。

2. SOLIDの意味と5つの原則の名前を紹介(意味はまだ詳しく書かない)

2. SOLIDの意味と5つの原則の名前を紹介(意味はまだ詳しく書かない)
2. SOLIDの意味と5つの原則の名前を紹介(意味はまだ詳しく書かない)

「SOLID(ソリッド)」という言葉は、オブジェクト指向の設計原則を覚えやすくするために作られた頭文字の集まりです。ひとつひとつの原則には、それぞれ大切な意味があります。

この5つの原則を守ることで、Javaのクラスやメソッドがすっきり整理され、あとから修正しやすくなったり、エラーが起こりにくくなったりします。

まずは名前だけを見ておきましょう。これが「SOLID」の中身です。

  • S:単一責任の原則(Single Responsibility Principle)
  • O:オープン・クローズドの原則(Open/Closed Principle)
  • L:リスコフの置換原則(Liskov Substitution Principle)
  • I:インターフェース分離の原則(Interface Segregation Principle)
  • D:依存性逆転の原則(Dependency Inversion Principle)

名前を見ると難しく感じるかもしれませんが、どれも「わかりやすくて使いやすいクラスにするための考え方」です。実際には、これらの原則を少しずつ意識しながらJavaのコードを書いていくことで、自然と理解できるようになります。

たとえば、「ひとつのクラスはひとつの役割だけにする」「クラスを直接書き換えるのではなく、広げるように追加する」といったことが、SOLIDの考え方には含まれています。

次の章では、この中からひとつずつ取り上げて、具体的な意味やJavaでの使い方をやさしく解説していきます。まずは最初の「単一責任の原則」からスタートしましょう。

3. 単一責任の原則(SRP)とは?初心者にもわかる例で説明

3. 単一責任の原則(SRP)とは?初心者にもわかる例で説明
3. 単一責任の原則(SRP)とは?初心者にもわかる例で説明

Javaのオブジェクト指向設計における「単一責任の原則(SRP)」とは、「ひとつのクラスはひとつの目的だけを持つようにしよう」という考え方です。英語では「Single Responsibility Principle」といいます。

たとえば「家電製品」というクラスがあったとして、その中に「テレビの機能」も「冷蔵庫の機能」も「電子レンジの機能」も全部入っていたら、どうでしょうか?とてもわかりにくくなってしまいますよね。

これと同じように、Javaでクラスを作るときも「このクラスは何をするのか?」を明確にして、ひとつの役割に集中させることで、プログラムが整理されて見やすくなります。

では、具体的なJavaの例を見てみましょう。たとえば、生徒の情報を管理するクラスがあったとします。


// 単一責任の原則を守っていない例
public class Student {
  String name;
  int score;

  void saveToDatabase() {
    // データベースに保存する処理
  }

  void printInfo() {
    System.out.println(name + "さんの点数は" + score + "点です。");
  }
}

このコードでは、生徒の情報だけでなく、データベースに保存する処理までひとつのクラスに含まれています。これだと「情報の管理」と「データ保存」という2つの責任を持ってしまっています。

これを単一責任の原則に沿って、役割ごとにクラスを分けてみましょう。


// 単一責任の原則を守った例
public class Student {
  String name;
  int score;

  void printInfo() {
    System.out.println(name + "さんの点数は" + score + "点です。");
  }
}

public class StudentDatabase {
  void save(Student student) {
    // データベース保存処理
  }
}

このように、「データを保存するクラス(StudentDatabase)」と「生徒の情報を表すクラス(Student)」を分けることで、それぞれのクラスが単純になり、変更や修正がしやすくなります。

これが単一責任の原則のポイントです。初心者の方でも「クラスはできるだけシンプルにして、ひとつの目的に集中させる」と覚えておくと、設計で迷いにくくなります。

Javaをこれから始める人や、 オブジェクト指向の考え方を基礎から理解したい人には、 定番の入門書がこちらです。

スッキリわかるJava入門 第4版をAmazonで見る

※ Amazon広告リンク

4. オープン・クローズドの原則(OCP)とは?考え方とやさしい例

4. オープン・クローズドの原則(OCP)とは?考え方とやさしい例
4. オープン・クローズドの原則(OCP)とは?考え方とやさしい例

次に紹介するのは「オープン・クローズドの原則(OCP)」です。これは、「クラスは拡張に対してはオープン(開いていて)、修正に対してはクローズド(閉じている)ように設計しよう」という考え方です。

ちょっと難しく感じるかもしれませんが、簡単に言えば「すでにあるクラスの中身をいじらずに、新しい機能を追加できるようにしよう」ということです。

たとえば、お店の割引計算をするプログラムがあったとしましょう。最初は「通常会員」だけ対応していたとします。


// 拡張しにくい例
public class DiscountService {
  int getDiscount(String type) {
    if ("normal".equals(type)) {
      return 5;
    } else if ("premium".equals(type)) {
      return 10;
    } else {
      return 0;
    }
  }
}

このような書き方をしてしまうと、新しい会員タイプ(たとえば「ゴールド会員」)を追加したいときには、毎回このDiscountServiceの中身を修正しなければなりません。

オープン・クローズドの原則では、このような修正ではなく「新しくクラスを追加するだけで対応できる」ように考えます。

そのために「インターフェース」を使って、拡張しやすい仕組みにします。


// 拡張しやすい例(OCPを意識)
public interface DiscountPolicy {
  int getDiscount();
}

public class NormalMember implements DiscountPolicy {
  public int getDiscount() {
    return 5;
  }
}

public class PremiumMember implements DiscountPolicy {
  public int getDiscount() {
    return 10;
  }
}

public class DiscountService {
  public void showDiscount(DiscountPolicy policy) {
    System.out.println("割引は " + policy.getDiscount() + "% です。");
  }
}

このようにしておけば、新しい割引ルールを追加したいときは、新しいクラス(たとえばGoldMember)を作るだけで済みます。元のDiscountServiceクラスは何も変更しなくていいのです。

これが「拡張にオープン、修正にクローズド」という設計の意味です。

Javaではこのような考え方がとても大切で、特にプログラムが大きくなるほど、修正による影響を最小限に抑えることが重要になります。

初心者のうちは難しく感じるかもしれませんが、「新しい機能は別のクラスで作れるようにする」と意識するだけでも、設計がだいぶ変わってきます。

5. リスコフの置換原則(LSP)とは?わかりやすく解説

5. リスコフの置換原則(LSP)とは?わかりやすく解説
5. リスコフの置換原則(LSP)とは?わかりやすく解説

リスコフの置換原則(LSP)は、Javaのようなオブジェクト指向言語でとても大切な考え方のひとつです。英語では「Liskov Substitution Principle」といいます。

内容を簡単に言うと、「親クラスの代わりに子クラスを使っても、プログラムの動きがおかしくならないようにしよう」ということです。

たとえば「動物」という親クラスがあり、「犬」や「猫」などの子クラスがあるとします。動物が「鳴く」というメソッドを持っていれば、犬や猫も同じように「鳴く」メソッドを持つはずです。

このとき、プログラムの中で「動物」として使っていても、犬や猫が代わりに来てもちゃんと動くようにするのがリスコフの置換原則です。


public class Animal {
  void speak() {
    System.out.println("動物が鳴きます。");
  }
}

public class Dog extends Animal {
  void speak() {
    System.out.println("ワンワン!");
  }
}

public class Cat extends Animal {
  void speak() {
    System.out.println("ニャーニャー!");
  }
}

このようにしておけば、プログラムの中で「Animal animal = new Dog();」と書いても、「animal.speak();」は正しく動きます。

もし子クラスが親クラスのルールを守らず、全然違う動きをしてしまったら、使う側は混乱してしまいます。だから「親の代わりに子を使っても大丈夫なようにする」というのが、この原則の目的です。

初心者向けに覚えておいてほしいのは、「子クラスは親クラスの期待を裏切らないように作る」ということです。それだけでも、オブジェクト指向の設計がとても安定してきます。

6. インターフェース分離の原則(ISP)と依存性逆転の原則(DIP)の概要

6. インターフェース分離の原則(ISP)と依存性逆転の原則(DIP)の概要
6. インターフェース分離の原則(ISP)と依存性逆転の原則(DIP)の概要

SOLID原則の最後の2つは、少し難しそうな名前ですが、やさしい考え方です。まずは「インターフェース分離の原則(ISP)」から説明します。

この原則は「使わない機能を押しつけない」という考え方です。たとえば、家電のリモコンで「テレビ」も「エアコン」も同じボタンを使うようになっていたら、使わないボタンがたくさんあって困りますよね。

Javaでも、インターフェースを使うときは「必要なものだけをまとめたインターフェース」を作るようにします。


public interface Printer {
  void print();
}

public interface Scanner {
  void scan();
}

このように分けておけば、「印刷するだけのクラス」にはPrinterだけを使えばよく、「スキャンだけするクラス」にはScannerだけを使えます。

次に「依存性逆転の原則(DIP)」ですが、これは「クラスは具体的なもの(詳細)に依存せず、抽象的なもの(インターフェースなど)に依存しよう」という考えです。

たとえば、クラスの中で「直接 new して他のクラスを使う」と、それに強く依存してしまいます。でも、インターフェースを使えば、どんな実装でも対応できるようになります。


public interface MessageService {
  void sendMessage();
}

public class EmailService implements MessageService {
  public void sendMessage() {
    System.out.println("メールを送信しました。");
  }
}

public class Notification {
  MessageService service;

  public Notification(MessageService service) {
    this.service = service;
  }

  public void notifyUser() {
    service.sendMessage();
  }
}

このコードのように、インターフェースを使えば「メール」でも「LINE」でも、あとから自由に変更できます。

これが依存性逆転の原則のポイントです。「具体的なクラス」ではなく「インターフェース」に注目することで、Javaのプログラムはもっと柔軟になります。

7. まとめ(初心者向けに振り返りと励まし)

7. まとめ(初心者向けに振り返りと励まし)
7. まとめ(初心者向けに振り返りと励まし)

ここまでJavaのオブジェクト指向設計におけるSOLID原則を、初心者向けにやさしく解説してきました。はじめはむずかしく感じるかもしれませんが、少しずつ理解すれば大丈夫です。

クラスを作るときに「目的をひとつに絞る」「あとから追加しやすくする」「親クラスとの関係を大切にする」「必要な機能だけ分ける」「具体的なクラスではなく抽象的なものを使う」など、ひとつひとつの原則はシンプルな考え方です。

これらを意識することで、Javaのコードはとてもきれいで整理されたものになります。そして、保守性が高く、エラーが少なくなり、他の人とも協力しやすくなります。

最初は「難しい」「ややこしい」と感じるかもしれませんが、あせらず、ゆっくりとコードを書きながら学んでいけばきっと身につきます。

Pleiadesを使ってJavaを学ぶみなさんが、これからも楽しくプログラミングを続けていけるよう、心から応援しています!

Javaのオブジェクト指向の一覧へ
新着記事
FlutterのMVP・MVVMアーキテクチャの違いと使い分け
FlutterのMVP・MVVMアーキテクチャの違いと使い分けを初心者向けに解説!
オニオンアーキテクチャの基本とFlutterでの適用例
オニオンアーキテクチャの基本とFlutterでの適用例を初心者向けに解説
クリーンアーキテクチャとは?Flutterでの導入メリット
クリーンアーキテクチャとは?Flutterでの導入メリットをやさしく解説
【AWS】RDS for Oracleの特徴・できないこと・バージョン・料金まとめ
【AWS】RDS for Oracleの特徴・できないこと・バージョン・料金を初心者向けに徹底解説
人気記事
インスタンスタイプの料金比較と最適な選び方(最新2025年版)
AWSのインスタンスタイプの料金比較と最適な選び方【2025年最新版】
【AWS】VPCの料金体系まとめ!無料枠・通信費・各種サービスごとの料金を徹底解説
【AWS】VPCの料金体系まとめ!無料枠・通信費・各種サービスごとの料金を徹底解説
【AWS】VPCエンドポイントとは?種類・使い方・S3連携まで完全解説
【AWS】VPCエンドポイントとは?種類・使い方・S3連携まで完全解説
【AWS】s3 cpコマンド完全ガイド!基本・recursive・exclude/includeも解説
【AWS】s3 cpコマンド完全ガイド!基本・recursive・exclude/includeも解説

🔌 USBポート不足を解消

Type-C 1本で拡張。
開発・作業環境を一気に快適に

UGREEN USB-Cハブを見る

※ Amazon広告リンク