DI (의존성 주입) 의 이해

송영재·2022년 10월 10일

Spring

목록 보기
1/45

🔥 혹시 아직도 더 개선할 부분이 있을까?

  • 43) 강한 결합의 문제점

    ['강한 결합' 이해를 위한 예제]

    1. Contoller1Service1 객체를 생성하여 사용

      public class Controller1 {
      	private final Service1 service1;
      
      	public Controller1() {
      		this.service1 = new Service1();
      	}
      }
    2. Service1Repostiroy1 객체를 생성하여 사용

      public class Service1 {
      	private final Repository1 repository1;
      
      	public Service1() {
      		this.repository1 = new Repository1();
      	}
      }
    3. Repostiroy1 객체 선언

      public class Repository1 { ... }
    4. 만약, 다음과 같이 변경된다면..
      1. Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용
      - 생성자에 DB 접속 id, pw 를 추가

          ```java
          public class Repository1 {
          
          	public Repository1(String id, String pw) {
              // DB 연결
              Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", id, pw);
            }
          }
          ```
          

      ["강한 결합"의 문제점]

    • Controller 5 개가 각각 Service1 을 생성하여 사용 중

    • Repository1 생성자 변경에 의해..

      ⇒ **모든 Contoller** 와 **모든 Service** 의 코드 변경이 필요

  • 44) 강한 결합 해결방법

    	👉 그렇다면 **"강한 결합"**을 해결할 방법이 없을까?
    1. 각 객체에 대한 객체 생성은 딱 1번만!!

    2. 생성된 객체를 모든 곳에서 재사용!!!

    3. Repository1 클래스 선언 및 객체 생성repository1

      		```java

      public class Repository1 { ... }

      // 객체 생성
      Repository1 repository1 = new Repository1();

      
      ![](https://velog.velcdn.com/images/twinsgemini/post/1059c4df-39f1-407b-97e2-bf6210c1da22/image.png)
      
      
    4. Service1 클래스 선언 및 객체 생성 (repostiroy1 사용)service1

      Class Service1 {
      	private final Repository1 repitory1;
      
      	// repository1 객체 사용
      	public Service1(Repository1 repository1) {
      		~~this.repository1 = new Repository1();~~
      		this.repository1 = repository1;
      	}
      }
      
      // 객체 생성
      **Service1 service1 = new Service1(repository1);**

    5. Contoller1 선언 ( service1 사용)

      Class Controller1 {
      	private final Service1 service1;
      
      	// service1 객체 사용
      	public Controller1(Service1 service1) {
      		~~this.service1 = new Service1();~~
      		this.service1 = service1;
      	}
      }
    6. 만약, 다음과 같이 변경된다면,
      1. Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용
      - 생성자에 DB 접속 id, pw 를 추가

          ```java
          public class Repository1 {
          
          	public Repository1(**String id, String pw**) {
              // DB 연결
              Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", **id, pw**);
            }
          }
          
          // 객체 생성
          **String id = "sa";
          String pw = "";**
          Repository1 repository1 = new Repository1(**id, pw**);
          ```
          

      [개선 결과]

      Repository1 생성자 변경은 이제 누구에게도 피해(?) 를 주지 않음

      Service1 생성자가 변경되면? 모든 Contoller → Controller 변경 필요 X

      결론적으로, 강한 결합 ⇒ 느슨한 결합

  • 45) DI (의존성 주입)의 이해

    👉 "제어의 역전 (IoC: Inversion of Control)"
    프로그램의 제어 흐름이 뒤바뀜

    • 일반적: 사용자가 자신이 필요한 객체를 생성해서 사용
    • IoC (제어의 역전)
      • 용도에 맞게 필요한 객체를 그냥 가져다 사용
        • "DI (Dependency Injection)" 혹은 한국말로 "의존성 주입"이라고 부릅니다.
      • 사용할 객체가 어떻게 만들어졌는지는 알 필요 없음
      • 실생활 예제) 가위의 용도별 사용
        • 음식을 자를 때 필요한 가위는? → 부엌가위 (생성되어 있는 객체 kitchenScissors)
        • 무늬를 내며 자를 때 필요한 가위는? → 핑킹가위 (생성되어 있는 객체 pinkingShears)
        • 정원의 나무를 다듬을 때 필요한 가위는? → 전지가위 (생성되어 있는 객체 pruningShears)
  • 46) DI 적용 시도

    ProductRepository 객체 생성 시 DB 접속을 위한 url, id, passwrod 를 모두 받아서 생성

    • [코드스니펫] ProductRepository
      import java.sql.*;
      import java.util.ArrayList;
      import java.util.List;
      
      public class ProductRepository {
      
          private final String dbUrl;
          private final String dbId;
          private final String dbPassword;
      
          public ProductRepository(String dbUrl, String dbId, String dbPassword) {
              this.dbUrl = dbUrl;
              this.dbId = dbId;
              this.dbPassword = dbPassword;
          }
      
          public void createProduct(Product product) throws SQLException {
              // DB 연결
              Connection connection = getConnection();
      
              // DB Query 작성
              PreparedStatement ps = connection.prepareStatement("select max(id) as id from product");
              ResultSet rs = ps.executeQuery();
              if (rs.next()) {
                  // product id 설정 = product 테이블의 마지막 id + 1
                  product.setId(rs.getLong("id") + 1);
              } else {
                  throw new SQLException("product 테이블의 마지막 id 값을 찾아오지 못했습니다.");
              }
              ps = connection.prepareStatement("insert into product(id, title, image, link, lprice, myprice) values(?, ?, ?, ?, ?, ?)");
              ps.setLong(1, product.getId());
              ps.setString(2, product.getTitle());
              ps.setString(3, product.getImage());
              ps.setString(4, product.getLink());
              ps.setInt(5, product.getLprice());
              ps.setInt(6, product.getMyprice());
      
              // DB Query 실행
              ps.executeUpdate();
      
              // DB 연결 해제
              ps.close();
              connection.close();
          }
      
          public Product getProduct(Long id) throws SQLException {
              Product product = new Product();
      
              // DB 연결
              Connection connection = getConnection();
      
              // DB Query 작성
              PreparedStatement ps = connection.prepareStatement("select * from product where id = ?");
              ps.setLong(1, id);
      
              // DB Query 실행
              ResultSet rs = ps.executeQuery();
              if (rs.next()) {
                  product.setId(rs.getLong("id"));
                  product.setImage(rs.getString("image"));
                  product.setLink(rs.getString("link"));
                  product.setLprice(rs.getInt("lprice"));
                  product.setMyprice(rs.getInt("myprice"));
                  product.setTitle(rs.getString("title"));
              }
      
              // DB 연결 해제
              rs.close();
              ps.close();
              connection.close();
      
              return product;
          }
      
          public void updateMyprice(Long id, int myprice) throws SQLException {
              // DB 연결
              Connection connection = getConnection();
      
              // DB Query 작성
              PreparedStatement ps = connection.prepareStatement("update product set myprice = ? where id = ?");
              ps.setInt(1, myprice);
              ps.setLong(2, id);
      
              // DB Query 실행
              ps.executeUpdate();
      
              // DB 연결 해제
              ps.close();
              connection.close();
          }
      
          public List<Product> getProducts() throws SQLException {
              List<Product> products = new ArrayList<>();
      
              // DB 연결
              Connection connection = getConnection();
      
              // DB Query 작성 및 실행
              Statement stmt = connection.createStatement();
              ResultSet rs = stmt.executeQuery("select * from product");
      
              // DB Query 결과를 상품 객체 리스트로 변환
              while (rs.next()) {
                  Product product = new Product();
                  product.setId(rs.getLong("id"));
                  product.setImage(rs.getString("image"));
                  product.setLink(rs.getString("link"));
                  product.setLprice(rs.getInt("lprice"));
                  product.setMyprice(rs.getInt("myprice"));
                  product.setTitle(rs.getString("title"));
                  products.add(product);
              }
      
              // DB 연결 해제
              rs.close();
              connection.close();
      
              return products;
          }
      
          private Connection getConnection() throws SQLException {
              return DriverManager.getConnection(dbUrl, dbId, dbPassword);
          }
      }
    • [코드스니펫] ProductService
      import java.sql.SQLException;
      import java.util.List;
      
      public class ProductService {
      
          private final ProductRepository productRepository;
      
          public ProductService(ProductRepository productRepository) {
              this.productRepository = productRepository;
          }
      
          public Product createProduct(ProductRequestDto requestDto) throws SQLException {
              // 요청받은 DTO 로 DB에 저장할 객체 만들기
              Product product = new Product(requestDto);
      
              productRepository.createProduct(product);
      
              return product;
          }
      
          public Product updateProduct(Long id, ProductMypriceRequestDto requestDto) throws SQLException {
              Product product = productRepository.getProduct(id);
              if (product == null) {
                  throw new NullPointerException("해당 아이디가 존재하지 않습니다.");
              }
      
              int myprice = requestDto.getMyprice();
              productRepository.updateMyprice(id, myprice);
      
              return product;
          }
      
          public List<Product> getProducts() throws SQLException {
              List<Product> products = productRepository.getProducts();
      
              return products;
          }
      }
    • [코드스니펫] ProductController
      import lombok.RequiredArgsConstructor;
      import org.springframework.web.bind.annotation.*;
      
      import java.sql.SQLException;
      import java.util.List;
      
      @RequiredArgsConstructor // final로 선언된 멤버 변수를 자동으로 생성합니다.
      @RestController // JSON으로 데이터를 주고받음을 선언합니다.
      public class ProductController {
      
          private final ProductService productService;
      
          public ProductController(ProductService productService) {
              this.productService = productService;
          }
      
          // 신규 상품 등록
          @PostMapping("/api/products")
          public Product createProduct(@RequestBody ProductRequestDto requestDto) throws SQLException {
              Product product = productService.createProduct(requestDto);
      
              // 응답 보내기
              return product;
          }
      
          // 설정 가격 변경
          @PutMapping("/api/products/{id}")
          public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto) throws SQLException {
              Product product = productService.updateProduct(id, requestDto);
      
              // 응답 보내기 (업데이트된 상품 id)
              return product.getId();
          }
      
          // 등록된 전체 상품 목록 조회
          @GetMapping("/api/products")
          public List<Product> getProducts() throws SQLException {
              List<Product> products = productService.getProducts();
      
              // 응답 보내기
              return products;
          }
      }

0개의 댓글