토비의 스프링 3.3~3.5

임종혁·2024년 3월 4일

3.3 전략 패턴 add 추가

public class AddStatement implements StatementStrategy{
    User user;

    public AddStatement(User user){
        this.user  = user;
    }
    public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
        PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values (?,?,?)");
        ps.setString(1,user.getId());
        ps.setString(1,user.getName());
        ps.setString(1,user.getPassWord());
        return ps;
    }
}

허나 다음과 같을시 DAO 메소드 마다 새로운 StatementStrategy 구현 클래스를 만들어야하는 문제 발생

해결 방법 1. 로컬 클래스 사용
UserDao 클래스 안 내부 클래스로 정의 해버리기

 public void add2(User user) throws SQLException, ClassNotFoundException {
        class AddStatement implements StatementStrategy{
            User user;
            public AddStatement(User user){
                this.user = user;
            }
            public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values (?,?,?)");
                ps.setString(1,user.getId());
                ps.setString(1,user.getName());
                ps.setString(1,user.getPassWord());
                return ps;
            }
        }
        StatementStrategy st = new AddStatement(user);
        jdbcContextWithStatementStrategy(st);
    }

이러한 로컬클래스는 내부클래스이기 때문에 자신이 선언된 곳의 정보에 접근할 수 있다는 점이다.
AddStatement 는 User 정보가 필요한다.
이를 위해 생성자를 만들어 add() 메소드에 전달 하였지만 메소드내 AddStatement 클래스 정의시 번거로운 생성자 전달을 할 필요 없다.

다만 내부 클래스에서 외부 변수를 사용할때 외부 변수를 반드시 final 로 선언 해 줘야한다.

3.4.1 JdbcContext 의 분리

전략패턴의 구조로 보면 UserDao 메소드가 클라이언트고 , 익명 내부 클래스로 만들어 지는 것이 개별 전략이고 , jdbcContextWithStatementStrategy() 메소드는 컨택스트이다.

 public void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException,ClassNotFoundException{
        Connection c = null;
        PreparedStatement ps = null;

        try{
            c = connectionMaker.makeConnection();
            ps = stmt.makePreparedStatement(c);
            ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            if(ps !=null){
                try {
                    ps.close();
                }catch (SQLException e){

                }
            }
            if(c!=null){
                try {
                    c.close();
                }catch (SQLException e){

                }
            }
        }
    }

익명 클래스 사용시

 public void add3(final User user) throws SQLException, ClassNotFoundException {
      StatementStrategy st = new StatementStrategy(){

            public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values (?,?,?)");
                ps.setString(1,user.getId());
                ps.setString(1,user.getName());
                ps.setString(1,user.getPassWord());
                return ps;
            }
        };
    }

해당 메서드는 PreparedStatement를 공유 한다

허나 이 jdbcContextWithStatementStrategy() 는 다른 DAO 에서도 사용가능 하니 클래스 분리를 시켜 보겠다.

public class JdbcContext {
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    public void workWithStatementStrategy(StatementStrategy stmt) throws SQLException{
        Connection c = null;
        PreparedStatement ps = null;
        
        try{
            c = this.dataSource.getConnection();
            ps = stmt.makePreparedStatement(c);
            ps.executeUpdate();
        }catch (SQLException e){
            throw  e;
        }finally {
            if(ps!= null) {
                try {
                    ps.close();
                    
                }catch (SQLException e){
                    
                }
            }
            if(c != null){
                try{
                    c.close();
                }catch (SQLException e){
                    
                }
            }
        }
    }
}

다음과 같이 사용

private JdbcContext jdbcContext;
    public  void setJdbcContext(JdbcContext jdbcContext){
        this.jdbcContext = jdbcContext;
    }
public void deleteAll(final User user) throws  SQLException{
        this.jdbcContext.workWithStatementStrategy(new DeleteAllStatement());
    }


그럼 다음과 같은 형식을 띈다

코드를 이용한 수동 DI

UserDao 를 @Component를 통해 빈 화 시켰다.
허나 JdbcContext를 빈으로 등록하지 않았으므로 다른 누군가가 JdbcContext의 생성과 초기화를 책임져야한다.
이 JdbcContext는 UserDao 가 갖는것이 적당하다.
허나 JdbcContext는 다른 빈을 의존하고 있다. 빈이 아닌것은 빈을 의존할 수 없다는 문제가 발생

그렇다면 해결 법으로
UserDao가 JdbcContext가 의존하고 있는 빈을 대신 받도록 하면 된다.

public  void setJdbcContext(DataSource dataSource){
        this.jdbcContext = new JdbcContext();
        this.jdbcContext.setDataSource(dataSource);

        this.dataSource = dataSource;
    }

3.5 템플릿과 콜백

전략 패턴의 기본 구조에 익명 내부 클래스를 활용하는 방식을 템플릿/콜백 패턴 이라 부름
전략 패턴의 컨텍스트를 템플릿 익명 내부클래스를 오브젝트 콜백 이라 부름

여러개의 메소드를 가진 일반적인 인터페이스를 사용할 수 있는 전략 패턴의 전략과 달리 템플릿/콜백 패턴의 콜백은 보통 단일 메소드 인터페이스를 사용

하나의 템플릿에서 여러 종류의 전략을 사용해야한다면 하나 이상의 콜백 오브젝트를 사용할 수 있다.
콜백은 일반적으로 하나 메소드를 가진 인터페이스를 구현하는 익명 내부 클래스로 만들어진다.

일반적인 DI라면 템플릿에 인스턴스 변수를 만들어두고 사용할 의존 오브젝트를 수정자 메소드로 받아 사용
반면 템플릿/콜백 방식에서는 매번 메소드 단위로 사용할 오브젝트를 새롭게 전달한다는것이 특징
콜백 오브젝트가 내부 클래스로 자신을 생성한 클라이언 메소드 내의 정보를 직접 참조한다는 것도 템플릿 콜백의 고유 특징

클라이언트와 콜백이 강하게 결합된다는 면에서도 일반적인 DI와 다르다

템플릿 콜백 방식은 전략 패턴과 DI 의 장점을 익명 내부 클래스 사용전략과 결합한 독특한 활용법

템플릿 콜백

    public void add5(final User user) throws  SQLException{
        // jdbcConext 의존을 받고

        // 콜백 StatementStrategy 생성
        this.jdbcContext.workWithStatementStrategy(new StatementStrategy(){

            public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values (?,?,?)");
                ps.setString(1,user.getId());
                ps.setString(1,user.getName());
                ps.setString(1,user.getPassWord());
                return ps;
            }
        });
    }

고정된 작업 흐름을 갖고 있으면서 여기저기 자주 반복되는 코드가 있다면 중복되는 코드를 분리할 방법을 생각해보는 습관을 기르자

우선 일부작업을 필요에 따라 바꾸어 사용해야 한다면 인터페이스를 사이에 두고 분리하는 전략 패턴을 적용한다.
그런데 바뀌는 부분이 한 애플리케이션 안에서 동시 여러 종류가 만들어지면 템플릿 콜백 패턴을 적용해 보자

0개의 댓글