스프링 프레임워크 사용 이유 및 장점 (면접 질문)

면접 질문: 스프링 프레임워크를 사용하는 이유는 무엇인가요?

스프링 프레임워크의 핵심 기능 중 하나는 객체를 생성하는 것입니다. 하지만 객체를 생성하는 행위 자체는 매우 간단합니다.

Product product = new Product();

하지만 실제 프로젝트 개발에서는 클래스의 수가 매우 많아지고, 생성해야 할 객체도 많아지며, 클래스 간의 의존성이 복잡해집니다. 객체를 직접 생성하면 결합도가 높아져 프로젝트 관리와 유지보수에 불리합니다.

따라서 스프링을 사용하여 객체를 생성하면 결합도를 낮추고, 낮은 결합도는 관리와 유지보수를 용이하게 만듭니다.

의존성 예시 설명:

기존 코드를 예로 들어보겠습니다.

public class ProductRepository { // 데이터베이스 관련 로직을 이 클래스에 직접 구현
  public void save(Product product) {
    // JDBC를 사용하여 제품 데이터를 데이터베이스에 저장
  }
  
  public Product findById(String id) {
    // ...
  }
  
  public void update(Product product) {
    // ...
  }
}

그리고 다음과 같은 컨트롤러가 있다고 가정해 봅시다.

// 제품 등록을 처리하는 컨트롤러
public class ProductController {
  public ProductRepository repository = new ProductRepository();
  
  // 제품 등록 요청을 처리하는 메서드
  public void handlePostRequest() {
    // 사용자가 제출한 등록 정보를 데이터베이스에 저장
    repository.save(newProduct);
  }
}

프로젝트에는 사용자 로그인을 처리하는 UserController나 비밀번호 변경을 처리하는 PasswordController 등 다른 컨트롤러도 있을 수 있습니다. 이러한 컨트롤러들은 데이터베이스 관련 작업을 수행할 때 다음과 같은 코드를 포함해야 합니다.

public ProductRepository repository = new ProductRepository();

이러한 코드는 ProductController와 같은 클래스가 ProductRepository의존하고 있다고 말할 수 있습니다!

만약 ProductRepository를 JDBC가 아닌 MyBatis 프레임워크를 사용하도록 변경해야 한다면, ProductRepositoryMyBatisProductRepository로 교체해야 합니다. 이 경우 기존의 public ProductRepository repository = new ProductRepository(); 코드는 모두 다음과 같이 수정해야 합니다.

public ProductRepository repository = new ProductRepository();
// 변경 후:
public ProductRepository repository = new MyBatisProductRepository();

이러한 코드는 관리와 유지보수가 어렵습니다! 프로젝트에 수십 또는 수백 개의 컨트롤러가 있다면, 이 수십 또는 수백 개의 컨트롤러 클래스의 소스 코드를 모두 수정해야 합니다. 이는 매우 번거롭고 비효율적입니다.

이 문제를 어떻게 해결할 수 있을까요?

1. 인터페이스를 활용한 해결

이 문제를 해결하기 위해, 먼저 인터페이스를 선언할 수 있습니다.

public interface ProductRepositoryInterface {
  void save(Product product);
  Product findById(String id);
  void update(Product product);
}

그리고 DAO 클래스들이 이 인터페이스를 구현하도록 합니다.

public class JdbcProductRepository implements ProductRepositoryInterface {
  // 인터페이스의 추상 메서드를 구현
}

public class MyBatisProductRepository implements ProductRepositoryInterface {
  // 인터페이스의 추상 메서드를 구현
}

그러면 DAO를 선언하는 코드는 다음과 같이 변경할 수 있습니다.

ProductRepositoryInterface repository = new MyBatisProductRepository();

이렇게 작성하면 이전에는 왼쪽과 오른쪽을 모두 변경해야 했지만, 이제는 오른쪽만 변경하면 되므로 작업량이 절반으로 줄어듭니다.

// "인터페이스로 선언하고, 구현체의 객체를 생성한다"는 것은 다형성을 보여줍니다.

// 이것이 다형성의 주요 특징입니다:
// 1. 부모 클래스로 선언하고, 자식 클래스의 객체를 생성
// 2. 인터페이스로 선언하고, 구현체의 객체를 생성

의존 관계가 존재할 때는 구체적인 클래스에 의존하기보다는 인터페이스에 의존하는 것이 좋습니다. 구체적인 클래스에 직접 의존하면 결합도가 높아지는 것입니다!

낮은 결합도는 관리와 유지보수를 더 쉽게 만듭니다.

결합도가 높은 코드를 낮은 결합도로 조정하는 것을 '결합도 해소'라고 합니다.

2. 팩토리 패턴을 통한 해결

그리고 디자인 패턴 중 **'팩토리 패턴'**을 사용하여 위 코드를 더 최적화할 수 있습니다!

public class RepositoryFactory {
  public static ProductRepositoryInterface newInstance() {
    return new JdbcProductRepository();
  }
}

그러면 DAO를 선언하는 코드는 다음과 같이 더욱 개선될 수 있습니다.

ProductRepositoryInterface repository = RepositoryFactory.newInstance();

위 코드에서 구현체의 이름이 전혀 드러나지 않는 것을 알 수 있습니다. 나중에 사용할 구현체를 변경해야 할 경우, 위 코드는 전혀 수정할 필요가 없습니다! 변경해야 할 위치는 팩토리 메서드의 반환값뿐입니다.

// 아래 문장만 변경하면 됩니다.
return new JdbcProductRepository(); // 변경 후:
return new MyBatisProductRepository();

따라서 인터페이스와 팩토리 메서드를 사용하면 코드의 관리성과 유지보수성을 크게 향상시킬 수 있습니다.

이 과정에서 핵심적인 역할을 하는 것이 바로 팩토리 클래스입니다. 실제 프로젝트를 작성할 때, 사용하는 모든 클래스에 대해 별도의 팩토리를 만드는 것은 현실적이지 않습니다. 스프링 프레임워크는 이 모든 것을 처리해주는 거대한 '만능 팩토리'와 같습니다. 개발자가 필요한 객체가 필요할 때, 스프링 컨테이너에서 가져오기만 하면 됩니다!

태그: Spring DI 결합도 팩토리 패턴

6월 12일 18:41에 게시됨