객체지향 세계에서는 비즈니스 요구사항에 따라서 오브젝트에 대한 설계가 변할 수 있다.
즉, 오브젝트를 구현한 코드가 변할 수 있다는 것 이다.
또한, 애플리케이션이 기반을 두고 있는 운영 환경이나 기술 또한 바뀌거나 폐기처분 될 때가 있다.
객체를 설계할 때, 가장 중요시 생각해야 하는 것은 미래의 변화에 어떻게 대응할 것인가 이다.
객체지향 설계와 프로그래밍이 이전의 절차적 프로그래밍에 비해 좀 더 번거러운 작업을 요구하는 이유도 여기에 있다.
변화에 대응할 수 있는 가장 좋은 대책 : 변화의 폭을 최소한으로 줄여주는 것이다.
분리
변경과 발전은 한 번에 한 가지 관심사항에 집중해서 일어난다.
여기서, 개발자는 한 가지 관심이 한 군데에 집중되게 하는 것이다.
즉 관심이 같은 것끼리는 모으고, 관심이 다른 것은 따로 떨어져 있게 하는 것이다. (관심사의 분리)
이전 장의 UserDao의 add() 메소드 하나에서 3 가지 관심사항을 발견할 수 있다.
DB와 연결을 위한 커넥션을 가져오는 방법
더 세분화 하여, DB 종류, 드라이버 종류, 로그인 정보, 커넥션 생성 방법 등을 정의 해 줘야한다.
Statement를 만들고 실행하는 것
파라미터로 넘어온 User객체의 정보를 Statement에 바인딩 시키고, 해당 Statement를 DB를 통해 실행시키는 방법이 하나의 관심사항 이다.
작업 종료 시, 실행 중 사용되었던 리소스를 종료시켜 주는 것
또한, 현재 UserDao에는 예외처리가 되어있지 않다.
DB Connection을 가져오는 부분이 UserDao의 add() 와 get() 메소드에 동일한 코드가 중복되어 존재한다.
해당 부분의 코드를 getConnection() 이라는 독립적인 메소드로 만들어 줄 수 이따.
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public void get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
// Connection 을 가져온다.
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/toby?useSSL=false", "root", "password"
);
return c;
}
나중에 드라이버 클래스 또는 DataBase URL 정보가 바뀌었을 때, getConnection() 이라는 한 메소드의 코드만 수정하면 된다.
관심이 다른 코드가 있는 메소드에는 영향을 주지도 않을뿐더러, 관심 내용이 독립적으로 존재하므로 수정도 간단해졌다.
메소드 추출 기법
특정 기능을 담당하는 여러 곳에서 중복되는 부분을 메소드로 뽑아내는 것
Refactoring : 기존의 코드를 외부의 동작방식에는 변화 없이 내부 구조를 변경해서 재구성하는 작업 또는 기술
getConnection() 메소드의 생성과 같은 Refactoring 후에는 반드시 테스트를 통해 기능에 문제가 없다는 것을 보장 해 주어야 한다.
이전 장의 main() 메소드를 이용한 테스트를 실행해 보면 된다.
만약 서로 다른 두 클라이언트 N사와 D사가 존재한다고 가정해보자.
이 두 클라이언트는 서로 다른 종류의 DB를 사용한다.
이때, UserDao 소스코드를 N사와 D사에 제공해주지 않고도 각 클라이언트의 DB 종류에 맞춰서 DB 커넥션 생성 방식을 적용해가면서 UserDao를 사용하게 할 수 있을까?
UserDao 코드를 한 단계 더 분리하면 된다.
public abstract class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public void get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public abstract Connection getConnection() throws ClassNotFoundException, SQLException ;
}
public class NUserDao extends UserDao{
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
...
}
}
public class DUserDao extends UserDao{
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
...
}
}
위 코드를 통해 관심사를 분리할 수 있다.
이제 UserDao는 단순히 변경이 쉽다는 점을 넘어, 확장 또한 쉽게 가능하다라고 말할 수도 있게 됐다.
템플릿 메서드 패턴
슈퍼클래스에 기본적인 로직의 프름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤, 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법
UserDao의 서브클래스(NUserDao, DUserDao)의 getConnection() 메소드는 어떤 Connection 클래스의 오브젝트를 어떻게 생성할 것인지를 결정하는 방법이라고 볼 수 있다.
"UserDao에 팩토리 메소드 패턴을 적용해서 getConnection()을 분리합시다" 라는 의사소통에 익숙해지자.
자바는 클래스의 다중 상속을 허용하지 않는다.
만약 나중에 서브 클래스(NUserDao)가 UserDao 뿐만 아니라, 다른 슈퍼 클래스를 상속해야 한다면 문제가 발생한다.
상속을 통한 상•하위 클래스의 관계는 생각보다 밀접하다.
서브클래스는 슈퍼클래스의 기능을 직접 사용할 수 없다.
그래서 슈퍼클래스 내부의 변경이 있을 때, 모든 서브클래스를 함께 수정해주어야 하는 문제가 발생한다.
확장된 기능인 DB 커넥션을 생성한느 코드를 다른 DAO 클래스에 적용할 수 없다는 것도 큰 단점이다.
소스코드 : github