마지막소스 첨부는

자 이제 마지막 리팩토링이네영..

책에선 제네릭을 이용한 다양한 형태로의 변환이 되는 자유로운 팩토링을 보였는데 정말 새롭고 유용한 방법인것 같아서 강추!!

암튼 소스는 다음과 같습니다..

만약 String값을 이용해서 덧붙이는 기능을 사용하고자 할때에는 이런식으로 바꿔주면 되는군요.

우선 Test해볼 메소드입니다.(여기서 파일안에 있는 숫자를 순서대로 덧붙이는 메소드입니다)

@Test public void concatenateOfNumber() throws IOException{
String concateStr = calculator.concatenateString(filepath);
assertThat(concateStr,is("1243"));
}

public class Calculator {
public <T> T lineReadTempleter(String filepath,LineCallback<T> callback,T initVal) throws IOException{
BufferedReader br = null;
try {
   br = new BufferedReader(new FileReader(filepath));
   String line = null;
   T res = initVal;
   
   while( (line = br.readLine()) != null ){
    res = callback.doSomethingWithLine(line, res);
   }
   return res;
} catch (IOException ie) {
// TODO: handle exception
ie.printStackTrace();
} catch (Exception e){
e.printStackTrace();
} finally {
if(br!=null){
try {
br.close();
} catch (Exception e2) {
// TODO: 무시
}
}
}
return null;
}
public Integer calcSum(String filepath) throws IOException{
LineCallback<Integer> callback = 
new LineCallback<Integer>(){
@Override
public Integer doSomethingWithLine(String line, Integer value) {
// TODO Auto-generated method stub
return Integer.parseInt(line) + value;
}
};
return this.lineReadTempleter(filepath, callback, 0);
}

public int calcMultiply(String filepath) throws IOException{
//TODO Auto-generated method stub
LineCallback<Integer> callback = 
new LineCallback<Integer>(){
@Override
public Integer doSomethingWithLine(String line, Integer value) {
// TODO Auto-generated method stub
return Integer.parseInt(line) * value;
}
};
return this.lineReadTempleter(filepath, callback, 1);
}
//주어진 String을 합치는 메소드
public String concatenateString(String filepath) throws IOException{
LineCallback<String> callback = 
new LineCallback<String>(){
@Override
public String doSomethingWithLine(String line, String value) {
// TODO Auto-generated method stub
return value + line;
}
};
return this.lineReadTempleter(filepath, callback, "");
}
}

여기서 주의해서 봐야할껀 템플릿 메소드에 T라고 제네릭코드 가 들어간다는 점..

암튼 참 도움 많이 되는 책인것 같습니다..

토비의 스프링 화이팅!!
3단계에서 보면 calc()랑 multiply()부분에서

while문에서 가져오는 부분이 중복된다는 걸 볼수 있다.

이걸 템플릿/콜백 패턴으로 중복 제거하면(어디까지 갈것인가..두둥..==ㅣ)

public class Calculator {
public Integer lineReadTempleter(String filepath,LineCallback callback,int initVal) throws IOException{
BufferedReader br = null;
try {
   br = new BufferedReader(new FileReader(filepath));
   String line = null;
   Integer res = initVal;
   
   while( (line = br.readLine()) != null ){
    res = callback.doSomethingWithLine(line, res);
   }
   return res;
} catch (IOException ie) {
// TODO: handle exception
ie.printStackTrace();
} catch (Exception e){
e.printStackTrace();
} finally {
if(br!=null){
try {
br.close();
} catch (Exception e2) {
// TODO: 무시
}
}
}
return 0;
}
public Integer calcSum(String filepath) throws IOException{
LineCallback callback = 
new LineCallback(){
@Override
public Integer doSomethingWithLine(String line, Integer value) {
// TODO Auto-generated method stub
return Integer.parseInt(line) + value;
}
};
return this.lineReadTempleter(filepath, callback, 0);
}

public int calcMultiply(String filepath) throws IOException{
//TODO Auto-generated method stub
LineCallback callback = 
new LineCallback(){
@Override
public Integer doSomethingWithLine(String line, Integer value) {
// TODO Auto-generated method stub
return Integer.parseInt(line) * value;
}
};
return this.lineReadTempleter(filepath, callback, 1);
}
}

