ブログに戻る

SOLID: オブジェクト指向設計の最初の5つの原則?

SOLID: オブジェクト指向設計の最初の5つの原則?

はじめに

SOLIDは、一般にUncle Bobとして知られるロバート・C・マーティン(Robert C. Martin)によって提唱された、オブジェクト指向設計の5つの原則の頭字語(アクロニム)です。これらの原則は、ソフトウェア設計者、アーキテクト、エンジニア、開発者が、より柔軟でメンテナンスしやすく、拡張性の高いソフトウェアを作成できるようにすることを目的としています。これらの原則に従うことで、テスト、リファクタリング、再利用、拡張が容易なクラスを設計できます。

SOLIDという頭字語は、以下を表しています。

S – Single Responsibility Principle

O – Open-Closed Principle

L – Liskov Substitution Principle

I – Interface Segregation Principle

D – Dependency Inversion Principle

この記事では、より優れたコードを書くために各原則がどのように役立つかを理解するために、それぞれの原則を個別に説明します。さらに、クリーンコードアーキテクチャにおいて避けるべきことだけでなく、コーディングの過程でこれらをどのように適用できるかを示すために、各原則のコードスニペットを追加します。概念を実証するために、JetBrainsとオープンソースの貢献者によって開発されたKotlin programming言語を使用します。

単一責任の原則

単一責任の原則(SRP: Single Responsibility Principle)は、プログラム内のすべてのクラスまたはモジュールが、単一の明確に定義された責任を持つべきであるとするソフトウェア設計の原則です。これは、クラスを変更する理由は1つだけでなければならず、プログラムの機能の単一の部分に対してのみ責任を負うべきであることを意味します。

この原則をKotlin:

Kotlinの dataキーワードは、このクラスがデータクラスであることを示しています。これは、データを保持することを目的としており、複雑な動作を持たないことを意味します。このデータクラスを使用して、以下に示すようにユーザーを管理するための UserServiceクラスを作成できます。

この例では、 UserServiceクラスは、データベース内のユーザーを管理するという単一の責任を持っています。各ユーザーは、以前に共有された data class Userコードによって表されます。 UserServiceクラスのすべてのメソッドはこの責任に関連しているため、凝集度が高くなります。これにより、クラス内のすべてのメソッドが単一の明確に定義されたタスクに関連していることが明らかになり、クラスの理解とメンテナンスが容易になります。

単一責任の原則(SRP)に違反する UserServiceクラスのメソッドは、データベース内のユーザー管理というクラスの主な責任に関連していないメソッドになります。たとえば、次の UserServiceクラスのバリエーションを考えてみましょう。

この例では、 sendEmail メソッドは、データベース内のユーザーを管理するという UserService クラスの主な責務とは関係ありません。このメソッドはメールの送信を担当していますが、これはユーザーの管理とは別の関心事です。その結果、このメソッドは UserService クラスが変更される2つ目の理由を導入することになり、SRP(単一責任の原則)に違反します。

単一責任の原則を遵守するためには、メール送信機能を例えば EmailService クラスのような別のクラスに分離する方が良いでしょう。これにより、 UserService クラスはユーザー管理という主な責務に集中でき、 EmailService クラスはメール送信という責務に集中できるようになります。

単一責任の原則とは、クラスが持つメソッドの数に関するものではなく、メソッドの凝集度やクラス内における責任の明確な分離に関するものであることに注意してください。

オープン・クローズドの原則

オープン・クローズドの原則(OCP)とは、ソフトウェアの構成要素(クラス、モジュール、関数など)は拡張に対して開いており、修正に対して閉じているべきであるというソフトウェア設計の原則です。これは、既存のコードを変更することなく、クラスやモジュールに新しい機能を追加できるようにすべきであることを意味します。

前の例の UserService クラスを考えてみましょう。メールアドレスでユーザーを検索できるようにする新機能を UserService クラスに追加したいとします。これを行う1つの方法は、以下のコードで強調表示されているように、 UserService クラスに新しいメソッドを追加することです。

このアプローチは機能しますが、新しい機能を追加するために既存の UserService クラスを変更する必要があったため、オープン・クローズドの原則に違反しています。より良いアプローチは、継承またはコンポジションを使用して、 UserService クラスのコードを変更せずにその機能を拡張することです。

これを実現するために、 UserSearchService という新しいクラスを作成し、 UserService クラスを拡張してメール検索機能を追加することができます。

この例では、 UserSearchService クラスは、 UserService クラス。同時に、 UserService クラスは、メール検索機能を追加するためにコードを変更する必要がなかったため、変更に対して閉じたままです。

リスコフの置換原則

リスコフの置換原則(LSP)は、プログラムの正確性に影響を与えることなく、スーパークラスのオブジェクトをサブクラスのオブジェクトで置き換えられるべきであるとするソフトウェア設計の原則です。これは、サブクラスがそのスーパークラスの有効な代替となり、同じコンテキストで使用されたときにスーパークラスと同じように動作する必要があることを意味します。

引き続き、前の例の UserUserService クラスを使用してデモンストレーションを続けます。User クラスを拡張できるようにするために、Kotlin の open  キーワードを使用します。

以下は、元の UserService クラスです。これは上記の User  データクラスを使用しています:

ここで、 User のサブクラスで、管理者権限を持つユーザーを表す AdminUser を作成したいとします。これは次のように行うことができます:

