본문 바로가기

토비의 스프링 정리

토비의 스프링 - 3.2 변하는 것과 변하지 않는 것

3.2.1 JDBC trt/catch/finally 코드의 문제점

  • 코드가 너무 난잡함
  • close()를 제대로 하지 않으면 리소스 문제 발생
  • 수정할 코드를 찾는 것이 어려움
  • 예외상황을 처리하는 테스트 코드를 만들기 힘듦(인터페이스 구현체를 만들어야 하기 때문)

💡 난잡한 코드를 해결하기 위해 변하는 것변하지 않는 것분리하여 리팩토링 해야함

 

3.2.2 분리

  • 변하는 것과 변하지 않는 것을 분리
  • deleteAll()에서 ps 만이 변하는 부분임
  • 변하는 부분만 메소드 추출하면 추출한 메소드를 다른곳에 재사용할 수 있어야 하는데, 이와 반대로 추출된 메소드만 재사용이 필요한 부분으로 변경됨
public void deleteAll() throws SQLException {
    PreparedStatement ps = null;
    Connection c = null;
    try {
        c = dataSource.getConnection();
        ps = makeStatement(c);
        ps.executeUpdate();
    } catch (SQLException e) {
        throw e;
    } finally {
        ...
    }
}
    
private PreparedStatement makeStatement(Connection c) throws SQLException{
    PreparedStatement ps = c.prepareStatement("delete from users");
    return ps;
}

3.2.3 템플릿 메소드 패턴의 적용

  • 템플릿 메소드는 상속을 통해 기능을 확장해서 사용하는 패턴임
  • 변하지 않는 부분은 슈퍼 클래스에 두고 변하는 부분은 추상 메소드를 정의해 둬서 서브 클래스에서 오버라이드하여 새롭게 정의하도록 사용하면 됨
  • UserDao 클래스를 추상 클래스로 만들고 변하는 부분인 makeStatement()를 추상 메서드로 해서 상속을 받을 클래스를 만들면 됨
  • 하지만, 템플릿 메소드 패턴으로 접근하면 DAO 로직마다 상속을 통해 새로운 클래스를 만들어야 하므로 단점이 매우 큼

public abstract class UserDao {
    ...
    public abstract PreparedStatement makeStatement(Connection c) throws SQLException;
}

public class UserDaoDeleteAll extends UserDao{
    @Override
    public PreparedStatement makeStatement(Connection c) throws SQLException {
        PreparedStatement ps = c.prepareStatement("delete from users");
        return ps;
    }
}

3.2.4 전략 패턴의 적용

  • 전략 패턴은 인터페이스를 사용하여 템플릿 메소드 패턴보다 유연함
  • 오브젝트를 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하는 패턴
  • 확장 기능은 인터페이스를 통해 외부의 독립된 전략 클래스에 위임
  • 전략 패턴의 구조에 따라 PreparedStatement를 만들어줄 인터페이스를 호출하면 됨
  • 그림 3-2에서 변하지 않는 부분은 contextMethod() 부분임
  • 하지만, 현재는 UserDao가 클래스 레벨에서 이미 구체적으로 어떤 전략을 쓸지(new DeleteAllStatement()) 고정되어 있기 때문에 클라이언트에게 책임을 넘김

public interface StatementStrategy {
    PreparedStatement makePrepareStatement(Connection c) throws SQLException;
}

public class DeleteAllStatement implements StatementStrategy{
    @Override
    public PreparedStatement makePrepareStatement(Connection c) throws SQLException {
        PreparedStatement ps = c.prepareStatement("delete from users");
        return ps;
    }
}

public void deleteAll() throws SQLException {
        PreparedStatement ps = null;
        Connection c = null;
        try {
            c = dataSource.getConnection();
            // UserDao가 구체적인 전략을 구현하고 있음(이러면 안됨)
            StatementStrategy strategy = new DeleteAllStatement();
            ps = strategy.makePrepareStatement(c);
            ps.executeUpdate();
        } catch (SQLException e) {
            throw e;
        } finally {
            ...
        }
    }

3.2.5 DI 적용을 위한 클라이언트/컨텍스트 분리

  • 클라이언트가 어떤 전략을 사용할지 결정(new DeleteAllStatement())하고, 해당 결과를 컨텍스트(UserDao)에게 전달
  • 결국 이 구조에서 전략 오브젝트 생성과 컨텍스트로의 전달을 담담하는 책임을 분리하는 것이 ObjectFactory이며, 이를 일반화한 것이 DI임
  • 결국 DI란, 이러한 전략 패턴의 장점을 일반적으로 활용할 수 있도록 만든 구조임
  • 제공받은 전략 오브젝트는 PreparedStatement 생성이 필요한 시점에 호출해서 사용

public void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException {
    PreparedStatement ps = null;
    Connection c = null;
    try {
        c = dataSource.getConnection();
        ps = stmt.makePrepareStatement(c);
        ps.executeUpdate();
    } catch (SQLException e) {
        throw e;
    } finally {
        ...
    }
}

3.2.6 마이크로 DI

  • DI의 가장 중요한 개념은 제 3자의 도움을 통해 두 오브젝트 사이의 유연한 관계가 설정되도록 하는 것임
  • 일반적인 DI는 두개의 오브젝트, DI 컨테이너, 클라이언트라는 4개의 오브젝트 사이에서 발생됨
  • 원시적인 전략 패턴의 경우 클라이언트가 DI 컨테이너의 역할도 함
  • 또한, 클라이언트와 전략(의존 오브젝트)가 결합될 수도 있고, 두개의 오브젝트 모두 하나의 클래스 안에 담길 수도 있음
  • DI 가 매우 작은 단위의 코드와 메소드 사이에 발생하기도 함
  • 얼핏보면 DI 같아 보이진 않지만, 세밀게 관찰해 보면 작은 단위의 DI가 발생하고 있음
  • 이렇게 DI의 장점을 단순화해서 IoC 컨테이너의 도움없이 코드 내에서 적용한 경우를 마이크로 DI 라고 함
  • 마이크로 DI는 코드에 의한 DI 란 의미로, 수동 DI라고 부를 수 있음