okpy

Pythonエンジニア兼テックリーダーが、多くのプロジェクトとチーム運営から得た実践的な知識を共有するブログです。

オブジェクト指向プログラミングの実践例:Spring

Spring Frameworkは、Javaエコシステムで最も広く使用されているフレームワークの1つです。その強力な機能と柔軟性は、オブジェクト指向プログラミング(OOP の原則を忠実に実現していることに由来します。本記事では、Springがオブジェクト指向設計をどのようにサポートしているのか、主要な機能(DI/IoCAOP、Spring Data JPAなど)を例とともに解説します。


1. DI/IoC(依存性注入/制御の反転)

1-1. IoC(制御の反転)とは?

IoC(Inversion of Control) は、オブジェクトの生成や依存関係の管理を開発者ではなくフレームワークに委譲する設計原則です。これにより、コードの柔軟性とテスト可能性が向上します。

1-2. DI(依存性注入)とは?

DI(Dependency Injection) は、オブジェクト間の依存関係をフレームワークが外部から注入する仕組みです。Springでは以下の3つの方法で依存性を注入できます。

  1. コンストラクタ注入
  2. セッター注入
  3. フィールド注入

1-3. DIの基本例

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

// サービスインターフェース
public interface PaymentService {
    void processPayment();
}

// クレジットカード支払いの実装
@Component
public class CreditCardPaymentService implements PaymentService {
    @Override
    public void processPayment() {
        System.out.println("Processing payment with credit card.");
    }
}

// 注入対象クラス
@Component
public class OrderService {
    private final PaymentService paymentService;

    // コンストラクタ注入
    @Autowired
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void placeOrder() {
        System.out.println("Placing order...");
        paymentService.processPayment();
    }
}

// 実行クラス
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("your.package.name");
        OrderService orderService = context.getBean(OrderService.class);
        orderService.placeOrder();
        context.close();
    }
}

ポイント:

  • @Autowired を使用して、PaymentService の依存性を OrderService に注入しています。
  • これにより、OrderService の内部で PaymentService の実装を直接生成する必要がなくなります。

2. AOPアスペクト指向プログラミング)

2-1. AOPとは?

AOPAspect-Oriented Programming) は、横断的な関心事(Cross-Cutting Concerns)をアスペクトとして分離するプログラミング手法です。ログ記録、トランザクション管理、セキュリティなどの共通機能をメインビジネスロジックから分離することで、コードの可読性と再利用性を向上させます。

2-2. AOPの基本構成

  • アスペクトAspect: 横断的な関心事を定義するモジュール。
  • アドバイス(Advice): 実行する具体的な処理(Before, After, Aroundなど)。
  • ジョインポイント(JoinPoint): アドバイスが適用されるポイント(例: メソッドの呼び出し)。
  • ポイントカット(Pointcut): アドバイスを適用する条件。

2-3. AOPの基本例

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

// アスペクト定義
@Aspect
@Component
public class LoggingAspect {

    // ポイントカットとアドバイスの定義
    @Before("execution(* your.package.name.*Service.*(..))")
    public void logBeforeMethod() {
        System.out.println("A method in the Service class is about to be executed.");
    }
}

// サービスクラス
@Component
public class ProductService {
    public void addProduct() {
        System.out.println("Adding a product...");
    }
}

// 実行クラス
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("your.package.name");
        ProductService productService = context.getBean(ProductService.class);
        productService.addProduct(); 
        // A method in the Service class is about to be executed.
        // Adding a product...
        context.close();
    }
}

ポイント:


3. Spring Data JPAとエンティティ設計

3-1. Spring Data JPAとは?

Spring Data JPAは、データベースアクセスを簡素化するためのSpringモジュールです。JPAJava Persistence API)の上に構築されており、リポジトリ(Repository)インターフェースを通じてCRUD操作を簡単に実装できます。

3-2. 基本的なエンティティ設計

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private double price;

    // ゲッターとセッター
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
}

3-3. リポジトリを使ったデータ操作

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    // カスタムクエリの例
    List<Product> findByName(String name);
}

// サービスクラス
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    public void saveProduct(Product product) {
        productRepository.save(product);
    }

    public List<Product> getProducts() {
        return productRepository.findAll();
    }
}

// 実行クラス
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class ApplicationRunner implements CommandLineRunner {
    @Autowired
    private ProductService productService;

    @Override
    public void run(String... args) throws Exception {
        Product product = new Product();
        product.setName("Laptop");
        product.setPrice(1500.00);
        productService.saveProduct(product);

        productService.getProducts().forEach(p -> System.out.println(p.getName()));
    }
}

ポイント:

  • JpaRepository を継承するだけで、CRUD操作を簡単に実装可能。
  • カスタムメソッド(例: findByName)を定義して動的クエリを実現。

4. まとめと次のステップ

本記事では、Spring Frameworkがどのようにオブジェクト指向の設計原則を支援しているのか、DI/IoCAOP、Spring Data JPAなどの機能を中心に解説しました。これらの機能を活用することで、モジュール化された拡張可能なアプリケーションを構築することが可能です。

次回の記事では、以下のトピックを予定しています:

質問やリクエストがあれば、ぜひコメントでお知らせください!


参考資料

  • "Spring in Action" by Craig Walls
  • "Pro Spring Boot 3"
  • Spring公式ドキュメント: https://spring.io/docs