```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
@Testpublicvoidtest_1_3() {UserDao dao =newUserDao();User user =newUser();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 연결
publicConnectiongetConnection() throws SQLException {// DB 연결returnDriverManager.getConnection("jdbc:벤더사","so","1234");}
반환
publicvoidresourceClear(Connection connection) {try {// 그외 prestatement 등 자원리소스 반환 connection.close(); } catch (SQLException e) {e.printStackTrace(); }}
CRUD
// 등록publicvoidadd(User user) {// DB 연결Connection connection =getConnection(); // 메서드로 분리 후 호출// 인설트// 자원 반환resourceClear(connection);}// 조회publicUserget(String id) {// DB 연결Connection connection =getConnection(); // 메서드로 분리 후 호출// 조회// 자원 반환resourceClear(connection);}
publicclassNUserDaoextendsAbstractUserDao {@OverridepublicConnectiongetConnection(AbstractUserDao dao) {// N사 생성코드System.out.println("N사 dao DB 연결");System.out.println(dao);returnnull;}}
DUserDao
publicclassDUserDaoextendsAbstractUserDao {@OverridepublicConnectiongetConnection(AbstractUserDao dao) {// D사 생성코드System.out.println("D사 dao DB 연결");System.out.println(dao);returnnull;}}
Test
@Testpublicvoidtest_1_3_1() {AbstractUserDao dao =newDUserDao();AbstractUserDao dao2 =newNUserDao();try {dao.insertCustomer(); // D사 연결dao2.insertCustomer(); // N사 연결 } catch (SQLException e) {e.printStackTrace(); }}
3. DAO의 확장
Dao에서 커넥션 부분을 인터페이스로 분리
커넥션 부분을 분리함으로써 UserDao에서 커넥션 부분을 수정할 필요가 없어졌다.
즉, 커넥션 부분 수정 발생시 커넥션 해당클래스를 수정하거나, 추가만시키면 언제든지 코드수정이 필요없이 적용이 가능해진다.상속보다 분리를 사용하자는 이런 관점에서 유익함을 주기 때문이고, 디자인패턴 공부했을때 중요한 원칙
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 메서드를 계속 수정해야할 것이다. 이렇게 수정하다가 또 롤백해주세요.
수정하다보면... 어쩌다가 ? 버그를 만들어 낼 수도 있게되므로 잠재적 버그가 항상 존재하는 코드가 되는 것이다.
publicConnectiongetConnection() throws SQLException {// DB 연결returnDriverManager.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 내부에서 구체적으로 직접관여 중 이럴 경우 단점은... 역시 변경일 때겠지?
수정 후 ConnectionMaker를 생성자 파라미터로 받아서 적용 ConnectionMaker의 선정 책임을 외부로 돌림
publicclassDaoFactory {ConnectionMaker connectionMaker; // 추가 has a 관계publicDaoFactory(ConnectionMaker connectionMaker){this.connectionMaker= connectionMaker; }publicUserDaouserDao() {returnnewUserDao(this.connectionMaker); }publicAccountDaoaccountDao() {returnnewAccountDao(this.connectionMaker); }publicMessageDaomessageDao() {returnnewMessageDao(this.connectionMaker); }// 책에서는 이걸 호출 함publicConnectionMakerconnectionMaker(){returnnewDConnectionMaker(); }}
중복문제의 해결
-> 메서드추출, 클래스분리 리팩토링
제어의 역전이란
객체의 생성 관리.. 등 주체가 내가 아니다.타인, 즉 외부가 관리의 주체가 되는 현상.
쉽게, 제어권이 역전됐다는 것이다.
-> 제어 권한이 나한테 있는것이 아니라 다른사람(컨테이너)한테 있다. -> 디자인패턴 활용 (템플릿메서드, 팩토리메서드, 전략 등)
프레임워크와 라이브러리 차이
프레임워크 > 라이브러리
라이브러리어떤 문제해결을 위한 기능 제공. 예를들면 엑셀다운로드 하고싶은데... 이걸 도와주는 이미 만들어져있는 API를 통해 문제해결을 할 수 있는 수준의 API 집합
프레임워크어떤 문제해결을 위한 통합적 API 제공. 내부적 기능활용과 프로세스 흐름제어 등 어떤 문제해결(시스템같은)을 위해 모든 것을 제공해 줄 수 있는 API 집합. 틀, 구조, 확장 모든게 가능하고 가장 중요한건 제어개념이 포함되어야 한다.
스프링의 예로 IoC컨테이너, DI 이런 흐름제어 뿐 아니라 기능활용 제공 등. 어떤걸 해결하기 위한 통합적 API 집합
5 스프링의 IoC
빈팩토리 (BeanFactory) - 상위
애플리케이션 컨텍스트 (ApplicationContext) - 하위 : 가장 많이 사용 됨
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. 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로써 만들어짐
// 관계설정 분리 전의 생성자publicUserDao(){ connectionMaker =newDConnectionMaker();}// 관계주입을 위한 코드...privateConnectionMaker connectionMaker; publicUserDao(ConnectionMaker connectionMaker){ // 생성자DIthis.connectionMaker= connectionMaker; }
7.3 의존관계 검색과 주입
의존관계 검색(LookUp) -> 오브젝트가 필요하는 곳에 자동으로 할당됨. 단, 자신의 구현체를 결정짓지는 못한다. 한번의 기동으로 인해 필요한 오브젝트들을 로딩시켜놔야 한다. 그래서 name 기반으로 검색이 가능하기 떄문.
의존관계 검색과 주입의 차이점의존관계 검색인 경우, 검색하는 오브젝트 자신이 굳이 스프링 빈일 필요가 없다는 것. DI는 검색하는 오브젝트까지 스프링 빈이어야 한다.
그래서 어노테이션 기반일 때
@Repository나 @Component 같은걸 클래스에 붙이나보다..?
7.4 의존관계 주입의 응용
OCP 관점 !
수정
클래스 기반인 경우, 수정시 딱 한군데만 수정하면 모든 곳에 적용이 된다. 그리고 영향을 다른 곳에서 받지 않게 된다.
// 수정 전@BeanpublicConnectionMakerconnectionMaker(){returnnewLocalDBConnectionMaker();}// 수정 후@BeanpublicConnectionMakerconnectionMaker(){returnnewProductionDBConnectionMaker();}
단 한줄 수정으로 위 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(); }