```java 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; } }
- UserDao
```java
public class UserDao {
private static UserDao instance;
public static synchronized UserDao getInstance() {
if (instance == null) {
instance = new UserDao();
}
return instance;
}
public void add(User user) {
// JDBC 설정
// 인설트
// 자원 반환
}
public User get(String id) {
// JDBC 설정
// 조회
// 자원 반환
User user = new User();
user.setId(id);
return user;
}
}
Test
@Test
public void test_1_3() {
UserDao dao = new UserDao();
User user = new User();
user.setId("open");
user.setName("소명섭");
user.setPassword("1234");
dao.add(user);
System.out.println(user.getId() + " 등록 성공");
User user2 = dao.get(user.getId());
System.out.println("user2 = " + user2);
System.out.println(user.getId() + " 조회 성공");
}
2. DAO 분리
2.1 관심사 분리
변경사항이 발생시 필요한 작업이 최소화 될 수 있도록 하기 위함
분리와 확장에 대한 개념 (OOP 개념 중 OCP 개념)
2.2 커넥션 만들기의 추출
초난감 DAO에서 add 메서드는 3가지의 관심(기능)을 갖고 있다.
메서드는 1가지일. 즉, 1가지 관심을 갖고 일을 해야한다.
그래야 수정시 분석시간도 줄고, 수정할 내용을 최소화 할 수 있기 때문
add 메서드의 3가지 관심사
1. DB 연결 2. 조회, 등록, 수정, 삭제 (CRUD) 3. 자원 반환
즉, 3가지 관심사를 1개의 메서드로 각각 분리를 해야한다.
DB 연결
public Connection getConnection() throws SQLException {
// DB 연결
return DriverManager.getConnection("jdbc:벤더사", "so", "1234");
}
반환
public void resourceClear(Connection connection) {
try {
// 그외 prestatement 등 자원리소스 반환
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
CRUD
// 등록
public void add(User user) {
// DB 연결
Connection connection = getConnection(); // 메서드로 분리 후 호출
// 인설트
// 자원 반환
resourceClear(connection);
}
// 조회
public User get(String id) {
// DB 연결
Connection connection = getConnection(); // 메서드로 분리 후 호출
// 조회
// 자원 반환
resourceClear(connection);
}
2.3 DB 커넥션 만들기의 독립
UserDao가 확장이 되어야한다!!!
Oralce DB -> MySQL DB로 변경발생
UserDao를 추상클래스 or 인터페이스로 구현
OralceDB / MySQLDB를 구현클래스로 구현 -> UserDao 확장
추상클래스 AbstractUserDao
public abstract class AbstractUserDao {
public abstract Connection getConnection(AbstractUserDao dao);
public abstract void add(); // 추가내용
// ... 다양한 작업 추가
public abstract void clear(); // 추가내용
// 템플릿 메서드 활용 -> 호출쪽에서 호출을 하겠지?
public final void insertCustomer() throws SQLException {
Connection connection = getConnection(this);
PreparedStatement preparedStatement = connection.prepareStatement("sqlss");
add();
clear(connection, preparedStatement);
}
}
NUserDao
public class NUserDao extends AbstractUserDao {
@Override
public Connection getConnection(AbstractUserDao dao) {
// N사 생성코드
System.out.println("N사 dao DB 연결");
System.out.println(dao);
return null;
}
}
DUserDao
public class DUserDao extends AbstractUserDao {
@Override
public Connection getConnection(AbstractUserDao dao) {
// D사 생성코드
System.out.println("D사 dao DB 연결");
System.out.println(dao);
return null;
}
}
Test
@Test
public void test_1_3_1() {
AbstractUserDao dao = new DUserDao();
AbstractUserDao dao2 = new NUserDao();
try {
dao.insertCustomer(); // D사 연결
dao2.insertCustomer(); // N사 연결
} catch (SQLException e) {
e.printStackTrace();
}
}
3. DAO의 확장
Dao에서 커넥션 부분을 인터페이스로 분리
커넥션 부분을 분리함으로써 UserDao에서 커넥션 부분을 수정할 필요가 없어졌다.
즉, 커넥션 부분 수정 발생시 커넥션 해당클래스를 수정하거나, 추가만시키면 언제든지 코드수정이 필요없이 적용이 가능해진다.상속보다 분리를 사용하자는 이런 관점에서 유익함을 주기 때문이고, 디자인패턴 공부했을때 중요한 원칙
ConnectionMaker 인터페이스
public interface ConnectionMaker {
Connection makeConnection();
}
DConnectionMaker 구현클래스
public class DConnectionMaker implements ConnectionMaker {
@Override
public Connection makeConnection() {
System.out.println("D사 커넥션 만들기");
return null;
}
}
NConnectionMaker 구현클래스
public class NConnectionMaker implements ConnectionMaker {
@Override
public Connection makeConnection() {
System.out.println("D사 커넥션 만들기");
return null;
}
}
Test
```java @Test public void test_1_12() {
/ 요구사항 1. 새로운 K사 DB 적용을 할 겁니다. 반영해주세요 /
UserDao dao = new UserDao(new DConnectionMaker()); // 이 부분만 수정하고 K사 추가시켜서 인터페이스대로 구현만 하면 끝
// 추가 적용한부분
UserDao dao = new UserDao(new KConnectionMaker()); // 위 기존코드는 삭제하고 KConnection 구현만하면 반영 끝.
dao.get("1");
}
- 추가 KConnectionMaker 구현클래스
추가시키기만 하면 끝 / UserDao 소스는 수정하나도 안해도 됨. -> 이런것이 분리의 장점이 된다
```java
public class KConnectionMaker implements ConnectionMaker {
@Override
public Connection makeConnection() {
System.out.println("K사 커넥션 만들기");
return null;
}
}
만약 이렇게 안했다면? 어떘을까?
UserDao의 getConnection 메서드를 계속 수정해야할 것이다. 이렇게 수정하다가 또 롤백해주세요.
수정하다보면... 어쩌다가 ? 버그를 만들어 낼 수도 있게되므로 잠재적 버그가 항상 존재하는 코드가 되는 것이다.
public Connection getConnection() throws SQLException {
// DB 연결
return DriverManager.getConnection("jdbc:벤더사", "so", "1234"); // 수정대상 부분
}
3.4 원칙과 패턴
OOP 5대원 칙 SOLID
S : SRP : 단일 책임 원칙
O : OCP : 개방 폐쇄 원칙
L : LSP : 리스코프 치환 원칙
I : ISP : 인터페이스 분리 원칙
D : DIP : 의존관계 역전 원칙
이 모든 내용은 변경 최소화, 유지보수성 관점에서 바라보면 된다.
SRP (단일 책임 원칙)
클래스는 한가지 일을, 메서드는 한가지 기능 이런식으로 구성을 해야한다.
클래스가 너무 많은 일을 하면, 분석 시간도 늘어나고, 메서드가 앞장에서 본것처럼 초난감 DAO의 UserDao.add() 같은 메서드는 3가지 관심(기능)을 갖고 일을 한다. 그러다보니 중복코드도 내재되고, 신경써야할 것도 많아지는데 이를 1가지로 구성하면, 수정을 해야할 때 더 적은 노력이면 충분하다는 것이다. 즉, 변경에 대해서 한가지만 고쳐야 한다는 관점
OCP (개방 폐쇄 원칙)
확장에 열려 있고, 수정에 닫혀 있다.
클래스를 확장할 때 다른 클래스는 건드리면 안된다는 내용이다. 그리고 SRP에서 언급한 것 처럼 수정할 때 다른데 영향을 미쳐서는 안된다는 것이다. (FP의 기반이 된다)
LSP (리스코프 치환 원칙)
다형성에 관한 내용이다.
상위객체(추상, 상속, 인터페이스)로 코딩을 해야하고, 서브 클래스로 데이터를 주고 받거나 실행 할때 아무 문제가 없어야 한다는 원칙.
생각해보면
파라미터(매개변수)로 넘길 때는 서브클래스의 하위호환이 가능해야하기 때문에 상위개념으로 항상 정의가 되어 있어야한다.
ISP (인터페이스 분리 원칙)
인터페이스로 분리해야하는 이유는 너무너무 많은 곳에서 이야기하고 있는 내용이다. 지금 까지 살펴본 내용처럼 인터페이스로 구분을 해놓으면, 구현할 때 인터페이스의 규칙에 맞게 구현을 하면되고, 사용할 때 서브클래스(구현체)를 통해서 내용을 담으면 되기 때문이다.
DIP (의존관계 역전 원칙)
고수준 모듈은 저수준의 모듈에 의존하면 안된다. -> 방향성 문제다. 항상 하위가 상위를 바라볼 수 있도록 해야 수정작업이 수월하다
이 두 모듈 모두 다른 추상화 된 것에 의존해야한다.
-> 각 모듈은 분리가 되어 각각의 추상화 된것에 의존해서 구현되어야 수정작업이 수월하다
추상화 된 것은 구체적인 것에 의존하면 안된다. 반대로 구체적인 것이 추상화 된 것에 의존해야한다. -> 클래스다이어그램을 생각해보자 (상속, 인터페이스 등) 화살표 방향성하위->상위로
수정작업이 어려운 이유 1. 영향도 2. 재사용성 부족
위 2가지 때문에 수정이 힘들다면 소프트웨어 품질이 나쁜 것이다.
이유는 간단하다. 수정이 편해야한다는것!그래서 OOP에서 SOLID 원칙이 중요한 것
높은 응집도와 낮은 결합도
높은 응집도
클래스 내의 정보가 강하게 결합되어 있어야한다는 것.
즉, 필드, 메서드가 항상 연관되어 있어야하고, 어떤 연관없는 내용이 있다면 응집도가 낮다.
낮으면 -> 1가지일이 아닌 2가지 이상의 일을 하고 있을 확률이 높다. 즉 SRP에 위배될 것이다.
2. 재사용성 부족이 나타난다.
낮은 결합도
낮은 결합도가 중요한 이유는. ISP 관점이 좀 강하다.
각 클래스간의 관계 분리를 통해서 낮은 또는 느슨한 결합을 해야 수정이 용이하다는 것이다.
강하면 -> 수정시 1. 영향도 때문에 영향도분석까지 하는... 미친듯이 코드분석을 해야겠지?? 그러면 답도없는거야
전략 패턴
전략패턴을 언급하고 있다. 스프링에서 전략패턴은ApplicationContext가 가장 대표적인 케이스가 아닌가 싶다.
디자인패턴 중 가장 유용한 패턴 중하나이고, 스프링에서 자주 사용한다.
예를들어, DB 변경 작업도 전략패턴을 구현해도 되는 부분이 필요할 수 있다.
4 제어의 역전 (IoC)
DaoFactory
~Dao 생성 책임 클래스
수정 전 ConnectionMaker 가 new로 구체적인 생성 진행중 -> ConnectionMaker가 DaoFactory 내부에서 구체적으로 직접관여 중 이럴 경우 단점은... 역시 변경일 때겠지?
public class DaoFactory {
public UserDao userDao() {
return new UserDao(new DConnectionMaker());
}
public AccountDao accountDao() {
return new AccountDao(new DConnectionMaker());
}
public MessageDao messageDao() {
return new MessageDao(new DConnectionMaker());
}
}
수정 후 ConnectionMaker를 생성자 파라미터로 받아서 적용 ConnectionMaker의 선정 책임을 외부로 돌림
public class DaoFactory {
ConnectionMaker connectionMaker; // 추가 has a 관계
public DaoFactory(ConnectionMaker connectionMaker){
this.connectionMaker = connectionMaker;
}
public UserDao userDao() {
return new UserDao(this.connectionMaker);
}
public AccountDao accountDao() {
return new AccountDao(this.connectionMaker);
}
public MessageDao messageDao() {
return new MessageDao(this.connectionMaker);
}
// 책에서는 이걸 호출 함
public ConnectionMaker connectionMaker(){
return new DConnectionMaker();
}
}
중복문제의 해결
-> 메서드추출, 클래스분리 리팩토링
제어의 역전이란
객체의 생성 관리.. 등 주체가 내가 아니다.타인, 즉 외부가 관리의 주체가 되는 현상.
쉽게, 제어권이 역전됐다는 것이다.
-> 제어 권한이 나한테 있는것이 아니라 다른사람(컨테이너)한테 있다. -> 디자인패턴 활용 (템플릿메서드, 팩토리메서드, 전략 등)
프레임워크와 라이브러리 차이
프레임워크 > 라이브러리
라이브러리어떤 문제해결을 위한 기능 제공. 예를들면 엑셀다운로드 하고싶은데... 이걸 도와주는 이미 만들어져있는 API를 통해 문제해결을 할 수 있는 수준의 API 집합
프레임워크어떤 문제해결을 위한 통합적 API 제공. 내부적 기능활용과 프로세스 흐름제어 등 어떤 문제해결(시스템같은)을 위해 모든 것을 제공해 줄 수 있는 API 집합. 틀, 구조, 확장 모든게 가능하고 가장 중요한건 제어개념이 포함되어야 한다.
스프링의 예로 IoC컨테이너, DI 이런 흐름제어 뿐 아니라 기능활용 제공 등. 어떤걸 해결하기 위한 통합적 API 집합
5 스프링의 IoC
빈팩토리 (BeanFactory) - 상위
애플리케이션 컨텍스트 (ApplicationContext) - 하위 : 가장 많이 사용 됨
BeanFactory(상위) <- ApplicationContext(하위)
설정 관련 어노테이션
@Configuration / @Bean 메서드명 -> 호출시 ID로 작용
@Configuration // -> 설정 파일이란 마킹
public class DaoFactory {
ConnectionMaker connectionMaker;
@Bean // -> Bean이란 마킹
public UserDao userDao() {
this.connectionMaker = new DConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
return new UserDao();
}
@Bean
public AccountDao accountDao() {
return new AccountDao(this.connectionMaker);
}
@Bean
public MessageDao messageDao() {
return new MessageDao(this.connectionMaker);
}
}
bean 스프링이 관리하는 객체를 스프링에서는 bean 이라함. 스프링 컨테이너가 관리 안하는 객체는 bean 이라 안함
bean facotry 스프링컨테이너. bean을 관리하는 또 다른 객체
application context
설정저보 / 설정 메타정보
컨테이너 또는 IoC 컨테이너
스프링 프레임워크
6 싱글톤 레지스트리와 오브젝트 스코프
기본 스코프는 싱글턴이다. 1. 싱글턴 (singleton) - default
스프링컨테이너에 의해 한번 로딩 된 후 어플리케이션이 죽을 때 까지 존재 (단, 인스턴스 1개)
프로토타입 (prototype)
스프링컨테이너에 의해 한번 로딩 된후 어플리케이션이 죽을 때 까지 존재 (단, 요청시마다 새로운 인스턴스로 반환)
요청 (request)
request로 요청들어온 객체에 대해 반환 된 후 삭제 됨. 꽤 유용할듯
세션 (session)
한 세션이 종료 될 때까지 존재 한 후 삭제. 웹의 세션과 (웹기반)
// 싱글턴 구현
public class UserDao{
private ConnectionMaker connectionMaker;
private static UserDao instance; // 싱글턴
public static synchronized UserDao getInstance() {
if (instance == null) {
instance = new UserDao();
}
return instance;
}
// 테스트
@Test
public void test_1_20_21() {
DaoFactory daoFactory = new DaoFactory();
UserDao dao1 = daoFactory.userDao();
UserDao dao2 = daoFactory.userDao();
System.out.println("dao1 = " + dao1);
System.out.println("dao2 = " + dao2);
ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao userDao1 = context.getBean("userDao", UserDao.class);
UserDao userDao2 = context.getBean("userDao", UserDao.class);
System.out.println("userDao1 = " + userDao1);
System.out.println("userDao2 = " + userDao2);
}
싱글턴이 멀티스레드 환경에서 주의 사항
전역상태로 만듬
상속불가
테스트힘듬
하나로 만드는것 자체가 실제로 불가능에 가까움... 주의필요
7. 의존관계 주입 (DI)
7.1 제어의 역전과 의존관계 주입
DI란 의존성 주입, 의존성 주입이란, 호출하는 쪽이 오브젝트를 직접 관리, 생성하는 것이 아니라, 제 3자(컨테이너)에게 관리 및 생성의 책임을 넘기고 (IoC) 이를 통해 객체주입 받는 행위(DI). 즉, 객체의 주입을 누군가에게 의존해서 받는 것을 DI라 함.
7.2 런타입 의존관계 설정
A,B의 has a ~ 관계에서 A가 B를 갖는다는 A->B로 표현하며, A는 B를 갖고 있기 때문에 B의 변경에 영향을 받게 된다. 따라서 B는 A와 상관이 없으므로 B가 변경되더라도 A를 신경쓸 필요가 없는 것!
항상 영향도 분석은 중요하다.
의존관계 주입 3가지 1. 인터페이스에만 의존 2. 런타임시점 의존관계는 컨테이너와 팩토리 같은 제3의 존재가 결정 3. 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로써 만들어짐
// 관계설정 분리 전의 생성자
public UserDao(){
connectionMaker = new DConnectionMaker();
}
// 관계주입을 위한 코드
...
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker){ // 생성자DI
this.connectionMaker = connectionMaker;
}
7.3 의존관계 검색과 주입
의존관계 검색(LookUp) -> 오브젝트가 필요하는 곳에 자동으로 할당됨. 단, 자신의 구현체를 결정짓지는 못한다. 한번의 기동으로 인해 필요한 오브젝트들을 로딩시켜놔야 한다. 그래서 name 기반으로 검색이 가능하기 떄문.
의존관계 검색과 주입의 차이점의존관계 검색인 경우, 검색하는 오브젝트 자신이 굳이 스프링 빈일 필요가 없다는 것. DI는 검색하는 오브젝트까지 스프링 빈이어야 한다.
그래서 어노테이션 기반일 때
@Repository나 @Component 같은걸 클래스에 붙이나보다..?
7.4 의존관계 주입의 응용
OCP 관점 !
수정
클래스 기반인 경우, 수정시 딱 한군데만 수정하면 모든 곳에 적용이 된다. 그리고 영향을 다른 곳에서 받지 않게 된다.
// 수정 전
@Bean
public ConnectionMaker connectionMaker(){
return new LocalDBConnectionMaker();
}
// 수정 후
@Bean
public ConnectionMaker connectionMaker(){
return new ProductionDBConnectionMaker();
}
단 한줄 수정으로 위 connectionMaker 메서드를 사용하는 모든 곳에서 바로 적용 가능. 이것이 분리의 힘, DI의 편리함
기능추가
기능추가시에도 영향을 받지 않는다.
```java // 설정 클래스 @Configuration public class CountingDaoFactory {
@Bean
public UserDao userDao() {
UserDao dao = new UserDao();
dao.setConnectionMaker(connectionMaker()); // 변경사항
return dao;
// return new UserDao(connectionMaker());
}
@Bean
public ConnectionMaker connectionMaker() {
return new CountingConnectionMaker (realConnectionMaker()); // 추가 됨
}
@Bean
public ConnectionMaker realConnectionMaker() {
return new DConnectionMaker(); // 기존 커넥션 연결
}
}
// 추가 된 클래스 public class CountingConnectionMaker implements ConnectionMaker { int counter = 0; private ConnectionMaker connectionMaker;
public CountingConnectionMaker(ConnectionMaker connectionMaker) { this.connectionMaker = connectionMaker; }
@Override public Connection makeConnection() { this.counter++; return this.connectionMaker.makeConnection(); }