【ITニュース解説】Software Design Principles: A Practical Example in Python
2025年09月15日に「Dev.to」が公開したITニュース「Software Design Principles: A Practical Example in Python」について初心者にもわかりやすく解説しています。
ITニュース概要
良いソフトウェアは保守・拡張が容易だ。この記事では、そのための設計指針「SOLID原則」を、Pythonでの通知システム開発を例に解説。コード例を通して、各原則がどのように適用され、柔軟なシステムが作れるかを示している。
ITニュース解説
ソフトウェアを開発する際、単に動作するコードを書くだけでなく、将来の変更や拡張に強く、理解しやすいコードを書くことが非常に重要となる。これを助けるのが「ソフトウェア設計原則」と呼ばれる指針の集まりだ。これらの原則に従うことで、長く使われるシステムを作り、開発効率を向上させることができる。特に「SOLID」原則は、オブジェクト指向プログラミングにおける基本的な設計の考え方であり、多くのシステムで活用されている。今回は、このSOLID原則がどのように実際のシステム開発に役立つのかを、Pythonを使った通知システムの例を通して具体的に見ていこう。
まず、SOLID原則のそれぞれの内容を説明する。
一つ目は「単一責任の原則(SRP)」だ。これは「一つのクラスは、一つの変更理由しか持たないべきである」という考え方だ。クラスとは、データとそれを操作する機能をまとめた設計図のようなものだ。もし一つのクラスが複数の異なる役割(例えば、データの保存と表示の両方)を持っていると、データの保存方法が変わっても、表示方法が変わっても、そのクラスは変更が必要になる。それぞれの役割を独立したクラスに分けることで、一方の変更が他方に影響を与えるリスクを減らし、コードの理解やテストを容易にすることができる。
二つ目は「オープン・クローズドの原則(OCP)」だ。この原則は、「ソフトウェアの構成要素は、拡張に対しては開かれていなければならないが、変更に対しては閉じられていなければならない」と説く。つまり、新しい機能を追加するとき、既存のコードを変更するのではなく、新しいコードを追加することで機能を拡張できるように設計すべきだ、という意味だ。既存のコードを修正すると、予期せぬバグを引き起こす可能性があり、テストの手間も増える。既存のコードを安定させつつ、新しい機能を追加できる設計は、システムの長期的な安定性と開発効率に大きく貢献する。
三つ目は「リスコフの置換原則(LSP)」だ。これは「派生クラス(子クラス)は、基底クラス(親クラス)の代わりに使えるべきである」という原則だ。例えば、「動物」という基底クラスがあり、「犬」や「猫」がその派生クラスだとしよう。もし「動物」を受け取る関数があった場合、その関数は「犬」のオブジェクトを受け取っても、「猫」のオブジェクトを受け取っても、問題なく動作しなければならない。つまり、派生クラスは基底クラスが持つ特性や振る舞いを壊してはならず、基底クラスが約束する「契約」を完全に満たす必要がある。これにより、コードの柔軟性と再利用性が高まる。
四つ目は「インターフェース分離の原則(ISP)」だ。この原則は、「クライアント(クラスを利用する側)は、利用しないインターフェースに依存すべきではない」というものだ。インターフェースとは、クラスが提供する機能の「窓口」のようなものと考えると良い。大きなインターフェースを一つ作るのではなく、特定の機能を提供する小さなインターフェースに分割することで、利用者は自分が必要とする機能だけのインターフェースを選んで使うことができる。これにより、不要な機能に対する依存関係を避け、クラス間の結合度を低く保ち、システムの変更容易性を向上させる。
五つ目は「依存関係逆転の原則(DIP)」だ。この原則は、「上位レベルのモジュール(より抽象的な機能を提供する部分)は、下位レベルのモジュール(より具体的な機能を提供する部分)に依存すべきではなく、両方とも抽象(インターフェースや抽象クラス)に依存すべきである」という考え方だ。また、「抽象は具体的な実装に依存すべきではなく、具体的な実装が抽象に依存すべきである」とも言われる。つまり、特定の具体的な実装に直接依存するのではなく、共通の抽象的な「契約」に依存することで、モジュール間の結合度を劇的に低く保つことができる。これにより、特定の具体的な実装が変更されても、上位モジュールに影響を与えにくくなり、システムの柔軟性とテストのしやすさが向上する。
これらの原則を念頭に置き、Pythonで通知システムを設計する例を見ていこう。このシステムの目標は、メールやSMSといった現在の通知手段に加え、将来的にプッシュ通知やWebhooksなど新しい通知手段が追加されても、既存のコードを大幅に変更せずに対応できるようにすることだ。
まず、Pythonのabcモジュールを使ってNotifierという抽象クラスを定義する。抽象クラスとは、それ自体ではインスタンスを作成できず、他のクラスに継承されて具体的な機能を実装するための「ひな形」となるクラスのことだ。@abstractmethodデコレータが付いたsendメソッドは、このNotifierを継承するすべてのクラスが必ず実装しなければならない共通の「通知を送る」という振る舞いを定義している。これは、すべての通知方法が「メッセージと送り先を受け取って通知する」という共通の窓口を持つことを保証する。
次に、このNotifier抽象クラスを継承して、EmailNotifierとSMSNotifierという具体的な通知クラスを作成する。それぞれのクラスは、Notifierで定義されたsendメソッドを具体的に実装する。EmailNotifierはメール送信の処理を、SMSNotifierはSMS送信の処理を行う。ここではprint文でその動作を模擬しているが、実際には外部のメール送信サービスやSMSプロバイダーのAPIを呼び出すことになる。
さらに、NotificationServiceという中心的な通知処理を行うクラスを作成する。このクラスのコンストラクタは、notifierという引数を一つ受け取るが、その型は具体的なEmailNotifierやSMSNotifierではなく、抽象的なNotifier型として指定されている。これは「依存性注入」と呼ばれる手法で、NotificationServiceが具体的な通知方法に直接依存するのではなく、抽象的な「通知を送るもの」に依存していることを意味する。NotificationServiceのnotifyメソッドが呼び出された際には、コンストラクタで受け取ったnotifierオブジェクトのsendメソッドを呼び出して、実際に通知を実行する。
この設計がどのようにSOLID原則を満たしているかを見てみよう。
まず、**単一責任の原則(SRP)**だ。EmailNotifierはメールを送信する責任のみを持ち、SMSNotifierはSMSを送信する責任のみを持つ。それぞれが「通知方法」という一つの変更理由しか持たないため、例えばメール送信のロジックが変わっても、SMS送信のロジックが変わっても、それぞれのクラスだけを変更すればよい。NotificationServiceは通知の「調整」という責任を持ち、具体的な送信方法には関与しない。
次に、**オープン・クローズドの原則(OCP)**だ。もし新たに「プッシュ通知」を追加したい場合でも、既存のEmailNotifierやSMSNotifier、NotificationServiceのコードを変更する必要はない。新しいPushNotifierクラスをNotifierを継承して作成し、そのインスタンスをNotificationServiceに渡すだけで、システムにプッシュ通知機能を追加できる。既存のコードは「変更に対して閉じられ」、新しい機能の「拡張に対して開かれている」状態が保たれる。
**リスコフの置換原則(LSP)**も満たされている。NotificationServiceはNotifier型を受け取るため、EmailNotifierのインスタンスを渡しても、SMSNotifierのインスタンスを渡しても、あるいは将来作成されるPushNotifierのインスタンスを渡しても、問題なく動作する。これらすべての具体的な通知クラスは、Notifierの「メッセージと送り先を受け取って通知する」という共通の振る舞いを正しく実装しているため、NotificationServiceはこれらを区別することなく、あたかもNotifierオブジェクトであるかのように扱えるのだ。
最後に、**依存関係逆転の原則(DIP)**もこの設計の中心にある。NotificationServiceは、具体的なEmailNotifierやSMSNotifierといった下位レベルのモジュールに直接依存していない。代わりに、NotificationServiceとEmailNotifierやSMSNotifierの両方が、抽象的なNotifierインターフェースに依存している。これにより、NotificationServiceは具体的な実装の詳細から切り離され、柔軟性が高まっている。この設計のおかげで、通知システムの具体的な実装を簡単に変更したり、テストのために模擬的な通知クラスを使用したりすることが可能になるのだ。
この通知システムの例は、SOLID原則がいかにコードの保守性、拡張性、柔軟性を高めるかを示している。これらの原則を理解し、適用することで、システムエンジニアを目指す初心者は、単に動くプログラムを書く以上の、高品質で長期的に利用できるソフトウェアを開発するための基礎を身につけることができる。