토비의 스프링을 읽으며 개인 기록 공간이에요.
자바 개발자가 아니라면 분명 학습하기 어려운 책이지만😢 자바 개발자뿐만 아니라 모든 개발자가 학습을 하면 좋겠다.
(자바, 스프링을 활용하는 개발자도 어렵게 다가올 수 있는 책이다.)
시간을 들여서 천천히 공부하고 습득해야 될 책이다.
매일 매일 읽고 직접 타이핑을 하면서 익혀보자. 더 나은 개발자가 될 수 있다.
개발에서는 모든 것은 수정될 수 있고 요구사항들이 변한다. 그럼 변화의 어떻게 대응할 것이고, 변하를 위한 코드를 어덯게 작성할 것인가?
변경이 있을 때 단 3줄만 고칠 것인가? 아니면 300줄을 고칠 것인가? 변경의 걸리는 시간뿐만 아니라, 검증 시에도 전자의 검증 시간은 5분, 후자의 검증시간은 5시간이 걸릴 수 있다.
변화의 대응할 수 있는 소프트웨어를 학습해보자 :)
관심사의 분리: 관심이 같은 것을 하나의 객체에서 관리하고, 가능한 서로 영향을 끼치지 않는다.
하나의 클래스에 작성되어 있는 기능들을 관심사에 맞게 분리를 해준다.
예시를 보자면 다음의 예시가 있다.
하나의 클래스(갓 클래스)에서 (자바를 활용하야) DB 작업을 했다면, 관심사 분리를 할 수 있다.
- DB 연결을 위한 커넥션은 어떻게? 의 관심사
- DB로 보낼 SQL 문장을 만들고 실행의 관심사
- 작업이 끝나고 리소스 종류는 어떻게? 의 관심사
관심사의 분리를 학습하지 않았더라면 위의 예시를 그냥 갓 클래스로 작성을 했을텐데, 공부를 해야 더 나은 코드를 만들고 확장성 있는 코드를 만들 수 있다.
첫번째 관심사 중 커넥션의 분리
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
private Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("org.h2.Driver");
Connection c = DriverManager.getConnection("jdbc:h2:~/test", "sa", "");
return c;
}
이전에는 add(), get() 마다 커넥션을 만들었는데, 커넥션을 한 곳에서 만들고 한 곳에서 관리를 한다. (관심사의 분리)
메소드 추출: 공통의 기능을 메소드로 만들어 중복된 코드를 제거
구글과 애플에서 UserDao를 사용하기 원한다. 하지만 구글과 애플에서는 DB 커넥션이 방법이 달라 getConnection()을 각 상황에 맞게 구현해서 사용하라고 하자.
UserDao 클래스를 상속 받는 GoogleUserDao와 AppleUserDao 내에서 getConnection() 구현해주면 되겠다.
getConnection()는 추상 메소드로 지정을 해주는 센스.
이때 중요한 디자인 패턴 2가지가 있다. :)
템플릿 메소드 패턴: 변하지 않는 기능은 슈퍼 클래스에, 변경이 필요한 기능은 서브 클래스에서 필요에 맞게 기능을 구현해서 사용 (기능의 초점)
팩토리 메소드 패턴: 서브 클래스에서 구체적인 오브젝트 생성 방법을 결정 (e.x, Connection 이 회사의 따라 결정)
상속은 제약이 많아서 불편해. 상속을 풀고 Connection을 만들어 주는 클래스를 별도로 만들어주고 Connection을 얻기 위해서 UserDao에서 사용을 해보자.
인터페이스를 사용하지 않고 SimpleConnectionMaker라는 순수 클래스를 사용을 했다. 이때는 구글이나 애플이 SimpleConnectionMaker 을 규정 없이 구현하게 된다면, 네이밍이 틀려서 사용할 수 없어. 규약이 필요하겠다. 이때 필요한 것이 인터페이스!!! 인터페이스를 사용하여 추상화를 시켜서 UserDao에서는 기능만을 사용하면 된다. 기능구현은 구글이나 애플에서 입맛에 맞게 구현을 하면 돼.
추상화: 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업. 추상화를 위한 도구가 인터페이스다.
인터페이스로 추상화를 시키고 생성자를 통해서 구현된 객체를 받아서 사용을 하자. (스프링에서는 직접 개체를 주입하지 않고 DI를 통해서 스프링이 생성자에 직접 부여를 해줄거야)
OCP: 확장에는 열려있고(Open), 변경에는 닫혀(Closed) 있다.
Connection을 변경해야 한다. DB 연결 방법(확장)에는 열려 있고. UserDao는 변경할 필요가 없으니, 닫혀 있다.
응집도가 높다는 것은 하나의 책임 또는 관심사에 집중을 한다는 뜻이다. 관심사 같은 기능끼리 모여 있을 수록 응집도느 높다.
낮은 결합도라 함은 클래스가 서로서로 독립적이고 서로를 알 필요가 없게 만들어 주는 것이다. 이때는 객체 지향의 DI가 필요한데, 인터페이스를 통해서 느슨한 결합을 만들어 줄 수 있다. (커넥션이 변경되어도 UserDao는 변경할 필요가 없듯이.)
전략 패턴: "필요에 따라 변경이 가능"하며 전략을 바꿔 가면서 사용을 할 수 있다. (e.x, Connection of google or apple)
팩토리(factory): "객체의 생성 방법을 결정"하고 그렇게 만들어진 오브젝트를 돌려 주는 것.
UserDaoTest(main 실행) - UserDao - ConnectionMaker 구조로 사용을 하였다. 실행의 시작인 UserDaoTest에서 어떤 Connection을 사용할지 결정을 해주었는데, 이것도 분리를 시켜보자. -> DaoFactory 생성
public class DaoFactory {
public UserDao userDao() {
ConnectionMaker connectionMaker = new GoogleConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
return userDao;
}
}
UserDaoTest에서는 UserDao가 어떻게 만들어지는지 신경쓰지 않고 DaoFactory를 받아서 사용을 하면된다.
:: 책임들을 분리하라. (관심사들을 최대한 분리하기)
userDao 뿐만 아니라, accountDao, messageDao 등 여러 Dao를 만드는 경우가 있을 수 있다. Connection 코드가 반복되니, 메소드 추출을 통해서 분리를 해주자. (중복문제를 해결하는 좋은 방법은 분리!!)
public class DaoFactory {
public UserDao userDao() {
return new UserDao(connectionMaker());
}
public AccountDao accountDao() {
return new AccountDao(connectionMaker())
}
public ConnectionMaker connectionMaker() {
return new GoogleConnectionMaker();
}
}
일반적인 프로그램의 흐름은 시작되는 지점에서 사용할 오브젝트를 결정하고, 생성하며 그에 따른 메소드를 호출한다.
IoC(제어의 역전): 자신이 사용할 오브젝트를 스스로 선택하는 것이 아닌, 모든 제어 권한을 다른 대상에게 위임을 한다. 자기 자신도 어떻게 만들어지고 어디서 사용되는지 알 수 없다.
- 템플릿 메소드 패턴 같은 경우 UserDao를 상속한 자식클래스는 커넥션을 구현했는데, 언제 어디서 어떻게 사용될지 자신은 모른다. 부모 클래스에서 필요할 때, add() / get() 등에서 필요 시 호출이 된다.
- UserDao - DaoFactory 경우 ConnectionMaker 결정의 권한을 DaoFactory 에 위임을 했다. UserDao도 수동적이 되었다. 관심을 분리 했고, 유연하게 확장할 수 있도록 구조를 변경. DaoFactory 과정이 IoC를 적용하기 위한 작업.
빈(Bean): 스프링에서 스프링이 제어권을 가지고 만들고 관계를 부여는 오브젝트를 빈이라고 한다. 스프링이 생성, 제어 관리하는 오브젝트.
Application Context: 애플리케이션 전반에 제어 작업 담당을 한다. IoC 담당. (i.e, IoC 컨테이너, 스프링 컨테이너, 스프링)
스프링에서는 객체를 싱글톤으로 만들어 준다. 그렇기 때문에 반드시 주의할 점 하나, 싱글톤 객체에는 상태정보를 갖지 않는 무상태 방식으로 만들어야 한다.
전역으로 변수를 만들어서, 사용자 요청올 때마다 공유하게 되면 큰일을 치루게 될 것이야.. 전역 변수 사용인 경우 final 키워드로 읽기 전용 값으로 만들어서 사용을 하자.
DI(Dependency Injection, 의존관계 주입): 스프링에서는 IoC를 사용하기 위해서, DI라는 의도가 더 명확한 DI 키워드를 더욱 많이 사용한다.
의존 관계라 함은, A -> B 인 경우 A가 B에 의존을 하고 있다. A는 B를 사용하고 있는데 B가 변하면 A에 영향을 미친다. B의 기능이 추가되거나 변경 되거나 하면 A는 무언가 수정이 될 것이고 추가가 될 수도 있다. 이를 의존하고 있다고 할 수 있다. 반대로 B는 A가 변경이 되어도 변화에 영향을 받지 않는다.
UserDao -> ConnectionMaker 관계인데 DI 컨테이너에 의해서 UserDao에 Connection을 주입해주는데 이를 의존관계주입이라고 부다.
의존 관계 주입은 최근은 생성자 방식을 통해서 주입을 많이 받는다.
autowired 주입을 받으면 아예 정해지는데, 생성자를 통해서 주입을 받으면 테스트 작성시 굉장히 유리하다.
학습 깃헙: 링크
책: 토비의 스프링
'개발 기록 > 스프링' 카테고리의 다른 글
[토비의 스프링 정리] #3 템플릿의 대해서 리뷰 및 기록 (0) | 2023.02.16 |
---|---|
[토비의 스프링 정리] #2 테스트의 대해서 리뷰 및 기록 (0) | 2023.02.07 |
[스프링] H2 데이터베이스 콘솔 로그인 (0) | 2023.02.04 |
[JPA 실습] java.lang.IllegalArgumentException: Unknown entity 에러 (0) | 2023.01.27 |
[스프링] 트랜잭션이란 무엇인가? (종류, 주의점) (0) | 2022.09.23 |