3장_템플릿

언젠간·2022년 8월 27일
0

토비의스프링

목록 보기
16/16

템플릿이란?

  • 일정한 패턴으로 유지되는 특성을 가진 부분을, 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는 방법
  • 확장에는 자유롭게 열려 있고 변경에는 굳게 닫혀 있다는 개방 폐쇄 원칙(OCP)과 관련 있음

일정한 패턴을 가진 코드

  • 일반적인 메소드 추출. 분리시킨 메소드의 재사용이 불가능 함
	public void deleteAll() throws SQLException{
		
		Connection c = null;
		PreparedStatement ps = null;
		
		try {
			c = dataSource.getConnection();

			ps = makeStatement(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 PreparedStatement makeStatement(Connection c) throws SQLException{
		PreparedStatement ps;
		ps = c.prepareStatement("delete from users");
		return ps;
	}
  • 템플릿 메소드 패턴
    • 상속을 통해 기능을 확장해서 사용하는 부분
    • 변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 추상메소드로 둬서 서브클래스에서 오버라이드하여 새롭게 정의해 쓰도록
    • 단점 : DAO 로직마다 상속을 통해 새로운 클래스를 만들어야 함. 즉 메소드 한 개 마다 서브클래스 한 개 필요함
        public abstract class UserDao { //추상 클래스로 변경
        	
            //추상 메소드
            abstract protected PreparedStatement makeStatement(Connection c) throws SQLException; 

            public void deleteAll() throws SQLException{

                Connection c = null;
                PreparedStatement ps = null;

                try {
                    c = dataSource.getConnection();

                    ps = makeStatement(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) {} }
                }

            }
        }
        
        //상속하는 서브 클래스
        public class UserDaoDeleteAll extends UserDao{
          @Override
          protected PreparedStatement makeStatement(Connection c) throws SQLException {
              // TODO Auto-generated method stub
              PreparedStatement ps = c.prepareStatement("delete * from users");
              return ps;
		  }
        }
  • 전략 패턴
    • 오브젝트를 아예 둘로 분리하고, 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만듬
    • 변하지 않는 부분 = contextMethod() = JDBC를 이용해 DB를 업데이트하는 작업이라는 변하지 않는 맥락(context)
    • 전략(PreparedStatement 생성)을 인터페이스로 만들어두고, 인터페이스의 메소드를 통해 PreparedStatement 생성 전략 호출
    • 즉, 변하지 않는 부분인 맥락(context)을 변하는 부분인 전략(stategy)로부터 분리해야 함
      public class UserDao {
          public void deleteAll() throws SQLException{

              StatementStrategy st = new DeleteAllStatement(); //선정한 전략 클래스의 오브젝트 생성
              jdbcContextWithStatementStrategy(st); //컨텍스트 호출. 전략 오브젝트 전달

          }

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

              try {
                  c = 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) {} }
              }
          }
      }
      
      public interface StatementStrategy {
          PreparedStatement makePreparedStatement(Connection c) throws SQLException;
      }
      
      public class DeleteAllStatement implements StatementStrategy{

          @Override
          public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
              // TODO Auto-generated method stub
              PreparedStatement ps = c.prepareStatement("delete from users");
              return ps;
          }

      }
  • 전략 패턴 또한 DAO 메소드마다 새로운 StatementStrategy 구현 클래스(ex. DeleteAllStatement)를 만들어야 하며, User와 같은 부가적인 정보를 전달해야 하는 경우 생성자와 인스턴스 변수를 만들어야 하는 단점이 있음.

    • 해결방법 : 로컬 클래스
  • 로컬 클래스

    • StatementStrategy 전략 클래스를 매번 독립된 파일로 만들지 말고, UserDao 클래스 안에 내부 클래스로 정의하면 됨. UserDao 밖에서는 사용되지 않기 때문.
    • 번거롭게 생성자를 통해 User 오브젝트를 전달해줄 필요 없음. 직접 접근 가능함
    • 내부 클래스에서 외부의 변수를 사용할 때는 외부 변수는 반드시 final로 선언해줘야 함.
      public void add(final User user) throws ClassNotFoundException, SQLException{
      				class AddStatement implements StatementStrategy{
      			
      					@Override
      					public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
      						// TODO Auto-generated method stub
      						PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");
      				
      						ps.setString(1, user.getId());
      						ps.setString(2, user.getName());
      						ps.setString(3, user.getPassword());
      				
      						return ps;
      					}
      				}
      		
      				StatementStrategy st = new AddStatement(); //User 오브젝트 전달 불필요
      				jdbcContextWithStatementStrategy(st);
      		
      			}
  • 익명 내부 클래스 적용

    • 익명 내부 클래스는 선언과 동시에 오브젝트를 생성. 클래스 이름 생략.

    • 클래스를 재사용할 필요 없을 때 사용

      public void add(final User user) throws ClassNotFoundException, SQLException{
      		
      				jdbcContextWithStatementStrategy(new StatementStrategy() {
      			
      					@Override
      					public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
      						// TODO Auto-generated method stub
      						PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");
      				
      						ps.setString(1, user.getId());
      						ps.setString(2, user.getName());
      						ps.setString(3, user.getPassword());
      				
      						return ps;
      					}
      				});
      		
      			}
      
  • JDBC의 일반적인 작업 흐름을 담고 있는 jdbcContextWithStatementStrategy() 분리. 모든 DAO가 사용할 수 있게끔.

	public class JdbcContext { //다른 DAO에서도 사용 가능하다
      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.getConection();
              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) {}}
          }

      }
	}
    
    public class UserDao {

      private DataSource dataSource;
      // private ConnectionMaker connectionMaker; //초기에 설정하면 사용 중에는 바뀌지 않는 읽기전용 인스턴스
      // 변수

      public void setDataSource(DataSource dataSource) {
          this.dataSource = dataSource;
      }

      private JdbcContext jdbcContext;

      public void setJdbcContext(JdbcContext jdbcContext) {
          this.jdbcContext = jdbcContext;
      }

      public void deleteAll() throws SQLException {
          this.jdbcContext.workWithStatementStrategy(new StatementStrategy() {

              @Override
              public PreparedStatement makePreparedStatement(Connection c) throws SQLException {

                  return c.prepareStatement("delete from users");
              }
          });
      }

      public void add(final User user) throws ClassNotFoundException, SQLException {

          this.jdbcContext.workWithStatementStrategy(new StatementStrategy() {

              @Override
              public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                  // TODO Auto-generated method stub
                  PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");

                  ps.setString(1, user.getId());
                  ps.setString(2, user.getName());
                  ps.setString(3, user.getPassword());

                  return ps;
              }
          });

      }
profile
코딩왕이될사나이

0개의 댓글