メインコンテンツへスキップ
見出し画像

3分間プログラミング講座 第13回: 抽象化・具体化・DI ~Spring Frameworkにおける設計の柔軟性~


    登場人物:

    • チャット先生(通称:先生): オブジェクト指向マスター。普段は優しく、たまにユーモアも交えながら解説。

    • ボット助手(通称:ボット君): プログラミングを始めたばかりの若手。素朴な疑問を投げかけるのが得意。


    ボット君: 先生!前回はインターフェースと抽象クラスでプログラムの「設計図」がぐっと整理されることがわかりました!なんだか複雑なシステムも設計できる気がしてきました!

    先生: その調子だ、ボット君!インターフェースと抽象クラスは、まさに複雑なシステムを柔軟に設計するための強力なツールだ。今日は、それらの概念がSpring Frameworkという現代のJavaアプリケーション開発でいかに活用され、システムをより賢く、そして変更に強くしていくかを見ていこう。キーワードは「抽象化」「具体化」、そして「DI (Dependency Injection) / 依存性の注入」だ!

    ボット君: 難しい言葉が並びましたね…!でも、先生と一緒なら乗り越えられる気がします!

    先生: もちろんさ!一つずつ丁寧に見ていこう。特にDIはSpring Frameworkの心臓部とも言える機能だから、しっかり理解しておくと、君のプログラムがまるで魔法にかかったように動き出すぞ!



    1. 抽象化と具体化の再確認



    先生: まずは、インターフェースと抽象クラスで学んだ「抽象化」と、それを具体的なクラスで実現する「具体化」の概念を再確認しよう。

    ボット君: 抽象化は「何をするか」だけ決めることで、具体化は「どうするか」を実際に書くこと、でしたよね?

    先生: 大正解だ!例えば、「通知を送る」という機能を考えてみよう。

    • 抽象化: 「通知を送る」という**振る舞い(契約)**だけを、NotificationSenderというインターフェースで定義する。

    Java

    // 抽象化されたインターフェース
    public interface NotificationSender {
        void send(String message);
    }
    
    • 具体化: NotificationSenderインターフェースを実装して、実際にメールで送ったり、Slackで送ったりする具体的なクラスを作る。

    Java

    // 具体化された実装1
    public class EmailNotificationSender implements NotificationSender {
        @Override
        public void send(String message) {
            System.out.println("Email送信: " + message);
        }
    }
    
    // 具体化された実装2
    public class SlackNotificationSender implements NotificationSender {
        @Override
        public void send(String message) {
            System.out.println("Slack送信: " + message);
        }
    }
    

    先生: ここまでは前回の復習だ。では、これらの「通知を送る機能」を、別のクラス(例えば、注文処理を行うOrderService)で使いたい場合、どうするだろう?

    ボット君: ええと、OrderServiceの中でnew EmailNotificationSender()とかnew SlackNotificationSender()ってすればいいんじゃないですか?

    先生: その通り!それは一つの方法だ。しかし、もし「Slackでの通知をやめて、別のチャットツールにしたい」とか、「メールとSlackの両方で送りたい」となったらどうなるかな?

    ボット君: OrderServiceのコードを書き換えないといけなくなりますね…。新しい通知方法が増えるたびに、OrderServiceを修正するのは大変そうです。



    2. DI (Dependency Injection) / 依存性の注入とは?~Springの魔法~



    先生: そこで登場するのが、「DI (Dependency Injection) / 依存性の注入」だ!これがSpring Frameworkの最も強力な機能の一つと言ってもいい。

    ボット君: 依存性の注入…?

    先生: OrderServiceがEmailNotificationSenderやSlackNotificationSenderに「依存している」状態を考えてみよう。つまり、OrderServiceが特定の具体的なNotificationSenderを自分で作り出して使っている、という状態だ。

    DIは、「依存しているオブジェクトを、そのオブジェクト自身が作らず、外部から与えてもらう(注入してもらう)」という考え方だ。

    ボット君: 外部から与えてもらう…?どういうことですか?

    先生: こういうことだ!

    Java

    // Spring FrameworkにおけるDIの例
    import org.springframework.stereotype.Service;
    import org.springframework.stereotype.Component;
    import java.util.List;
    
    // 抽象化されたインターフェース(再掲)
    // public interface NotificationSender { ... }
    
    // 具体化された実装1(再掲) - Springがコンポーネントとして認識できるようにアノテーションを付ける
    @Component // Springに「これは汎用的なコンポーネントだよ」と教えるアノテーション
    public class EmailNotificationSender implements NotificationSender {
        @Override
        public void send(String message) {
            System.out.println("Email送信: " + message);
        }
    }
    
    // 具体化された実装2(再掲) - Springがコンポーネントとして認識できるようにアノテーションを付ける
    @Component // Springに「これも汎用的なコンポーネントだよ」と教えるアノテーション
    public class SlackNotificationSender implements NotificationSender {
        @Override
        public void send(String message) {
            System.out.println("Slack送信: " + message);
        }
    }
    
    // 通知サービス - Springが管理するサービスとして定義
    @Service // Springに「これはビジネスロジックを扱うサービスだよ」と教えるアノテーション
    public class NotificationService {
        // なお、@Serviceはビジネスロジック、@Repositoryはデータアクセス層、@Controllerは制御層を表す
        // これらは全て@Componentの特殊化されたバージョンで、役割を明確にする意味合いがある
        private final List<NotificationSender> senders; // NotificationSenderインターフェースに依存
    
        // コンストラクタインジェクション:Springが自動的に全ての実装を注入してくれる
        // Spring 4.3以降は、コンストラクタが一つだけなら@Autowiredアノテーションは不要
        public NotificationService(List<NotificationSender> senders) {
            this.senders = senders; // Springが、見つけた全てのNotificationSender実装をリストとして渡してくれる
            System.out.println("NotificationServiceが初期化されました。利用可能な通知サービス: " + senders.size() + "種類");
        }
    
        public void sendNotification(String message) {
            System.out.println("\n--- 通知処理開始 ---");
            // リスト内の全てのNotificationSenderを使って通知を送る
            senders.forEach(sender -> sender.send(message));
            System.out.println("--- 通知処理完了 ---"); // ←ここを修正
        }
    }
    

    先生: ここで注目してほしいのは、NotificationServiceのコンストラクタだ。

    1. NotificationServiceは、NotificationSenderというインターフェースに依存している。具体的なEmailNotificationSenderやSlackNotificationSenderには依存していない。これが**「依存性逆転の原則」**とも呼ばれる考え方だ。

    2. そして、NotificationServiceのインスタンスを作る際に、List<NotificationSender> sendersとして受け取っているだろう?

    3. Spring Frameworkが、@Componentアノテーションを付けたNotificationSenderインターフェースの全ての実装クラス(EmailNotificationSenderとSlackNotificationSender)を自動で見つけ出し、そのインスタンスをリストとして作成し、NotificationServiceのコンストラクタに「注入」してくれるんだ!

    ボット君: すごい!NotificationServiceは自分でnewしなくてもいいし、どのNotificationSenderを使うかを知らなくてもいいってことですか?魔法みたいですね!

    先生: その通り!これがDIの強力なポイントだ。

    • 結合度の低下: NotificationServiceは、具体的な実装クラス(EmailNotificationSenderなど)ではなく、抽象(インターフェース)に依存する。これにより、各クラスの結びつきが弱くなり、変更に強くなる。

    • テストのしやすさ: 特定の実装クラスに依存しないため、テスト時にダミーのNotificationSender(モックオブジェクト)を簡単に差し込むことができる。

    • 再利用性の向上: NotificationServiceはあらゆるNotificationSenderで機能するため、汎用性が高い。

    • 設定の容易さ: どの実装を使うかは、コードの内部でなく、Springの設定(@Componentなど)で決めることができる。

    先生: Spring Frameworkでは、このDIの仕組みを「IoCコンテナ (Inversion of Control Container) / 制御の反転コンテナ」が実現している。オブジェクトの生成や依存関係の解決を、プログラマが直接コードで書く代わりに、Springが自動で行ってくれるんだ。これにより、君はビジネスロジックの記述に集中できるようになる。 また、Beanのスコープ(例:@Scope("singleton")、@Scope("prototype")など)やセキュリティ関連(@PreAuthorize、@Securedなどによる認可・認証など)もIoCコンテナが管理できるが、これらはさらに高度な話題なので、今回は触れないでおこう。



    3. Spring BootでのDI実践例



    先生: より実践的な例として、Webアプリケーションの注文処理サービスを見てみよう。ここでは、注文処理サービスが支払い、在庫、そして通知サービスに依存している形だ。

    Java

    // Spring BootでのDI実践例
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional; // トランザクション管理用
    
    // (補足) PaymentServiceとInventoryServiceは、それぞれ@Serviceアノテーションを持つ別のクラスとする
    interface PaymentService {
        Payment processPayment(double amount); // Paymentオブジェクトを返すとする
    }
    
    interface InventoryService {
        void checkAvailability(String productId);
    }
    
    // 注文関連のクラス(簡易的な定義)
    class OrderRequest {
        String productId;
        double paymentInfo;
    
        public OrderRequest(String productId, double paymentInfo) {
            this.productId = productId;
            this.paymentInfo = paymentInfo;
        }
    
        public String getProductId() { return productId; }
        public double getPaymentInfo() { return paymentInfo; }
    }
    
    class Order {
        Long id; // 仮の注文ID
        String productId;
    
        public Order(OrderRequest request) {
            this.id = System.currentTimeMillis(); // 仮のID生成
            this.productId = request.getProductId();
        }
    
        public Long getId() { return id; }
    }
    
    class Payment {
        // 支払いに関する情報(今回はシンプルに)
    }
    
    
    // 実際の実装クラス(省略)
    @Service
    class PaymentServiceImpl implements PaymentService {
        @Override
        public Payment processPayment(double amount) {
            System.out.println("支払いサービス: " + amount + "円の支払い処理を実行。");
            return new Payment();
        }
    }
    
    @Service
    class InventoryServiceImpl implements InventoryService {
        @Override
        public void checkAvailability(String productId) {
            System.out.println("在庫サービス: " + productId + "の在庫確認を実行。");
        }
    }
    
    
    @Service // このクラスがSpringによって管理されるサービスであることを示す
    public class OrderService {
        private final PaymentService paymentService;     // 支払いサービスへの依存
        private final InventoryService inventoryService; // 在庫サービスへの依存
        private final NotificationService notificationService; // 通知サービスへの依存
    
        // コンストラクタインジェクション:SpringがPaymentServiceとInventoryService、NotificationServiceのインスタンスを注入する
        // @Autowiredアノテーションは、コンストラクタが一つだけの場合、Spring 4.3以降は省略可能
        public OrderService(PaymentService paymentService,
                            InventoryService inventoryService,
                            NotificationService notificationService) {
            this.paymentService = paymentService;
            this.inventoryService = inventoryService;
            this.notificationService = notificationService;
            System.out.println("OrderServiceが初期化され、必要なサービスが注入されました。");
        }
    
        @Transactional // 複数のデータベース操作やサービス連携を一つのまとまりとして扱う
        public Order processOrder(OrderRequest request) {
            System.out.println("\n--- 注文処理開始 ---");
    
            // 在庫チェック
            inventoryService.checkAvailability(request.getProductId());
            System.out.println(request.getProductId() + "の在庫確認完了。");
    
            // 支払い処理
            Payment payment = paymentService.processPayment(request.getPaymentInfo());
            System.out.println(request.getPaymentInfo() + "円の支払い処理完了。");
    
            // 注文作成 (ここでは簡易的にオブジェクトを生成)
            Order order = new Order(request);
            System.out.println("注文が作成されました。注文番号: " + order.getId());
    
            // 通知送信(複数の通知方法で自動送信)
            notificationService.sendNotification(
                "注文が完了しました。注文番号: " + order.getId() + " - 製品: " + order.getProductId()
            );
    
            System.out.println("注文が正常に処理されました。");
            System.out.println("--- 注文処理完了 ---");
            return order;
        }
    }
    
    // (Spring Bootアプリケーションのメインクラスからの呼び出しイメージ)
    /*
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.beans.factory.annotation.Autowired;
    
    @SpringBootApplication
    public class MyApplication implements CommandLineRunner {
    
        @Autowired
        private OrderService orderService; // SpringがOrderServiceのインスタンスを注入
    
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }
    
        @Override
        public void run(String... args) throws Exception {
            System.out.println("アプリケーション起動完了。テスト注文を処理します。");
            orderService.processOrder(new OrderRequest("LaptopX", 1200.00));
            orderService.processOrder(new OrderRequest("SmartphoneY", 800.00));
        }
    }
    */
    

    先生: このOrderServiceは、具体的なPaymentServiceImplやInventoryServiceImpl、NotificationServiceを知る必要がない。ただPaymentServiceとInventoryService、NotificationServiceという「役割」を持ったオブジェクトが必要なだけだ。これらのオブジェクトはSpringが自動的に見つけて、OrderServiceを生成する時に渡してくれるんだ。 そして、@Transactionalアノテーションにも注目してほしい。これは、このメソッド内の複数の操作(在庫チェック、支払い処理、注文作成、通知送信)が、すべて成功するか、すべて失敗するかを保証してくれるものだ。例えば、支払い処理が失敗したら、注文も通知も行われず、まるで何も起こらなかったかのように元に戻る。これもSpringが提供する強力な機能の一つなんだ。

    ボット君: わー!本当に魔法みたい!これなら、支払い方法が変わっても、OrderServiceのコードは全然変えなくていいんですね!新しい支払いサービスを作って、@Serviceって付ければ、Springが勝手に切り替えてくれるってことですか?しかも、処理の途中でエラーが起きても安心なんですね!

    先生: その通り!それがDIの最大のメリットの一つであり、@Transactionalが加わることで、さらに信頼性の高いビジネスロジックが構築できるようになる。まさに柔軟性と拡張性、そして堅牢性の実現だね。



    今日のまとめ!



    • 抽象化: 「何をすべきか」を定義し、具体的な実装から独立させること。インターフェースや抽象クラスを使う。

    • 具体化: 抽象化された定義に基づき、「どのようにすべきか」を実際にコードで実装すること。

    • DI (Dependency Injection) / 依存性の注入:

      • オブジェクトが必要とする依存(他のオブジェクト)を、オブジェクト自身が作らず、外部(SpringのIoCコンテナ)から与えてもらう仕組み。

      • これにより、クラス間の結合度が下がり、システムが変更に強く、テストしやすくなる。

      • Spring Frameworkでは@Service、@ComponentなどのアノテーションとコンストラクタインジェクションなどでDIが実現される。

    • @Transactional: メソッド内のデータベース操作などをトランザクションとして管理し、処理の原子性(成功か失敗か)を保証する。


    ボット君: 先生、今日はDIと@Transactionalがよく分かりました!プログラムがまるで生き物みたいに賢く連携し合う仕組みで、しかも堅牢になるんですね!Spring Frameworkがなぜ便利なのか、さらに深く分かった気がします!

    先生: よくぞ気づいた、ボット君!DIとトランザクション管理はSpring Frameworkの強力な武器であり、現代のエンタープライズアプリケーション開発には欠かせない考え方だ。この概念をしっかり掴んでいれば、君のプログラムはさらに洗練されたものになるはずだ。

    先生: 次回は、アプリケーションがデータを保存・管理するための非常に重要な技術、「O/Rマッピング」について学んでいこう。そして、それがSpring Frameworkでどのように簡潔に実現されているか、実際のデータベース(PostgreSQL)を使った例を交えて解説するぞ!

    ボット君: データベース!ついにデータが保存できるようになるんですね!楽しみです!ありがとうございました!


    次回:第14回: O/RマッピングとDI ~Spring Data JPAでデータベース連携~

    #オブジェクト指向 #プログラミング初心者 #Java #クラスとメソッド #プログラミング学習 #入門


     
     
     
    まだ知らない『面白い』への案内人。人・作品・AIの魅力を見つけ、物語と知識に変えて届けるコンシェルジュです。毎週土曜は漫画ソムリエ。スキ動画コンテストほか、いろいろ実験中。

    あなたへのおすすめ