1.2.1 관심사의 분리
관심사의 분리
변하는 것과 변하지 않는 것이 있는 현실과 달리 객체지향 세계는 모든 것이 변한다.
변수나 오브젝트 필드 값이 변한다는게 아니라 오브젝트에 대한 설계와 이를 구현한 코드가 변한다.
소프트웨어 개발에서 끝이란 개념은 없다. 사용자의 비지니스 프로세스와 그에 따른 요구사항은 끊임없이 바뀌고 발전한다.
애플리케이션이 기반을 두고 있는 기술도 시간이 지남에 따라 바뀌고, 운영되는 환경도 변화한다.
애플리케이션이 더 이상 사용되지 않아 폐기처분될 때가 돼야 변화는 중지한다.
그래서 개발자가 객체를 설계할 때 가장 염두에 둬야 할 사항은 바로 미래의 변화를 어떻게 대비할 것인가이다.
객체지향 기술 자체가 지니는, 변화에 효과적으로 대처할 수 있다는 기술적인 특징이 있어 미래에 보다 적극적으로 대비할 수 있다.
객체 지향 기술은 흔히 실세계를 최대한 가깝게 모델링해낼 수 있기 떄문에 의미가 있다고 여겨진다.
하지만 그보다는 객체지향 기술이 만들어내는 가상의 추상세계 자체를 효과적으로 구성할 수 있고, 이를 자유롭고 편리하게 변경, 발전, 확장시킬 수 있다는데 더 의미가 있다.
미래를 준비에 있어 가장 중요한 과제는 변화에 대비하는 가장 좋은 대책은 변화의 폭을 최소한으로 줄여주는 것이다.
변화의 폭을 최소한으로 줄이고 그 변경이 다른 곳에 문제를 일으키지 않게 할 수 있는 것은 분리와 확장을 고려한 설계로 해결할 수 있다.
먼저 분리에 대해 생각해보자.
변경에 대한 요청이 "DB를 오라클에서 MySQL로 바꾸면서, 웹 화면의 레이아웃을 다중 프레임 구조에서 단일 프레임에 Ajax를 적용한 구조로 바꾸고, 매출이 일어날 때 지난달 평균 매출액보다 많으면 감사 시스템의 정보가 웹 서비스로 전송되는 동시에 로그의 날짜 포멧을 6자리에서 Y2K를 고려해 8자리로 바꿔라"는 식으로 발생하지는 않는다. 무슨 얘기냐 하면 모든 변경과 발전은 한 번에 한 가지 관심사항에 집중해서 일어난다는 뜻이다.
문제는, 변화는 대체로 집중한 한 가지 관심에 대해 일어나지만 그에 따른 작업은 한 곳에 집중되지 않는 경우가 많다는 점이다. 단지 DB 접속용 암호를 변경하려고 DAO클래스 수백 개를 모두 수정해야 한다면? 트랜잭션 기술을 다른 것으로 바꿨다고 비즈니스 로직이 담긴 코드의 구조를 모두 변경해야 한다면? 또는 다른 개발자가 개발한 코드의 변경이 일어날 때마다 내가 만든 클래스도 함께 수정을 해줘야한다면 얼마나 끔찍할지 모르겠다.
변화가 한 번에 한 가지 관심에 집중돼서 일어난다면, 우리가 준비해야 할 일은 한가지 관심이 한 군데에 집중되게 하는 것이다.
즉 관심이 같은 것끼리는 모으고, 관심이 다른 것은 따로 떨어져 있게 하는 것이다.
프로그래밍의 기초 개념 중에 관심사의 분리(Separation of Concerns)라는 것이 있다. 이를 객체지향에 적용해보면, 관심이 같은 것 끼리는 하나의 객체 안으로 또는 친한 객체로 모이게 하고, 관심이 다른 것은 가능한 한 따로 떨어져서 서로 영향을 주지 않도록 분리하는 것이라고 생각할 수 있다.
모든 것을 뭉뚱그려서 한데 모으는 편이 처음엔 쉽고 편하다. 그런데 언젠가는 그 뭉쳐 있는 여러 종류의 관심사를 적절하게 구분하고 따로 분리하는 작업을 해줘야만 할때가 온다. 관심사가 같은 것끼리 모으고 다른 것은 분리해줌으로써 같은 관심에 효과적으로 집중할 수 있게 만들어주는 것이다.
1.2.2 커넥션 만들기의 추출
UserDao의 구현된 메소드를 다시 살펴보자.
add() 메소드 하나에서만 적어도 세 가지 관심사를 발견할 수 있다.
UserDao의 관심사항
- 첫째는 DB와 연결을 위한 커넥션을 어떻게 가져올까라는 관심
- 어떤 DB를 쓰고, 어떤 드라이버를 사용하고, 어떤 로그인 정보를 쓰는데 그 커넥션을 생성하는 방법 등등
- 둘째는 사용자 등록을 위해 DB에 보낼 SQL 문장을 담을 Statement를 만들고 실행하는 것이다.
- 파라미터로 넘어온 사용자 정보를 Statement에 바인딩시키고, Statement에 담긴 SQL을 DB를 통해 실행시키는 방법
- 셋째는 작업이 끝나면 사용한 리소스인 Statement와 Connection 오브젝트를 닫아 소중한 공유 리소스를 시스템에 돌려주기
+ 예외 상황에 대한 처리 - 예외 상황에 공유 리소스 반환 관련 설정
처리해야하는 1순위는 DB 연결을 위한 Connection 오브젝트를 가져오는 부분이다.
DB Connection을 가져오는 부분이 add(), get() 메소드에 들어있고 중복되어 있음
이런게 쌓여서 스파게티 코드가 된다.
중복 코드의 메소드 추출
가장 먼저 할 일은 커넥션을 가져오는 중복된 코드를 분리하는 것이다.
중복된 DB 연결 코드를 getConnection()이라는 이름의 독립적인 메소드로 만들어둔다.
각 DAO메소드에는 이렇게 분리한 getConnection 메소드를 호출해 DB 커넥션을 가져오게 만든다.
리스트 1-4 getConnection() 메소드를 추출해서 중복을 제거한 UserDao
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
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());
ps.executeUpdate();
ps.close();
c.close();
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
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;
}
private Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook?serverTimezone=Asia/Seoul&useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=true&useSSL=false", "spring", "book");
return c;
}
지금은 UserDao 클래스의 메소드가 두 개이지만 나중에 메소드가 2,000개쯤 된다고 가정해보자.
DB 연결과 관련된 부분에 변경이 일어났을 경우, 예를 들어 DB 종류와 접속 방법이 바뀌어서 드라이버 클래스와 URL이 바뀌었다거나, 로그인 정보가 변경돼도 앞으로는 getConnection()이라는 한 메소드의 코드만 수정하면 된다.
관심의 종류에 따라 코드를 구분해놓았기 때문에 한 가지 관심에 대한 변경이 일어날 경우 그 관심이 집중되는 부분의 코드만 수정하면 된다.
관심이 다른 코드가 있는 메소드에 영향을 주지 않을 뿐더러, 관심 내용이 독립적으로 존재해 수정도 간단해졌다.
변경사항에 대한 검증: 리팩토링과 테스트
그런데 앞에서 이미 UserDao의 기능이 잘 동작한다는 것을 테스트해봤다.
하지만 코드를 수정한 후에는 기능에 문제가 없다는게 보장되지 않는다.
다시 검증이 필요하다. 변경된 UserDao의 기능이 변경하기 전과 동일한지 확인해보려면 어떻게 해야 할까?
방법은 간단하다. 앞에서 만들어놨던 main() 메소드를 이용해 테스트해보면 된다.
현재 main() 메소드 테스트에는 한 가지 단점이 있는데,
main() 메소드를 여러번 실행하면 두 번째부터는 무조건 예외가 발생한다는 것이다.
테이블의 기본키인 id 값이 중복되기 때문이다.
따라서 main() 메소드 테스트를 다시 실행하기 전에 User 테이블의 사용자 정보를 모두 삭제해줘야 한다.
그리고 다시 UserDao 클래스(static main 메소드)를 실행해보자. 아마 문제없이 이전과 동일한 결과가 출력될 것이다.
예외가 발생했다면 변경 작업에 문제가 있는 것이니 문제가 있는 코드를 찾아 수정하자.
물론 수정한 코드의 검증은 다시 main() 메소드를 실행해서 처음과 같은 결과가 화면에 출력되는지 확인해보면 된다.
이 작업에는 기능에는 영향을 주지 않으면서 코드의 구조만 변경한다.
기능이 추가되거나 바뀐 것은 없지만 UserDao는 이전보다 훨씬 깔끔해졌고 미래의 변화에 좀 더 손쉽게 대응할 수 있는 코드가 됐다. 이런 작업을 리팩토링(refactoring)이라고 한다. 또한 위에서 사용한 getConnection()이라고 하는 공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 것을 리팩토링에서는 추출(extract method)기법이라고 부른다.
리팩토링은 객체지향 개발자라면 반드시 익혀야 하는 기법이다.
리팩토링
리팩토링은 기존의 코드를 외부의 동작방식에는 변화 없이 내부 구조를 변경해서 재구성하는 작업 또는 기술을 말한다.
리팩토링을 하면 코드 내부의 설계가 개선되어 코드를 이해하기가 더 편해지고 변화에 효율적으로 대응할 수 있다.
결국 생산성은 올라가고, 코드의 품질은 높아지며, 유지보수하기 용이해지고, 견고하면서도 유연한 제품을 개발할 수 있다.
리팩토링을 공부할 때는 리팩토링에 관해 체계적으로 잘 정리된 책인 「리팩토링」(마틴 파울러, 켄트 벡 공저)를 추천
1.2.3 DB 커넥션 만들기의 독립
아주 초보적인 관심사의 분리 작업이지만, 메소드 추출만으로 변화에 좀 더 유연하게 대처할 수 있는 코드를 만들었다.
더 나아가 변화를 반기는 DAO를 만들어보자.
상속을 통한 확장
기존 USerDao 코드를 한 단계 더 분리
UserDao에서 메소드 구현 코드를 제거, getConnection()을 추상 메소드로 만들어놓는다.
이제 추상 클래스인 UserDao를 상속을 통해 하위 클래스에 구현
public abstract class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
public class NUserDao extends UserDao {
public Connection getConnection() throws ClassNotFoundException, SQLException {
// TODO Auto-generated method stub
return null;
}
}
public class DUserDao extends UserDao {
public Connection getConnection() throws ClassNotFoundException, SQLException {
// TODO Auto-generated method stub
return null;
}
}
}
이렇게 슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩 가능한 protected 메소드 등으로 만든 뒤 서브 클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법을 디자인 패턴에서
템플릿 메소드 패턴(template method pattern)이라고 한다.
서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것을 팩토리 메소드 패턴(factory method fattern)이라 한다.
장점 :
- 하위 클래스를 통해 독립적으로 기능 확장 가능
- 상위 클래스를 사용하는 기존 코드에 영향을 주지 않고 구현체를 변경할 수 있다.
단점:
- 상속을 통해 결합도 증가 ( 상위 클래스 변경시 하위 클래스도 수정 혹은 다시 개발해야 할 수 있음)
'BackEnd > Spring & Springboot Study' 카테고리의 다른 글
[토비의 스프링] 1.4 제어의 역전(IoC) (0) | 2022.12.01 |
---|---|
[토비의 스프링] 1.3 DAO의 확장 (0) | 2022.11.29 |
[토비의 스프링] 1.1 초난감 DAO (0) | 2022.11.24 |
[토비의 스프링] 토비의 스프링 프로젝트 생성 (0) | 2022.11.22 |
[토비의 스프링] 토비의 스프링 - 스프링을 효과적으로 익히기 위한 세 가지 (0) | 2022.11.22 |