이때 콜백으로 처리된 인터페이스는 다음과 같다.

public interface LineCallback {
Integer doSomethingWithLine(String line,Integer value);
}

흠냐...대단하단 말밖에 안나온다...^^;

계속해서 리팩토링 되는 것 같다...(소름끼침..==ㅣ);
템플릿/콜백 패턴은 간단한 정의로는 중복되는 코드들을 인터페이스등을 통한 추출로 인하여 코드의 간결함과 효율성을 높이는 데 있다고 한다.

여기 예제에서는 파일입출력시 try/catch/finally등 자원반납에 관해서 모든 메소드가 중복되고 있다.

그래서 그 부분을 빼고자 하는 것이다.

암튼 적용한 다음의 소스는 다음과 같다. 
(참고 : @Before는 junit실행시 서론, @Test는 본론, @after는 결론) 

JUnit는 main메소드가 없어도 테스트를 실행함..^^

CalcSumTest.java

public class CalcSumTest {
Calculator calculator;
String filepath;
@Before public void setUp(){
calculator = new Calculator();
filepath = getClass().getResource("numbers.txt").getPath();
}
@Test public void sumOfNumber() throws IOException{
int sum = calculator.calcSum(filepath);
System.out.println(sum);
assertThat(sum,is(10));
}
}

