코드의 변경을 쉽게 하고 변경이 다른 문제를 일으키지 않게 하기 위해 분리와 확장을 고려해 설계한다.
ex) DAO 접속 암호를 변경하기 위해 모든 DAO class를 변경해야 하는 경우
관심이 같은 것은 하나의 객체로 모으고, 관심이 다른 것은 떨어져 영향을 주지 않도록
public class UserDao{
public void add(User user) throws ClassNotFoundException, SQLException{
Connection c = getConnetion();
...
}
private Connection getConnection() throws ClassNotFoundException, SQLException{
Class.forName("com.mysql.cj.jdbc.driver")
Connection c = DriverManager.getConnection("url", "user", "password");
return c;
}
}
public abstract class UserDao{
public void add(User user) throws ClassNotFoundException, SQLException{
Connection c = getConnetion();
...
}
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}
다시 UserDao를 상속받아 getConnetction을 구현하는 DUserDao를 만든다.(오버라이딩)
public class DUserDao extends UserDao{
public Connection getConnection() throws ClassNotFoundException, SQLException{
Class.forName("com.mysql.cj.jdbc.driver")
Connection c = DriverManager.getConnection("url", "user", "password");
return c;
}
}
그러면 UserDao의 add()와 get() 함수를 갖고 있지만 DUserDao에서 자체적으로 getConnection()을 구현해서 사용할 수 있다.
but 클래스는 상속이 하나만 가능하기 때문에 다른 용도로 상속을 적용하기 힘듦
connection 생성을 위해 SimpleConnectionMaker 클래스를 생성하고 이 클래스 안에 커넥션 생성 함수인 makeNewConnection()을 만든다.
public class UserDao{
private SimpleConnectionMaker simpleConnectionMaker;
public UserDao(){
simpleConnectionMaker = new SimpleConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException{
Connection c = simpleConnectionMaker.makeNewConnection();
...
}
}
단점
1. dao가 늘어날수록 add함수와 같은 db 접근 함수 안의 makeNewConnection과 같은 함수를 전부 수작업으로 고쳐야 한다.(고객사는 openConnection이라는 이름의 연결 함수를 쓸 수도 있음)
2. UserDao가 db 커넥션 용 클래스를 명확하게 알아야 한다.
UserDao와 SimpleConnectionMaker 사이에 인터페이스를 넣어 추상적인 느슨한 연결고리를 만든다.
추상화: 공통적인 성격을 뽑아내 따로 분리하는 것으로 인터페이스를 사용한다.
인터페이스: 어떤 일을 하겠다는 기능만을 정의해놓은 것으로 구현 방법은 인터페이스를 상속해서 구현하는 자식 클래스에서 정해진다.
인터페이스를 통해 자신이 구현한 클래스에 대한 구체적 정보를 모두 감춘다.
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
public class DConnectionMaker implements ConnectionMaker{
public Connection makeConnection() throws ClassNotFoundException, SQLException{
... 연결 코드 구현
}
public class UserDao {
ConnectionMaker connectionMaker;//어떤 클래스를 사용하는지 알지 못함
public UserDao(){
connectionMaker = new DConnectionMaker();//클래스 이름이 직접적으로 보임
}
public void add(User user) throws ClassNotFoundException, SQLException{
Connection c = connetionMaker.makeConnection();
...사용자 저장 코드
}
add()에서 인터페이스의 makeConnection 함수를 구현한 DConnectionMaker 객체를 사용해 연결함으로 고객사가 ConnectionMaker을 상속받아 자체적으로 커넥션 클래스를 생성해도 makeConnection()이 다른 이름으로 변경되지 않는다.(변경에는 닫혀있다)
UserDao가 어떤 ConnectionMaker의 구현 클래스를 사용할지 모르게 설정하자!
객체지향의 다형성에 의해 클래스 B에 대해 알지 못해도 B가 A 인터페이스를 구현한 클래스라면 B의 오브젝트를 인터페이스 타입(A)로 받아서 사용할 수 있다.
public class UserDao {
ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker){//의존관계 주입 부분
this.connectionMaker = connectionMaker;
}
//UserDaoTest 클래스의 main 함수
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao dao = new UserDao(connectionMaker);
UserDao의 클라이언트에게 ConnectionMaker 구현 클래스를 결정하는 책임이 전가됨.
클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.
인터페이스를 통해 구현하게 되면 확장을 위해서는 개방돼 있지만 불필요한 변화는 폐쇄돼 있다.
SOLID(객체지향 설계 원칙): 특별한 상황에서 발생하는 문제에 대한 구체적인 해결책인 디자인 패턴에 비해 일반적인 상황에서 객체 지향의 특징을 잘 살려 적용 가능한 설꼐 원칙
+) UserDao와 ConnectionMaker은 응집도가 높고 서로의 연결이 느슨함으로 결합도가 낮다.
변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 구현 클래스를 필요에 따라 바꿔서 사용할 수 있는 디자인 패턴.
UserDao는 ConnectionMaker을 인터페이스로 정의해 구현 클래스를 바꿔가며 사용할 수 있는 전략패턴이다.
팩토리란 객체의 생성 방법을 결정하고 생성된 오브젝트를 돌려주는 것.(오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 분리하는 목적)
public class DaoFactory {//팩토리를 통해 객체를 생성해서 반환함으로 UserDaoTest에서 ConnectionMaker을 결정하는 것을 막음
public UserDao userDao(){
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
return userDao;
}
다른 dao객체를 생성하는 함수를 DaoFactory 안에 만들 시 new DConnectionMaker() 가 중복되게 된다.
public class DaoFactory {
public UserDao userDao(){
return new UserDao(connectionMaker());
}//new DConnectionMaker() 중복이 제거됨
public ConnectionMaker connectionMaker(){
return new DConnectionMaker();
}
}
라이브러리는 애플리케이션의 코드를 직접 제어하지만 프레임워크는 애플리케이션 코드가 프레임워크에 의해 사용된다.
처음에는 ConnectionMaker의 구현 클래스를 결정하고 오브젝트를 생성하는 제어권은 UserDao에게 있었지만 지금은 DaoFactory에게 있다.(UserDaoTest도 DaoFactory가 공급하는 ConnectionMaker을 사용하는 수동적 클래스가 됐다.
빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라고 인식하게 하기 위해 @Configuration 애노테이션 추가
@Configuration: 애플리케이션 컨텍스트가 사용할 설정정보
@Bean: 오브젝트 생성을 담당하는 IoC용 메소드
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
this.dao = context.getBean("userDao", UserDao.class);
userDao라는 이름의 빈을 가져온다는 것은 DaoFactory의 userDao()를 호출해 그 결과를 가져오는 것과 동일하다.
애플리케이션 컨텍스트에 DaoFactory 클래스를 설정정보로 등록 -> @Bean 메소드 이름을 가져와 빈 목록 생성 ->getBean() 호출 시 빈 목록에서 빈 이름 찾음-> 빈 생성 메소드 호출해 오브젝트 생성 후 클라이언트에게 반환
애플리케이션 컨텍스트의 장점
1. 클라이언트는 구체적인 팩토리 클래스를 알 필요 없다(DaoFactory를 사용할 것이다!)
2. 종합적인 IoC 서비스 제공(오브젝트 후처리, 시점 등)
3. 빈 검색하는 다양한 방법을 제공한다(타입/애노테이션 기반 검색)
동일성: 완전히 같은 동일한 오브젝트 ==, Object의 equals()메서드
동등성: 동일한 정보를 담고 있는 오브젝트 equals()
싱글톤 패턴: 하나만 만들어지는 클래스의 오브젝트는 애플리케이션 내에서 전역적으로 접근이 가능하다.
싱글톤의 단점
싱글톤은 인스턴스 필드 값을 변경하고 유지하는 상태유지(stateful) 방식으로 만들지 않는다. 싱글톤 패턴은 private Connection c와 같이 상태가 변하는 변수를 갖으면 하나의 객체가 공유되어 c의 정보에 혼란이 생길 수 있다. 따라서 자신이 사용하는 다른 싱글톤 빈을 저장하는 읽기 전용 인스턴스 변수는 사용 가능하다.
A가 B를 의존한다 = A가 B에 정의된 메소드를 호출해서 사용한다.
getBean()을 통해 의존관계를 검색해 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾는다. getBean()을 사용하는 클래스는 스프링의 빈이 아니어도 된다. getBean()에서 검색하는 빈 이름만 등록돼있으면 된다.
의존관계에 주입에는 두 가지 방법이 있다.
1. 수정자 메소드(setter)을 이용해 주입
파라미터로 전달된 값을 내부 인스턴스 변수에 저장한다.
2. 일반 메소드를 이용해 주입