この例では、 AdminUser クラスは User クラスの有効な代替です。なぜなら、 User クラスと同じように動作し、 User オブジェクトが期待される任意の場所で使用できるからです。例えば、次のように AdminUser クラスを UserService  クラスと一緒に使用できます:

このコードは正しく動作します。なぜなら、 AdminUser クラスは User クラスの有効な代替であり、 User オブジェクトと同じように使用できるからです。

リスコフの置換原則は、単なる継承以上の意味を持つことに注意することが重要です。これは、サブクラスがどのように実装されているかに関わらず、サブクラスのオブジェクトがスーパークラスのオブジェクトと同じように動作することを保証することについて述べています。例えば、もし AdminUser クラスが何らかの方法で User クラスとは異なる動作をする場合、それは User クラスの有効な代替とはならず、リスコフの置換原則に違反することになります。

インターフェース分離の原則

インターフェース分離の原則(ISP)は、クライアントが使用しない interfacesに依存することを強制されるべきではないとするソフトウェア設計の原則です。これは、一般的に、1つのことをうまく行う、小さく焦点を絞った interfacesを作成する方が、多くのことを行おうとする大規模で汎用的な interfacesを作成するよりも良いアイデアであることを意味します。

以下は、前の例の User  および UserService  クラスを書き直すことで、この原則をどのように適用できるかを示す例です:

この例では、 UserService インターフェースは、データベース内のユーザー管理に関連する4つのメソッドを定義しています。 DatabaseUserService  クラスはこのインターフェースを実装し、これらのメソッドの具体的な実装を提供します。

たとえば、新しい機能を UserService インターフェースに追加して、メールアドレスでユーザーを検索できるようにしたいとします。これを行う1つの方法は、新しいメソッドを UserService インターフェースに追加することです:

コードを実行するには、 UserService インターフェースを実装するすべてのクラスで、このメソッドも実装する必要があります:

このアプローチは機能しますが、インターフェース分離の原則に違反しています。なぜなら、 DatabaseUserService  クラスに対して、自身が必要としない、または使用しない可能性のあるメソッド( searchUsersByEmail )の実装を強制することになるからです。

より良いアプローチは、メール検索機能用に別の インターフェース  を作成することです:

これで、分割された、小さく、目的が明確なインターフェース(すなわち、 UserService  と UserSearchService)になります。単一の責任を持つものです。これらのインターフェースのすべての機能を必要とするクラスは、以下のコードスニペットに示すようにそれらを実装できます。

これはインターフェース分離の原則に準拠しています。なぜなら、クライアント(例えば DatabaseUserService クラス)が、使用しないインターフェースへの依存を強制されないようにするためです。

この概念をよりよく理解するために、別のクラスとして MemoryUserService  があり、これが UserService  インターフェースを実装しているものの、メール検索機能を必要としないと仮定します。その場合、コードは以下のように記述できます。

この例では、 MemoryUserService クラスは、 UserService インターフェースで定義されたメソッドのみを実装すればよく、メール検索機能について心配する必要はありません。これにより、 MemoryUserService クラスは、無関係な機能の実装を強制されることなく、メモリ内でのユーザー管理という本来の責任に集中することができます。

依存性逆転の原則

依存性逆転の原則(DIP)とは、上位モジュールは下位モジュールに依存すべきではなく、両者は抽象に依存すべきであるというソフトウェア設計の原則です。これは一般的に、上位コンポーネントが下位コンポーネントの具体的な実装に縛られるのではなく、さまざまな方法で実装可能な抽象(インターフェースや抽象クラスなど)に依存するようにソフトウェアを設計することが望ましいということを意味します。

この原則が、以前のコードスニペットで使用された User および UserService クラスにどのように適用できるか、例を見てみましょう。

この例では、 UserService クラスは、 UserRepository インターフェースという抽象化に依存しており、ユーザーリポジトリの具体的な実装には依存していません。これにより、 UserRepository インターフェースをデータベースやインメモリストレージなど、さまざまな方法で実装できるようになり、 UserService クラスに影響を与えることがなくなります。

例えば、以下は UserRepository インターフェースをデータベースを使って実装した例です:

以下は、 UserRepository インターフェースをインメモリストレージを使って実装したもう一つの例です:

これにより、システムの他の部分に影響を与えることなくリポジトリの実装を変更できるようになるため、システムがより柔軟でメンテナンスしやすくなります。また、テストで UserRepository の依存関係をモック化できるため、 UserService クラスのテストも容易になります。

まとめ

この記事では、 SOLID の5つの原則について説明しました。  各原則を満たすコードと共有コードスニペット。これらの原則に従うことで、より柔軟で、保守しやすく、拡張性の高いソフトウェアシステムを設計することができます。ただし、これらの原則は厳格なルールではなくガイドラインであり、特定のプロジェクトの文脈においていつどのように適用するかを決定するのは開発者次第であることを念頭に置くことが重要です。ブログをチェックして、より深く最新の記事や チュートリアルクラウドコンピューティングやDevOps、ソフトウェアの設計と開発、注目すべきテクノロジートレンドなど)を読んで、学習を続けましょう。

Happy Coding!

author

Preslav Dobrev

著者 · CloudSigma

Preslav DobrevはCloudSigmaのクリエイティブデザイナーであり、従来型および革新的なマーケティングチャネルを活用した一貫性のあるビジネスアイデンティティに注力しています。彼は芸術的なビジョンと戦略的マーケティングを融合させ、インパクトのあるブランドナラティブを生み出すことに長けています。

コメント

コメントはまだありません。最初のコメントを投稿しましょう。