この記事で、SOLID原則について学びます。各原則について理解し、Javaのコード例を学びます。

SOLID原則は、5つの設計原則の集合であり、これらの原則を従って、オブジェクト指向プログラミングを行います。これらの原則に従うことで、堅固なソフトウェアを開発することができます。これらの原則は、コードを効果的で、読みやすく、保守可能にすることができます。

SOLIDは、これらの原則のAcronymであり、以下の意味でstands forを表します。

  • Single Responsibility Principle
  • Open/Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Single Responsibility Principle

The single responsibility principleは、クラスには1つの responsibilityしか持たないこと、1つの変更理由しか持たないことを宣言します。

public class Employee{
  public String getDesignation(int employeeID){ // }
  public void updateSalary(int employeeID){ // }
  public void sendMail(){ // }
}

上記の例で、Employeeクラスには、getDesignationupdateSalaryなどのクラス固有のemployeeの行为がいくつかあります。

加えて、sendMailという別の方法も存在し、これはEmployeeクラスの責任を逸脱させるものです。

この動作はこのクラスに特定されていませんが、これを持っていることはsingle responsibility principleを違反するためです。これを解決するために、sendMailメソッドを別のクラスに移動することができます。

以下は、その方法です。

public class Employee{
  public String getDesignation(int employeeID){ // }
  public void updateSalary(int employeeID){ // }
}

public class NotificationService {
    public void sendMail() { // }
}

開闭原則

開闭原則により、コンポーネントは拡張に向けて開かれ、変更に向けて閉じられなければならない。この原則を理解するために、図形の面積を計算するクラスの例を見てみましょう。

public class AreaCalculator(){
  public double area(Shape shape){
    double areaOfShape;
    if(shape instanceof Square){
        // 的四の面積を計算する
    } else if(shape instanceof Circle){
        // 円の面積を計算する
    }
    return areaOfShape;
  }

上記の例の問題は、将来新しいShape型のインスタンスがあり、その面積を計算する必要があるとき、上記のクラスをelse-ifブロックを追加して変更する必要があることです。Shape型の新しいオブジェクトによってこのプロセスは繰り返されます。

これを解決するために、インターフェースを作成し、各Shapeにこのインターフェースを実装します。そして、各クラスは自身の面积計算の実装を提供することができます。これにより、将来的にはプログラムの拡張性を向上させます。

interface IAreaCalculator(){
  double area();
}

class Square implements IAreaCalculator{
  @Override
  public double area(){
    System.out.println("Calculating area for Square");
    return 0.0;
   }
}

class Circle implements IAreaCalculator{
  @Override
  public double area(){
    System.out.println("Calculating area for Circle");
    return 0.0;
   }
}

リスコフ Substitution Principle

リスコフ Substitution Principleは、スーパークラスのオブジェクトをサブクラスのオブジェクトに置き換えることが可能であることを表します。

abstract class Bird{
   abstract void fly();
}

class Eagle extends Bird {
   @Override
   public void fly() { // いくつかの実装 }
}

class Ostrich extends Bird {
   @Override
   public void fly() { // ダミーの実装 }
}

上の例では、Eagle クラスと Ostrich クラスはどちらも Bird クラスを拡張し、fly() メソッドを上書きします。しかし、Ostrich クラスは飛べないため、ダミー実装を提供する必要があり、そのため、Bird クラスのオブジェクトをそれに置き換えると同じ動作を行わないでしょう。

これはリスコフ替代原則を違反します。これに対処するために、飛ぶ鳥のための別のクラスを作成することができ、Eagle はそれを拡張し、他の鳥は異なるクラスを拡張し、fly 行为を含まないようにすることができます。

abstract class FlyingBird{
   abstract void fly();
}

abstract class NonFlyingBird{
   abstract void doSomething();
}

class Eagle extends FlyingBird {
   @Override
   public void fly() { // いくつかの実装 }
}

class Ostrich extends NonFlyingBird {
   @Override
   public void doSomething() { // いくつかの実装 }
}

インターフェース分割原則

インターフェース分割原則によると、クライアントが必要としない行為を実装することを強制することなく、小さく、焦点を持たせたインターフェースを構築するべきです。

たとえば、形状の面積と体积を計算するインターフェースを持つことが考えられます。

interface IShapeAreaCalculator(){
  double calculateArea();
  double calculateVolume();
}

class Square implements IShapeAreaCalculator{
  double calculateArea(){ // 面積を計算 }
  double calculateVolume(){ // ダミー実装 }
}

問題の点は、Square 形状がこれを実装すると、calculateVolume() メソッドを実装する必要があるために、calculateVolume() メソッドを必要としないということです。

他方、Cubeは両方を実装できる。これを克服するために、インターフェースを分離し、面積と体积の計算を行うための別々のインターフェースを作成することができる。これにより、各形状が実装する内容を決定できる。

interface IAreaCalculator {
    double calculateArea();
}

interface IVolumeCalculator {
    double calculateVolume();
}

class Square implements IAreaCalculator {
    @Override
    public double calculateArea() { // 面積の計算}
}

class Cube implements IAreaCalculator, IVolumeCalculator {
    @Override
    public double calculateArea() { // 面積の計算}

    @Override
    public double calculateVolume() {// 体积の計算}
}

依存倒置原則

依存倒置原則により、高层次のモジュールは低层次のモジュールに依存してはならない。つまり、抽象化に従って松散な結合を Ensure する必要がある。

public interface Notification {
    void notify();
}

public class EmailNotification implements Notification {
    public void notify() {
        System.out.println("Sending notification via email");
    }
}

public class Employee {
    private EmailNotification emailNotification; 
    public Employee(EmailNotification emailNotification) {
        this.emailNotification = emailNotification;
    }
    public void notifyUser() {
        emailNotification.notify();
    }
}

ある例では、Employee クラスが直接 EmailNotification クラスに依存していることが示されている。これは低层次のモジュールであるため、依存倒置原則を破っている。

public interface Notification{
  public void notify();
}

public class Employee{
  private Notification notification;
  public Employee(Notification notification){
      this.notification = notification;
  }
  public void notifyUser(){
    notification.notify();
  }
 }

 public class EmailNotification implements Notification{
    public void notify(){
        // 電子メールを使用して通知を実装}
    }
 }

 public static void main(String [] args){
    Notification notification = new EmailNotification();
    Employee employee = new Employee(notification);
    employee.notifyUser();
 }

上記の例では、松散な結合を Ensure している。Employee は具体的な実装に依存しているわけではなく、それは抽象化(通知インターフェース)にだけ依存している。

通知方法を変更する必要がある場合、新しい実装を作成して Employee に渡すことができる。

結論

この記事では、SOLID原則の本質を簡単な例で説明した。

これらの原則は、高 extendibility と reusability のあるアプリケーションの開発の基盤を形成します。

Let’s connect on LinkedIn → リンクしてください。LinkedIn上でお会いしましょう。