1.2.1 관심사의 분리
객체지향에서 변한다는 것은 변수나 오브젝트 필드 값이 변하는 것이 아닌 오브젝트에 대한 설계와 이를 구현한 코드가 변하는 것이다. 그래서 개발자가 객체를 설계할 때 가장 염두에 두어야 할 사항은 미래의 변화를 어떻게 대비할 것인가이다. 👉 결국 변경이 일어날 때 필요한 작업을 최소화하고 분리와 확장을 고려한 설계를 해야한다.
DB를 오라클에서 MySQL로 바꾸고, 웹 화면의 레이아웃을 다중 프레임 구조에서 단일 프레임에 Ajax를 적용한 구조로 바꾸고 매출이 일어날 때에 지난달 평균 매출액보다 많으면 감사 시스템의 정보가 웹 서비스로 전송되는 동시에 로그의 날짜포맷을 6자리에서 Y2K를 고려해 8자리로 바꿔달라... (모든 변경이 한 번에 한 가지 관심사항에 집중해서 일어나고 있다는 뜻..)
위와 같은 문제는, 변화는 대체로 집중된 한 가지 관심에 대해 일어나지만 그에 따른 작업은 한 곳에 집중되지 않는 경우가 많다는 것이다. 그렇기 떄문에 우리는 한 가지 관심이 한 군데에 집중되게 일어나도록 만들어주어야 한다. 즉 관심이 같은 것끼리는 모으고, 관심이 다른 것은 따로 떨어져 있게 하는 것이다.
💡 관심사의 분리 💡
: 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게하고, 관심이 다른 것은 가능한 따로 떨어져서 서로 영향을 주지않도록 분리하는 것
1.2.2 커넥션 만들기의 추출
- UserDao의 관심사항
UserDao의 구현된 메소드를 살펴보면 add() 하나에서만 적어도 3가지의 관심사항을 발견할 수 있다.
1. DB와 연결을 위한 커넥션을 어떻게 가져올까라는 관심
2. 사용자 등록을 위해 DB에 보낼 SQL 문장을 담을 Statement를 만들고 실행
3. 작업이 끝나면 사용한 리소스인 Statement와 Connection 오브젝트를 닫아줘서 공유 리소스를 시스템에 돌려주는 것
public void add(User user) throws ClassNotFoundException, SQLException {
// 1. DB 연결을 위한 Connection을 가져옴
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "spring", "book");
// 2. SQL을 담은 Statement를 만듦
PreparedStatement ps = c.prepareStatement(
"insert into users(id, name, password) value(?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
// 3. 만들어진 Statement를 실행
ps.executeUpdate();
// 4. 오브젝트를 닫아줌
ps.close();
c.close();
}
또 다른 문제점은 add()메소드에 있는 DB 커넥션을 가져오는 코드와 동일한 코드가 get() 메소드에도 중복되어 있는 것이다. 아직까지는 2개의 메소드밖에 없지만 수백, 수천개의 DAO메소드를 만들면 이러한 코드는 계속 중복되어 나타날 것이다.
- 중복 코드의 메소드 추출
그래서 중복된 DB 연결 코드를 getConnection()이라는 이름의 독립적인 메소드로 만들어주자!
public void add(User user) thorws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
private Connection getConnection() throws ClassNotFoundException SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "spring", "book");
return c;
}
만약 DB 종류와 접속 방법이 바뀌어서 드라이버 클래스와 URL이 바뀌었다거나, 로그인 정보가 변경돼도 앞으로는 getConnection()이라는 한 메소드의 코드만 수정하면 된다. 관심의 종류에 따라 코드를 구분해놓았기 때문에 한 가지 관심에 대한 변경이 일어날 경우 그 관심이 집중되는 부분의 코드만 수정하면 된다.
- 변경사항에 대한 검증 : 리팩토링과 테스트
변경된 기능이 전과 동일한지 확인하려면 앞서 만들었던 main() 메소드를 이용한 테스트를 실행해보면 된다.
하지만 main()의 단점은 여러 번 실행하면 두번쨰부터는 테이블의 기본키인 id가 중복되어 무조건 예외가 발생한다는 점이다. 따라서 main()메소들 테스트를 다시 실행하기 전에 user 테이블의 사용자 정보를 모두 삭제해줘야 한다.
main()메소드를 실행해보았더니 이전과 동일한 결과가 출력되었다. 메소드를 추출한 것은 UserDao에는 어떠한 변화를 주지 않았기 때문이다. 그저 여러 메소드에 중복돼서 등장한 특정 관심사항이 담긴 코드를 별도의 메소드를 분리해낸 것이다. 기능에는 영향을 주지않으면서 코드의 구조만 변경하는 작업을 리팩토링이라고 한다.
💡 리팩토링💡
기존의 코드를 외부의 동작방식에는 변화 없이 내부 구조를 변경해서 재구성하는 작업 또는 기술을 말한다. 리팩토링을 하면 코드 내부의 설계가 개선되어 코드를 이해하기가 더 편해지며 변화에 효율적으로 대응할 수 있다.
1.2.3 DB 커넥션 만들기의 독립
UserDao를 N사와 D사에서 사용자 관리를위해 구매하겠다는 주문이 들어왔다고 가정해보자. 그러나 문제는 N사와 D사가 각기 다른 종류의 DB를 사용하고 있고, DB커넥션을 가져오는 데 있어 독자적으로 만든 방법을 적용하고 싶어한다. 더욱 큰 문제는 UserDao를 구매한 이후에도 DB 커넥션을 가져오는 방법이 종종 변경될 가능성이 있다는 것이다.
이러한 경우 UserDao의 소스코드를 고객에게 제공하고 변경이 필요하면 직접 메소드를 수정해서 사용하라고 할 수 있다. 하지만 우리는 고객에게 미리 컴파일된 클래스 바이너리 파일만 제공하고 싶다. 이런 경우에는 어떻게 해야할까?
- 상속을 통한 확장
UserDao의 getConnection()을 추상메소드로 만들어 놓으면 된다.
Userao를 구입한 포탈사들은 UserDao 클래스를 상속해서 각각 NUserDao, DUserDao라는 서브 클래스를 만든다. 서브 클래스에서는 UserDao에서 추상 메소드로 선언했던 getConnection() 메소드를 원하는 방식대로 구현할 수 있다.
public abstract class UserDao {
public void add(User user) throws SQLException, ClassNotFoundException {
Connection c = getConnection();
...
}
public User get(String id) throws SQLException, ClassNotFoundException {
Connection c = getConnection();
...
}
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}
public class NUserDao extends UserDao {
public Connection getConnection() throws ClassNotFoundException, SQLException {
// N사 DB connection 생성코드
}
}
public class DUserDao extends UserDao {
public Connection getConnection() throws ClassNotFoundException, SQLException {
// D사 DB connection 생성코드
}
}
이렇게 슈퍼클래스에 기본적인 로직의 흐름을 만들고 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법은 디자인 패턴에서 템플릿 메소드 패턴이라고 한다.
UserDao의 getConnection() 메소드는 Connection타입 오브젝트를 생성하는 기능을 정의해놓은 추상 메소드이다. 그리고 UserDao의 서브클래스의 getConnection() 메소드는 어떤 Connection 클래스의 오브젝트를 어떻게 생성할 것인지를 결정하는 방법이라고 볼 수 있다. 이렇게 서브 클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것을 팩토리 메소드 패턴이라고 부른다.
UserDao는 Connection 오브젝트가 만들어지는 방법과 내부 동작방식에는 상관없이 자신이 필요한 기능을 Connection 인터페이스를 통해 사용하기만 할 뿐이다. UserDao는 Connection 인터페이스 타입의 오브젝트라는 것 외에는 관심을 두지 않는다. UserDao는 어떤 기능을 사용한다는 데에만 관심이 있고, NUserDao, DUserDao는 어떤 식으로 Connection기능을 제공하는지에 관심을 두고 있는 것이다.
💡 템플릿 메소드 패턴 💡
상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 패턴으로, 변하지 않는 기능은 슈퍼클래스에 만들어두고 자주 변경되어 확장할 기능은 서브클래스에 만들어 놓는다. 슈퍼클래스에는 미리 추상 메소드 또는 오버라이드 가능한 메소드를 정의해두고, 서브클래스는 슈퍼클래스를 상속받은 후 메서드들의 내부를 각 서브클래스에 맞게 구현하여 다양한 확장 클래스를 만들 수 있다.
public abstract class Super {
public void templateMethod() {
// 기본 알고리즘 코드
hookMethod(); // 서브 클래스에서 선택적으로 작성한다.
abstractMethod(); // 서브 클래스에서 필수적으로 작성한다.
...
}
protected void hookMethod() {} // 서브 클래스에서 선택적으로 오버라이드 가능
public abstract void abstractMethod() // 서브 클래스에서 반드시 구현해야 하는 추상 메소드
}
public class Sub1 extends Super { // 슈퍼클래스를 상속
protected void hookMethod() {
...
}
public void abstractMethod() {
...
}
}
💡 팩토리 메소드 패턴 💡
팩토리 메소드 패턴도 마찬가지로 상속을 통해 기능을 확장하는 패턴이다. 서브클래스에서 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드를 팩토리 메소드라고 하고, 이 방식을 통해 오브젝트 새성 방법을 나머지 로직, 즉 슈퍼클래스의 기본 코드에서 독립시키는 방법을 팩토리 메소드 패턴이라고 한다.
- 상속을 통한 확장의 단점
템플릿 메소드 패턴, 팩토리 메소드 패턴 모두 관심사항이 다른 코드를 분리해내고, 서로 독립적으로 변경 또는 확장할 수 있도록 만드는 것에 있어서 매우 간단하고 효과적인 방법이다. 하지만 이 방법은 상속을 사용했다는 단점이 있다.
1. 이미 UserDao가 다른 목적을 위해 상속을 사용하고 있다면 어떻게 할 것인가? 자바는 다중 상속을 허용하지 않는다. 단지 커넥션 객체를 가져오는 방법을 분리하기 위해 상속구조로 만들어버리면, 후에 다른 목적으로 UserDao에 상속을 적용하기 어렵다.
2. 또한 상속관계는 두가지 다른 관심사에 대한 긴밀한 결합을 허용한다. 슈퍼클래스 내부의 변경이 있을 떄 모든 서브클래스를 함께 수정하거나 다시 개발해야한다.
3. 확장된 기능인 DB 커넥션을 생성하는 코드를 다른 DAO 클래스에 적용할 수 없다. UserDao 외의 DAO 클래스들이 계속 만들어진다면 그때는 상속을 통해 만들어진 getConncetion()의 구현 코드가 매 DAO 클래스마다 중복돼서 나타나는 심각한 문제가 발생할 것이다.
'ETC > 토비의 스프링' 카테고리의 다른 글
토비의 스프링 1.1장 초난감 DAO (0) | 2022.07.01 |
---|---|
토비의 스프링 1장 오브젝트와 의존 관계 (0) | 2022.07.01 |
댓글