閉じる

SOLID原則とは?5つの意味と覚え方をやさしく解説

プログラムをあとから直したり足したりしても壊れにくくする考え方が「SOLID原則」です。

SOLIDは5つの短いルールの頭文字で、初心者でも少しずつ実践しながら身につけられる実用的な指針です

この記事では意味と覚え方をやさしく解説します。

SOLID原則とは

プログラミング初心者向けのやさしい定義

SOLID原則は、クラスやメソッドの作り方を迷わないように導いてくれる5つの設計ルールです

難しい数学や専門用語を覚えなくても、日常の例えに置きかえると理解しやすく、コードが大きくなっても見通しを保てます。

役割を1つにすること変えずに足す工夫など、現場でよく起きる問題に対するシンプルな解決策のセットだと思って大丈夫です。

オブジェクト指向プログラミングで役立つ理由

オブジェクト指向は「モノの責任」と「モノ同士の関係」を考えるので、SOLID原則ととても相性が良いです

なぜ役立つかを短くまとめます。

  • 変更に強いコードになる: 要件が変わっても影響範囲が小さく、安心して直せます。
  • 再利用しやすくなる: クラスの役割がはっきりし、別の場面でもそのまま使えます
  • テストしやすい: 部品が小さく分かれているので、個別に動作確認しやすいです。

5つの原則の全体像

SOLIDはSingle、Open-Closed、Liskov、Interface、Dependencyの5単語の頭文字です

まずは全体を地図のように眺めましょう。

文字原則(英語)日本語の意味1行フレーズ日常イメージ
SSingle Responsibility Principle単一責任の原則1つのクラスは1つの理由だけで変わる役割が1つの引き出し
OOpen-Closed Principle開放/閉鎖の原則既存コードは変えずに機能を足すコンセントにアダプタを足す
LLiskov Substitution Principle置換の原則親の代わりに子を使っても期待が変わらない同じ規格の電池の入れ替え
IInterface Segregation Principleインターフェース分離の原則使わないメソッドは押しつけない必要なボタンだけのリモコン
DDependency Inversion Principle依存関係逆転の原則具体ではなく抽象に依存する規格に合わせて機器をつなぐ

「開放/閉鎖」の言い回しはややこしいですが「拡張に開いている、修正に閉じている」と覚えると楽です

5つの意味(SOLID)をやさしく解説

S

どんな考え方

単一責任の原則は「1つのクラスは1つのことに集中する」考え方です

そのクラスが変更される理由が1つだけなら、修正するときに他へ波及しにくくなります。

ありがちな失敗

表示も保存も通知も1つのクラスが全部やると、どれか1つを直すたびに他も壊れやすくなります

役割ごとに分けると見通しが良くなります。

ミニ例(Python)

レポートの作成と出力を分けるだけで責任がクリアになります

Python
class Report:
    def __init__(self, data):
        self.data = data

    def summarize(self):
        return f"件数: {len(self.data)}"  # まとめる責任

class ReportPrinter:
    def print(self, report: Report):
        print(report.summarize())  # 出力する責任

O

どんな考え方

開放/閉鎖の原則は「振る舞いは足せるが、既存コードはできるだけ触らない」ように設計する考え方です

足しやすく直しにくい設計が理想です。

ありがちな失敗

新しい種類が増えるたびにif文を追加して既存の関数を書き換えるのは、壊すリスクを毎回増やす行為です

共通の約束を作り、クラスを足して対応しましょう。

ミニ例(Python)

図形の面積合計を計算するクラスは、図形の追加に対して変更不要にできます

Python
import math

class Shape:
    def area(self) -> float:
        raise NotImplementedError()

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w, self.h = w, h
    def area(self):
        return self.w * self.h

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return math.pi * self.r * self.r

class AreaCalculator:
    def total(self, shapes: list[Shape]) -> float:
        return sum(s.area() for s in shapes)  # ここは固定

# Triangleを足してもAreaCalculatorは変更不要

L

どんな考え方

置換の原則は「親クラスの代わりに子クラスを置いても、使う側の期待が変わらない」ことを保証する考え方です

使う側が驚かないことが大切です。

ありがちな失敗

「鳥は飛ぶ」という親に対して「ペンギンは飛ばない」と例外を投げる子を作ると、使う側は困ります

最初の約束自体を分けて考えるのがコツです。

ミニ例(Python)

飛べる鳥と飛べない鳥を別の約束に分けると、使う側の期待がぶれません

Python
class Bird:
    def walk(self): pass

class FlyingBird(Bird):
    def fly(self): pass

class Sparrow(FlyingBird):
    def fly(self): print("バサバサ")

class Penguin(Bird):
    def swim(self): print("スイスイ")

def travel_to(tree_top: FlyingBird):
    tree_top.fly()  # FlyingBirdだけ受け取れば安全

I

どんな考え方

インターフェース分離の原則は「使わないメソッドを押しつけない」ことです

小さく分かれた約束により、必要な分だけ実装します。

ありがちな失敗

印刷・スキャン・FAXをひとまとめにした太った約束を作ると、印刷だけしたい機械もスキャンやFAXの空実装が必要になってしまいます

ミニ例(Python)

役割ごとに小さな約束を作ると、実装がシンプルになります