그리고 템플릿 메소드와 계산메소드가 있는 계산 클래스
public class Calculator {
public Integer calcTemplete(String filepath,BufferedReaderCallback callback) {
BufferedReader br = null;
int result = 0;
try {
   br = new BufferedReader(new FileReader(filepath));
 
   result = callback.doSomethingWithReader(br);
} catch (IOException ie) {
// TODO: handle exception
ie.printStackTrace();
} catch (Exception e){
e.printStackTrace();
} finally {
if(br!=null){
try {
br.close();
} catch (Exception e2) {
// TODO: 무시
}
}
}
return result;
}
public Integer calcSum(String filepath){
BufferedReaderCallback callback = 
new BufferedReaderCallback(){
@Override
public Integer doSomethingWithReader(BufferedReader br){
// TODO Auto-generated method stub
int sum = 0;
try {
String line = "";
while( (line = br.readLine()) != null ){
sum += Integer.parseInt(line);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return sum;
}
};
return calcTemplete(filepath,callback);
}
}

그리고 콜백 인터페이스

public interface BufferedReaderCallback {
Integer doSomethingWithReader(BufferedReader br);
}

그럼 만약 곱하기를 추가한다고 가정하면 어떻게 하면 될까?

참 간단해진다.

우선 테스트 코드 하나 넣고
@Test public void mutiplyOfNumber() throws IOException{
int multiply = calculator.calcMultiply(filepath);
assertThat(multiply,is(24));
}

그다음 SUM이랑 비슷한 메소드만 카피 앤 패스트 하면 된다.
public int calcMultiply(String filepath) {
// TODO Auto-generated method stub
BufferedReaderCallback callback = 
new BufferedReaderCallback(){
@Override
public Integer doSomethingWithReader(BufferedReader br){
// TODO Auto-generated method stub
int multiply = 1;
try {
String line = "";
while( (line = br.readLine()) != null ){
multiply *= Integer.parseInt(line);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return multiply;
}
};
return calcTemplete(filepath,callback);
}


package springbook.learningtest.template;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Calculator {
public Integer calcSum(String filepath) {
BufferedReader br = null;
int sum = 0;
try {
br = new BufferedReader(new FileReader(filepath));
String line = null;
while( (line=br.readLine())!=null ){
sum += Integer.parseInt(line);
}
br.close();
} catch (IOException ie) {
// TODO: handle exception
ie.printStackTrace();
} catch (Exception e){
e.printStackTrace();
} finally {
if(br!=null){
try {
br.close();
} catch (Exception e2) {
// TODO: 무시
}
}
}
return sum;
}
}

출처 : 1400페이지짜리 '토비의 스프링' 서적
간단한 파일 입출력 리펙토링 과정을 보여준다.

우선 기본적으로 알아야 하는 건 JUnit 방법이다..

검색해보면 금방 알수 있으므로 해보삼^^

실제 계산하는 Calculator.java

package springbook.learningtest.template;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Calculator {
public Integer calcSum(String filepath) throws IOException{
BufferedReader br = new BufferedReader(new FileReader(filepath));
int sum = 0;
String line = null;
while( (line=br.readLine())!=null ){
sum += Integer.parseInt(line);
}
br.close();
return sum;
}
}

이걸 이용하는 구현 클래스는 CalcSumTest.java

여기서 빨간부분을 주의해서 보도록..

package springbook.learningtest.template;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.io.IOException;

import org.junit.Test;

public class CalcSumTest {
@Test
public void sumOfNumber() throws IOException{
Calculator calculator = new Calculator();
int sum = calculator.calcSum(getClass().getResource("numbers.txt").getPath());
assertThat(sum,is(10));
}
}


여기서 문제점은 try/catch/finally를 안넣어서 중간에 예외가 발생하면 자원반납을 안해줘서 문제가 됨.

그래서 2단계에선 묶어줌..
출처 : http://blog.naver.com/lriberio?Redirect=Log&logNo=80068562529

Spring Framework 다운로드

 

(2008년 11월 4일 기준)

 

1. http://www.springsource.org/download 로 간다.

 

2. http://www.springsource.com/download?project=Spring%20Framework 로 이동된다.

   아래로 조금 스크롤 => Community Download [Download Now] 를 클릭한다.

3. [No thanks, take me to the download] 클릭

4. spring-framework-2.5.6-with-dependencies.zip 클릭

5. 저장 후 압축 해제 아래 경로에 있는 라이브러리 추가 할 것

  - %SRPING_HOME%\dist\modules

 

 
6. eclipse 안에 build path 추가
 
 
 
7. 해당 프로젝트에 추가한 라이브러리 설정
 
[Spring] 이클립스에 스프링 개발환경 셋팅 Spring

2010/08/25 11:25

복사 http://skyunoe.blog.me/20112368374

Java JDK 설치 : http://java.sun.com (version 1.6.20)

Apache Tomcat 설치 : http://apache.org  (version 7)

Eclipse IDE 설치 ; http://www.eclipse.org (Eclipse IDE for Java EE Developers 3.6 package)

Spring Framework 설치 : http://www.springsource.com/download/community?project=Spring%20Framework (version 3.0.4)

 

1. Help > Install New Software ... 메뉴로 이동하여 클릭해 줍니다.

 

그럼 아래와 같은 다이얼로그가 생성 됩니다.

 

2. 좌측 상단의 "Add..." 버튼을 클릭하면 Add Repository 다이얼로그가 나옵니다.

   여기에 아래 그림과 같이 적어 줍니다.

   Spring 업데이트 URL은 http://springide.org/updatesite 입니다.

 

 

3. Work width 콤보박스에서 2번에서 등록했던 항목을 선택하면 다이얼로그 중앙에 설치할 내역이 보여 집니다.

   그림과 같이 Core, Extensions(Incubation) , Extensions, Resources 를 선택하고 "Next" 버튼을 누릅니다.

   

 

4. 앞서 선택한 항목에 대한 Detail한 항목이 나옵니다 계속 "Next" 버튼을 눌러줍니다.

 

5. 약관에 동의하고 "Finish" 버튼을 누르면 설치가 시작됩니다.

 

6. 설치가 모두 끝나면 Eclipse를 재시작 하라는 메시지가 나옵니다.  재시작 해주시면 됩니다.

 

7. 이클립스 상단의 File > New > Project 메뉴를 눌러봅니다. 프로젝트 생성란에 Spring이 추가되었습니다.


전체 소스는 밑에..하지만 책과는 좀 다름..내 나름대로 끝에 마음대로 짠것임..T.T

위 기능을 분리해서 담당할 클래스를 만들어보자.

우선 UserDaoTest 클래스다

public class UserDaoTest { 
 public static void main(String[] args) {
  UserDao user = new DaoFactory().userDao();
 }
}


Factory에서 연결될 오브젝트를 정의를 한다.
public class DaoFactory { 
 public UserDao userDao(){
  ConnectionMaker connectionMaker = new DConnectionMaker();  
  UserDao dao = new UserDao(connectionMaker);  
  return dao;
 }
}

만약 여러개의 DAO가 있다고 하면

Factory에서
public UserDao getUserDao(ConnectionMaker connectionMaker){
   
  return new UserDao(connectionMaker);  
  
 }

public AccountDao accountDao(){
return new AccountDao(new DConnectionMaker());
}
....


DConnectionMaker에선 따로 커넥션 부분만 정의해서 하면 된다.

마지막 Client에서는

 //main
 public static void main(String[] args) throws ClassNotFoundException,SQLException{
  
  UserDao dao = new DaoFactory().getUserDao(new MyConnectionMaker());
  
  User user = new User();
  user.setId("hello");
  user.setName("김말통");
  user.setPassword("1234");
  
  dao.add(user);
  
  User user2 = dao.get("hello");
  System.out.println("id:"+user2.getId());
  System.out.println("name:"+user2.getName());
  System.out.println("password:"+user2.getPassword());
  
  
 }
}

이런식으로 UserDao를 사용하면 된다.
그럼 아예 이런식으로 독립시켜버리자.

UserDaoTest라는 클래스를 만들어서 여기안에서 실행및 테스트를 할수 있다.

UserDao의 생성자
public UserDao(ConnectionMaker connectionMaker){
  //초기화 ID 및 PASSWORD
  this.connectionMaker = connectionMaker;
 }

public class UserDaoTest {
 
 public static void main(String[] args) {

  ConnectionMaker connectionMaker = new DConnectionMaker();
  
  UserDao dao = new UserDao(connectionMaker);  
 }

이렇게 나누어 지면 거의 끝이다.

하지만 위의 코드에서 문제점은 UserDaoTest라는 클라이언트 클래스는 예전 UserDao가 맡던 connectionMaker 구현클래스 사용할지 안할지 기능까지 맡고 있는 셈이다.

원래목적이 UserDaoTest는 UserDao의 기능이 잘 동작하는 지만 테스트할려고 만든거다.

그래서 또 하나의 관심을 줌으로써 또 한번 더 리팩토링을 해야 한다.

다음 마지막 과정은 팩토리 과정이다.

-토니의 스프링 책참고
위 경우의 해결책은 두개의 클래스가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 느슨한 연결고리를 만들어주는 것이다.추상화란 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업이다.자바가 추상화를 위해 제공하는 가장 유용한 도구는 바로 인터페이스이다.

인터페이스의 최대장점은 추상화가 되는 동시에 실제 이걸이용해서 구현하는 클래스는 밑 바탕 클래스를 몰라도 된다.. 이 바탕 클래스가 별 스트립쇼를 부리든 난동을 부리든.. 인터페이스만 신경쓰면 된다.

아래는 인터페이스 설정이다.
public interface ConnectionMaker {
public Connection makeNewConnection() throws ClassNotFoundException,SQLException;
}

그리고 이걸 실제 독자적으로 구현하는 고객 클래스는 

-D사
public class DConnectionMaker implements ConnectionMaker {

@Override
public Connection makeNewConnection() throws ClassNotFoundException,
SQLException {
return null;// D사가 개별적으로 생성한 커넥션;
}

}
-N사
public class NConnectionMaker implements ConnectionMaker {

@Override
public Connection makeNewConnection() throws ClassNotFoundException,
SQLException {
return  null;// N사가 개별적으로 생성한 커넥션;
}

}

이고..바탕클래스에선 이 인터페이스를 이용해 이렇게 작성할 수 있다.하지만 문제점이 있는데 빨간 줄로 된 걸 보면 된다.

-바탕클래스 : UserDao

private ConnectionMaker connectionMaker;
public UserDao(){
//초기화 ID 및 PASSWORD
connectionMaker = new DConnectionMaker();
}
public void add(User user) throws ClassNotFoundException,SQLException{
Connection c = connectionMaker.makeNewConnection();
.....

저 코드를 보면 결국 클라이언트 구현클래스를 이용해서 UserDao를 구현하고 있다.

결국 UserDao의 완전 독립이라는 나의 최종목표에서 벗어나 버렸다.

그래서 다음 단계인 "관계설정 책임의 분리"에서 해결해보자.



앞에서 잠시 언급했듯이 다중상속이 허용되지 않는 자바에선 위와 같은 방법은 별로 좋지 못하다.

그래서 우선 Connection 를 주는 클래스를 아예 따로 떼어버리자.

그럼 상속받을 필요도 없고 그냥 생성해서 쓰면 그만이다. abstact일 필요도 없어진다.

자세한 코드는 다음과 같다.

Connection만 관리하는 클래스 추출

public class SimpleConnectionMaker {
public Connection makeNewConnection() throws ClassNotFoundException,SQLException{
return 만들어질 커넥션;
}
}

그럼 add와 get 메소드 안에서는 

Connection c = simpleConnectionMaker.makeNewConnection();

이 한줄로 간단히 예전과 똑같이 사용할 수 있다.

하지만..

이렇게 바뀌고 나면 제공해주고 있는 D사와 N사에서 작동안된다고 클레임 건다.

이유는 더이상 상속관계가 아니기 때문에 상속을 받아도 Connection 객체를 사용할 수 없다는 것이다.

그래서 "인터페이스의 도입"이 이루어진다.
1단계에서 메소드 추출을 이용했다.

만약 이 UserDao클래스가 인기가 많아 N사와 D사에 판매를 하는 경우
N사와 D사의 경우 각각 다른 DB을 쓰고 연결관리를 한다는 가정하에선

기존에 만들어 놓은 UserDao방식은 확장성에서 상당히 비효율적이다. 안에 메인클래스를 수정해야 함은 당연지사고 내가 고생해서 만든 UserDao클래스 극비문서를 남에게 보여줘야 한다는 문제점도 있다.

그래서 이번 단계에서는 내가 만든 UserDao클래스를 보호하면서 각기 다른 회사에 맞는 커넥션을 제공해줄수 있는 리팩토링이다.

여기에서 쓰이는 패턴은 템플릿 메소드 패턴(template method pattern:슈퍼클래스에 기본적인 로직의 흐름을 만들고 , 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드등으로 만든뒤 필요한 서브클래스에서 이러한 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법)

그리고 서브클래스에서 구체적인 오브젝트 생성방법을 결정하게 하는 것을 팩토리 메소드 패턴(factory method pattern)이라고도 한다. 

그럼 구체적인 예로는 다음과 같다.

1.클래스를 추상클래스로 변환한다.
public abstract class UserDao {
Connection c = getConnection();
.....

public abstract Connection getConnection() throws ClassNotFoundException,SQLException;

}
2.N사와 D사는 제공되어진 추상클래스에 제공해서 getConnection() 부분만 따로 정의를 한다.

public class DUserDao extends UserDao {

@Override
public Connection getConnection() throws ClassNotFoundException,
SQLException {
//N 사 DB connection 생성코드 작성
return 만들어진 Connection;
}

}

public class NUserDao extends UserDao {

@Override
public Connection getConnection() throws ClassNotFoundException,
SQLException {
//N 사 DB connection 생성코드 작성
return 만들어진 Connection;
}

}

하지만 여기에서 큰 문제점이 있다.

자바는 다중상속이 금지되어있다.

그래서 만약 N사나 D사의 구현클래스가 다른 클래스를 이미 상속받았다면 우찌할텐가..

그래서 다음단계에서 "DAO의 확장" 한단계 더 리팩토링을 해보자.

이전 UserDao 클래스에서 add() 안의 메소드를 보면 세가지 관심사항을 발견할 수 있다.

UserDao의 관심사항
1.DB와 연결을 위한 커넥션을 어떻게 가져올까라는 관심
2.사용자 등록을 위해 DB에 보낼 SQL문장을 담을 Statement를 만들고 실행하는 것
3.작업이 끝나면 사용한 리소스를 닫아주는 일

public void add(User user) throws ClassNotFoundException,SQLException{
Connection c = getConnection();
String sql = "insert into users(id,name,password) values(?,?,?)";
PreparedStatement ps = c.prepareStatement(sql);
                .....

아래 메소드를 추가함으로써 add와 get에서 중복되는 커넥션 부분을 추출했다.
private Connection getConnection() throws ClassNotFoundException,SQLException{
Class.forName("org.gjt.mm.mysql.Driver");
return DriverManager.getConnection("jdbc:mysql://localhost/springbook","root","1234");
}

이정도는 금방 이해가 될꺼라 본다...하지만 다음단계부터 중요해짐.^^
토비의 스프링3이란 책을 보고 공부하는 중..^^;

우선 처음부터 리팩토링에 대해서 간략하게 얘기하는데 좋은 내용이다..

그래서 한번 따라해봄.^^

우선 DB 커넥션 클래스 준비

user.java 파일안

package springbook.user.domain;

public class User {
String id;
String name;
String password;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

2. UserDao.java 파일
package springbook.user.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import springbook.user.domain.User;

public class UserDao {

public UserDao(){
//초기화 ID 및 PASSWORD
}
public void add(User user) throws ClassNotFoundException,SQLException{
Class.forName("org.gjt.mm.mysql.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook","root","1234");
String sql = "insert into users(id,name,password) values(?,?,?)";
PreparedStatement ps = c.prepareStatement(sql);
ps.setString(1, user.getId());
ps.setString(2,user.getName());
ps.setString(3,user.getPassword());
ps.execute();
ps.close();
c.close();
}
public User get(String id) throws ClassNotFoundException,SQLException{
Class.forName("org.gjt.mm.mysql.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook","root","1234");
String sql = "select * from users where id = ?";
   PreparedStatement ps = c.prepareStatement(sql);
   ps.setString(1, id);
   ResultSet rs = ps.executeQuery();
   rs.next();
   User user = new User();
   user.setId(rs.getString("id"));
   user.setName(rs.getString("name"));
   user.setPassword(rs.getString("password"));
   
   rs.close();
   ps.close();
   c.close();
   
   return user;
   
}
public static void  main(String[] args) throws ClassNotFoundException,SQLException{
UserDao dao = new UserDao();
/*
User user = new User();
user.setId("admin");
user.setName("tommmy");
user.setPassword("1234");
dao.add(user);
*/
User user2= dao.get("admin");
System.out.println(user2.getId()+","+user2.getName()+","+user2.getPassword());
}
}

테스트를 위해 메인하나 정의한 후 테스트완료함^^

그럼 이걸 가지고 하나씩 하나씩 리팩토링해보장..^^ 다 뭐를 위해? 스프링을 위해^^;;

+ Recent posts