IFの切り出しは、物事を切り分けるっていう「理解」のプロセスに似ていますよね。

今回は、他のサービスのURLにアクセスする機能を持ったプログラムをひとつのClassにまとめて書いたものから、ChatGPTにインターフェースに分離してもらいます。


プロンプト

次のクラスから、mytestservice.comにアクセスするインタフェースとして切り出して、MyTestClientはそのインターフェースをコンポジションの形で持たせてください。また、その部分に差し込むTestMockも用意したいと思っています。TestMockはJunitで利用してください。

## Javaクラス
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class MyTestClient {
  public static void main(String[] args) throws IOException, InterruptedException {
    // HttpClientのインスタンスを作成します。
    HttpClient client = HttpClient.newHttpClient();

    // リクエストを作成します。
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://mytestservice.com"))
        .build();

    // リクエストを送信します。
    HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());

    // レスポンスのステータスコードを確認します。
    int statusCode = response.statusCode();
    System.out.println("ステータスコード: " + statusCode);

    // レスポンスのボディを取得します。
    String body = response.body();
    System.out.println("ボディ: " + body);
  }
}

ChatGPTの回答

まず、MyTestClientクラスがmytestservice.comにアクセスするためのインターフェースを使うようにリファクタリングする必要があります。そのために、まずインターフェースを作成し、次に実装クラスとモッククラスを作成します。

1. インターフェースの作成

javaCopy codepublic interface MyTestService {
    HttpResponse<String> sendRequest() throws IOException, InterruptedException;
}

2. インターフェースの実装クラス

javaCopy codeimport java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class MyTestServiceImpl implements MyTestService {
    
    @Override
    public HttpResponse<String> sendRequest() throws IOException, InterruptedException {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://mytestservice.com"))
            .build();
        return client.send(request, HttpResponse.BodyHandlers.ofString());
    }
}

3. モッククラスの作成 (TestMock)

javaCopy codeimport java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class TestMock implements MyTestService {
    
    @Override
    public HttpResponse<String> sendRequest() throws IOException, InterruptedException {
        return new HttpResponse<>() {
            @Override
            public int statusCode() {
                return 200;
            }

            @Override
            public String body() {
                return "This is a mock response";
            }

            // ... 他のメソッドを実装してください。
        };
    }
}

4. MyTestClientクラスのリファクタリング

javaCopy codeimport java.io.IOException;
import java.net.http.HttpResponse;

public class MyTestClient {
    private MyTestService myTestService;
    
    public MyTestClient(MyTestService myTestService) {
        this.myTestService = myTestService;
    }
    
    public static void main(String[] args) throws IOException, InterruptedException {
        MyTestService myTestService = new MyTestServiceImpl();
        MyTestClient client = new MyTestClient(myTestService);
        
        HttpResponse<String> response = client.myTestService.sendRequest();
        int statusCode = response.statusCode();
        System.out.println("ステータスコード: " + statusCode);
        
        String body = response.body();
        System.out.println("ボディ: " + body);
    }
}

5. JUnitテストクラスの作成

javaCopy codeimport org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.http.HttpResponse;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class MyTestClientTest {
    
    @Test
    public void testSendRequest() throws IOException, InterruptedException {
        TestMock testMock = new TestMock();
        MyTestClient client = new MyTestClient(testMock);
        
        HttpResponse<String> response = client.sendRequest();
        assertEquals(200, response.statusCode());
        assertEquals("This is a mock response", response.body());
    }
}

これにより、MyTestClientクラスはMyTestServiceインターフェースを使ってmytestservice.comにアクセスし、テスト時にはTestMockを使用してリクエストをモック化することができます。


というように、元のClassからインターフェースを分離してくれました。ついでにChatGPTにプログラムからインターフェースを切り出すことに対するおもしろい蘊蓄を教えてもらいましょう。

プログラムからインターフェースを切り出すことに対するおもしろい蘊蓄を教えてください。

ChatGPTからの回答2

プログラムからインターフェースを切り出すことには、多くの面白くて重要な蘊蓄が含まれています。

  1. 依存性逆転の原則 (Dependency Inversion Principle, DIP): ソフトウェア設計において、依存性逆転の原則は、高水準のモジュールが低水準のモジュールに依存するのではなく、両方が抽象に依存するべきだと提唱しています。これは、コードが具体的な実装ではなくインターフェースに依存することを意味します。これにより、コードの柔軟性と再利用性が向上します。
  2. テスト容易性: インターフェースを切り出すことで、モックオブジェクトを用いた単体テストが容易になります。実際のサービスへの依存性を排除し、テスト用のモック実装を注入できるようになります。これにより、テストの速度と信頼性が向上します。
  3. プラグインアーキテクチャ: インターフェースを使用すると、システムにプラグインアーキテクチャを導入しやすくなります。これにより、ユーザーや他の開発者がカスタム実装を提供し、既存のコードベースに追加することができます。
  4. コードの分離と読みやすさ: インターフェースを切り出すことで、コードのロジックが明確に分離され、それぞれの部分が独立して理解しやすくなります。これはコードのメンテナンスを容易にし、新しい開発者がプロジェクトに参加する際の学習曲線を緩和します。
  5. 交換可能な実装: インターフェースを使用すると、実行時に異なる実装を容易に交換できます。これは、異なる環境や条件に応じて異なる戦略を選択する際に有用です。
  6. オープン/クローズド原則 (Open/Closed Principle, OCP): ソフトウェアエンティティは拡張に対して開いており、修正に対して閉じているべきだとする原則です。インターフェースを使用することで、新しい機能を追加する際に既存のコードを修正することなく、新しい実装を追加することができます。

これらは、プログラムのインターフェースを適切に設計することの長所であり、これによりソフトウェア開発プロセスが向上し、結果として高品質でメンテナブルなソフトウェアが生まれます。