Python
class Printer:
    def print(self, text: str): pass

class Scanner:
    def scan(self) -> str: pass

class SimplePrinter(Printer):
    def print(self, text: str):
        print(text)

class MultiFunctionMachine(Printer, Scanner):
    def print(self, text: str): print(text)
    def scan(self) -> str: return "scanned"

D

どんな考え方

依存関係逆転の原則は「具体的なクラスではなく、抽象的な約束に依存する」設計にすることです

これにより差し替えが簡単になります。

ありがちな失敗

支払い処理が特定サービスのクラスを直接作って使っていると、別サービスに切り替えるだけで大量の修正が必要になります

抽象に合わせて作れば差し替えは注入だけで済みます。

ミニ例(Python)

支払いサービスは「支払えるもの」という約束に依存し、具体の実装は外から渡します

Python
class PaymentGateway:
    def pay(self, amount: int): raise NotImplementedError()

class StripeGateway(PaymentGateway):
    def pay(self, amount: int): print(f"Stripeで{amount}円")

class PayPalGateway(PaymentGateway):
    def pay(self, amount: int): print(f"PayPalで{amount}円")

class CheckoutService:
    def __init__(self, gateway: PaymentGateway):
        self.gateway = gateway  # 抽象に依存
    def checkout(self, amount: int):
        self.gateway.pay(amount)

# 差し替えはここだけ
service = CheckoutService(StripeGateway())
service.checkout(1200)

SOLID原則の覚え方

SOLIDの語呂とイメージで暗記

「SOLIDは固い」という語感を利用して「壊れにくい設計」と結びつけると覚えやすいです

Sは引き出し1個、Oはコンセントとアダプタ、Lは電池の規格、Iは必要なボタンだけのリモコン、Dは規格のプラグを思い出すと記憶が安定します。

Oの「開放/閉鎖」は「拡張はOK、修正はNG」と口に出して覚えましょう

1行フレーズで覚えるコツ

短いフレーズを繰り返し唱えると実装中にすぐ思い出せます

  • S: 変わる理由は1つだけ
  • O: 足す、でも変えない
  • L: 親の約束は守る
  • I: 使わない約束は渡さない
  • D: 抽象に寄りかかる

かんたんなコード例でイメージ

「注入で差し替える」を体験すると、OとDが一気に腹落ちします

以下は出力先を差し替えられる挨拶クラスです。

Python
class Output:
    def write(self, text: str): raise NotImplementedError()

class ConsoleOutput(Output):
    def write(self, text: str): print(text)

class Greeter:
    # D: Outputという抽象に依存
    def __init__(self, out: Output):
        self.out = out
    # S: Greeterは挨拶文を作って出力するだけ
    def hello(self, name: str):
        self.out.write(f"Hello, {name}!")
    # O: 新しい出力先を追加してもGreeterは変更不要

g = Greeter(ConsoleOutput())
g.hello("SOLID")

ファイル出力やWeb出力を新クラスで足せば、Greeterはそのまま使えます

初心者の学び方と練習ステップ

小さなプログラムでSOLIDを試す

まずは小さな題材で各原則を1つずつ試すのが上達の近道です

例えば、メモ帳アプリで「保存」「表示」「検索」を別クラスに分けてSを体験し、検索方法を足せるようにOを試す、といった具合に進めます。

規模が小さいほど、変更の効果が見えやすく学びやすいです。

リファクタリングで設計の違いを知る

最初はあえてベタ書きで作り、あとからSOLIDの観点で直すと、良い設計の価値が体感できます

直す前後で何が変わったかを表で比べると理解が深まります。

観点直す前直した後
責任の数1つのクラスに多い役割ごとに分離
変更の容易さ影響範囲が読めない変更範囲が小さい
テストのしやすさまとめてしか試せない部品ごとに試せる

よくあるつまずきと回避法

「原則を守ること」自体が目的化すると複雑になりがちです

以下の罠に注意しましょう。

  • 分けすぎ問題: 小さなクラスを作りすぎて関係が複雑になることがあります。最初は大雑把に作り、必要になったら分けます。
  • 早すぎる抽象化: まだ1種類しかないのに抽象で包むと分かりにくくなります。似たものが2つ出てから抽象化しても間に合います。
  • if地獄の温存: Oを意識せずifを増やし続けると破綻します。共通の約束を作って足す方向へ切り替えましょう。
  • テスト不足: 小さく分けたなら小さくテストしましょう。テストしやすさは設計の良さのサインです。

まとめ

SOLID原則は「壊れにくく、直しやすく、足しやすい」コードに近づくための5つの実践ルールです

Sで役割をはっきりさせ、Oで変更を最小化し、Lで驚きをなくし、Iで約束を小さく保ち、Dで差し替えを容易にします。

まずは小さな題材で1つずつ試し、良い実感を積み重ねていきましょう

慣れてくると、日常の設計判断の多くがこの5つに自然と収まることに気づくはずです。

この記事を書いた人
エーテリア編集部
エーテリア編集部

このサイトでは、プログラミングをこれから学びたい初心者の方に向けて記事を書いています。 基本的な用語や環境構築の手順から、実際に手を動かして学べるサンプルコードまで、わかりやすく整理することを心がけています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!