[Spring] 토비의 스프링 Vol.1 1장 오브젝트와 의존관계

Shiba·2023년 7월 13일
0

🍀 스프링 정리

목록 보기
1/21
post-thumbnail

📗 오브젝트와 의존관계

❗ 토비의 스프링 3.1 vol 1 정리입니다.
책을 읽지 않으셨다면 이해가 어려울 수 있습니다!

📝 예제에 등장할 기본코드

토비의 스프링 예제코드!
- 깃허브에서 다운로드 가능

// 1.1.2 코드 - 초난감DAO
public class UserDao {
	public void add(User user) throws ClassNotFoundException, SQLException {
    	////////////DB연결코드////////////
    	Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection(
        			"jdbc:mysql://localhost/springbook", "spring", "book");
        //////////////////////////////
        
        PreparedStatement ps = c.prepareStatement(
        	"insert into users(id, name, password) values(?, ?, ?)");
        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());
        
        ps.executeUpdate();
        
        ps.close();
        c.close();
        
    }
    
    public User get(String id) throws ClassNotFoundException, SQLException {
    	////////////DB연결코드////////////
    	Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection(
        			"jdbc:mysql://localhost/springbook", "spring", "book");
        //////////////////////////////
        
        PreparedStatement ps = c.prepareStatement(
        	"select * from users where id=?");
        ps.setString(1, id);
        
        ResultSet rs = ps.executeQuery();
        rs.next();
        User user = new User();
        user.setId(rs.getString("id"));
        user.setName(rs.getString("name"));
        user.setPassword(rs.getString("password"));
        
        rs.close();
        ps.close();
        c.close();
        
        return user;
    }
}

📖📌 관심사의 분리

미래의 변화를 대비하여 관심사가 같은 것들 끼리는 하나의 객체로, 다른 것들은 가능한 따로 떨어져서 영향을 주지 않도록 하는 프로그래밍의 기초 개념

⚙ 리팩토링(refactoring)

기존의 코드외부의 동작방식(실행의 결과)에는 변화 없이 내부 구조를 변경해서 재구성 하는 작업

◼ 리팩토링을 하는 이유

  • 코드가 간결해져 이해하기 쉬워짐
  • 변화에 효율적으로 대응할 수 있음

📄 중복 코드의 메소드 추출

중복되어있는 코드하나의 독립적인 메소드로 작성하는 것
- 이를 리팩토링에서는 메소드 추출(extract method)기법 이라고 한다

// 1.2.2 코드
// 예제 코드에서 중복되어있는 DB연결 코드를 하나의 독립적인 메소드로 작성
public class UserDao {
	public void add(User user) throws ClassNotFoundException, SQLException {
    	////////////DB연결코드//////////// - 중복된 코드
		//Class.forName("com.mysql.jdbc.Driver");
       // Connection c = DriverManager.getConnection(
        			//"jdbc:mysql://localhost/springbook", "spring", "book");
        ////////////////////////////////
    	Connection c = getConnection(); // 함수만 호출하면 같은 작업 수행
		
        ...  
    }
    
    public User get(String id) throws ClassNotFoundException, SQLException {
    	////////////DB연결코드//////////// - 중복된 코드
		//Class.forName("com.mysql.jdbc.Driver");
       // Connection c = DriverManager.getConnection(
        			//"jdbc:mysql://localhost/springbook", "spring", "book");
        ////////////////////////////////
    	Connection c = getConnection(); // 함수만 호출하면 같은 작업 수행
		
        ...  
    }
    
    //중복 코드를 하나의 독립적인 메소드로 분리
    private Connection getConnection() throws ClassNotFoundException, 
    		SQLException{
    	////////////DB연결코드////////////
		Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection(
        			"jdbc:mysql://localhost/springbook", "spring", "book");
        ////////////////////////////////
        return c;
	}
}

📄 상속을 통한 확장

코드를 각각 다르게 구현하여 사용해야하는 경우 추상메소드를 만들어 상속받는 클래스가 알아서 각각 구현하도록 함

// 1.2.3 코드
// DB연결 코드를 추상메소드로 만들어 각 제조사에서 구현하도록 함
public abstract class UserDao { //추상클래스 작성
	public void add(User user) throws ClassNotFoundException,
    		SQLException {
    	Connection c = getConnection(); 
        
       ...
        
    }
    
    public User get(String id) throws ClassNotFoundException,
   			 SQLException {
    	Connection c = getConnection(); 
        
       ...
    }
    
    //추상메소드
    private abstract Connection getConnection() throws ClassNotFoundException, 
    		SQLException;
}


//N 제조사
public class NUserDao extends UserDao{
	public Connection getConnection() throws ClassNotFoundException, 
    		SQLException{
		//N제조사의 구현코드
    }
}

//D 제조사
public class DUserDao extends UserDao{
	public Connection getConnection() throws ClassNotFoundException, 
    		SQLException{
		//N제조사의 구현코드
    }
}

  • UserDao를 상속하여 add(), get()은 자유롭게 사용
  • getConnection()을 오버라이딩하여 필요에 맞게 구현하여 사용가능

📒 템플릿 메소드 패턴

기능의 일부를 추상 메소드나 오버라이딩이 가능한 메소드로 만들고
서브 클래스에서 메소드를 필요에 맞게 구현해서 사용하는 방법

📒 팩토리 메소드 패턴

슈퍼클래스(부모)에서 서브클래스에서 구현할 메소드를 호출하여 필요한 타입의 오브젝트를 가져옴
- 구체적인 오브젝트 생성방법을 서브클래스가 정함

👎 상속의 단점

  • 자바는 다중상속을 허용하지 않음
    - 이미 상속을 받은 경우면 사용할 수 없으며, 추후에 상속을 적용하기 힘들다
  • 상속은 슈퍼클래스의 메소드를 사용할 수 있어 슈퍼클래스가 변화하면 다시 개발해야 할 수도 있다
  • 확장된 기능을 다른 클래스에도 적용하려면 계속 상속을 해야한다
    - 코드가 매번 중복되어 나타나는 문제가 발생

📄 클래스의 분리

성격이 다른 관심사를 가진 코드를 하나의 독립적인 클래스로 분리
- 메소드가 아니라서 완전히 독립적인 관심사가 됨

// 1.3.1 코드
public class UserDao {

	private SimpleConnectionMaker simpleConnectionMaker;
    
    public UserDao() {
    	simpleConnectionMaker = new SimpleConnectionMaker();
    }

	public void add(User user) throws ClassNotFoundException, SQLException {
    	Connection c = simpleConnectionMaker.makeNewConnection();
        
       ...
        
    }
    
    public User get(String id) throws ClassNotFoundException, SQLException {
    	Connection c = simpleConnectionMaker.makeNewConnection();
        
       ...
    }
}

public class SimpleConnectionMaker {
	 public Connection makeNewConnection() throws ClassNotFoundException, 
     		SQLException{
    	////////////DB연결코드////////////
		Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection(
        			"jdbc:mysql://localhost/springbook", "spring", "book");
        ////////////////////////////////
        return c;
	}
}

  • Connection메소드를 SimpleConnectionMaker라는 하나의 클래스로 재생성
  • 클래스를 호출하여 사용함으로서 관심을 완전히 분리

◼ 위 코드의 문제점

  • 상속하여 썻을때와 달리 기능을 확장하여 사용하도록 하지 못함
    - 변경을 위해서는 관련 있는 코드를 직접 수정해야함

📄 인터페이스의 도입

인터페이스를 통해 두 클래스 사이 추상적인 느슨한 연결고리 생성

// 1.3.2 코드
public class UserDao {

	private ConnectionMaker connectionMaker;
    
    public UserDao() {
    	connectionMaker = new DConnectionMaker();
        //connectionMaker = new NConnectionMaker();
    }

	public void add(User user) throws ClassNotFoundException, SQLException {
    	Connection c = connectionMaker.makeNewConnection();
        
       ...
        
    }
    
    public User get(String id) throws ClassNotFoundException, SQLException {
    	Connection c = connectionMaker.makeNewConnection();
        
       ...
    }
}

//DB연결 인터페이스
public interface ConnectionMaker {
	public Connection makeConnection() throws ClassNotFoundException,
    SQLEXception;
}

public class DConnectionMaker implements ConnectionMaker {
	 public Connection makeConnection() throws ClassNotFoundException, 
     		SQLException{
    	// D제조사의 구현코드
	}
}

public class NConnectionMaker implements ConnectionMaker {
	 public Connection makeConnection() throws ClassNotFoundException, 
     		SQLException{
    	// N제조사의 구현코드
	}
}

  • ConnectionMaker인터페이스 타입 변수를 생성하여 느슨한 연결 유지
  • ConnectionMaker인터페이스를 상속하여 각 제조사가 필요에 맞게 구현

◼ 위 코드의 문제점

사용시 클래스 자체 생성자를 호출하는 코드가 남아있음

public class UserDao {
	private ConnectionMaker connectionMaker;
    
    public UserDao() {
    	connectionMaker = new DConnectionMaker(); //클래스를 직접 호출!!
    }
    
    ...
}

📄 관계설정 책임의 분리

의존관계를 설정하는 책임을 분리

//UserDao의 생성자 수정
public class UserDao {
	...
    
    //public UserDao() {
    	//connectionMaker = new DConnectionMaker(); //클래스를 직접 호출!!
    //}
    
    //수정
    public UserDao(ConnectionMaker connectionMaker){
		this.connectionMaker = connectionMaker;
	}
    
    ...
}

//새로운 클래스 UserDaoTest 생성
public class UserDaoTest {
	public static void main(String[] args) throws ClassNotFoundException, 
     		SQLException{
            
     // UserDao가 사용할 ConnectionMaker설정
     ConnectionMaker connectionMaker = new DConnectionMaker(); //D제조사 의존
     //ConnectionMaker connectionMaker = new NConnectionMaker(); //N제조사 의존
     
     UserDao dao = new UserDao(connectionMaker); // 오브젝트 제공하여 의존관계 설정
}

  • UserDao가 클래스를 결정하여 생성하던 변수매개변수로 받음
    - ConnectionMaker를 구현한 클래스를 결정하는 책임을 분리


📚 원칙과 패턴

📝 개방폐쇄원칙(OCP)

클래스나 모듈은 확장에는 열려있어야하고 변경에는 닫혀있어야 한다
- 클래스나 모듈이 기능을 확장할때 다른 클래스나 메소드에 영향을 주지않는 것

📝 높은 응집도와 낮은 결합도(high coherence and low coupling)

◼ 높은 응집도

변경이 일어날 때 해당 기능을 지닌 모듈의 많은 부분이 함께 변하면 응집도가 높다
- 관심사가 비슷한 것들이 한곳에 몰려있다

◼ 낮은 결합도

책임과 관심사가 다른 오브젝트들과는 느슨하게 연결된 형태를 유지하는 것
- 관심사가 다르면 독립적으로 구현하여 변화가 일어날때 영향이 없도록

📝 전략 패턴(Strategy Pattern)

자신의 기능 맥락에서 필요에 따라 변경이 필요한 알고리즘인터페이스를 통해 통째로 분리시키고 이를 구현한 클래스를 필요에 따라 바꿔쓸 수 있도록 하는 패턴



📖📌 제어의 역전(IoC)

프로그램의 제어 흐름 구조가 뒤바뀌는 것

  • 오브젝트가 자신직접 사용할 오브젝트를 생성하거나 선택하지 않음
    - 심지어는 자기자신이 어디서 어떻게 만들어지고 사용되는지 모름

  • 제어의 역전의 예
    • 템플릿 메소드 패턴서브클래스가 작성한 확장한 메소드는 자기자신이 언제 어떻게 사용될지 모름
      - 슈퍼클래스가 필요에 의해 호출되서 사용이 되는 형태

    • 라이브러리와 달리 프레임워크는 자신이 흐름을 주도하며 필요에 의해 개발자가 만든 코드를 호출해서 사용하는 형태이다
      - 프레임워크에 의해 코드가 수동적으로 사용되는 형태
      - 반면 라이브러리코드가 라이브러리를 호출해서 쓰는 능동적 형태


📖 오브젝트 팩토리를 이용한 스프링IoC

📄 오브젝트 팩토리

◼ 팩토리

객체의 생성 방법을 결정하고 만들어진 오브젝트를 돌려주는 오브젝트
- 디자인 패턴의 팩토리메소드와는 다른 것이니 혼동하지 않도록 주의!

public class DaoFactory{
	public UserDao userDao{
    	// UserDao가 사용할 ConnectionMaker설정
     ConnectionMaker connectionMaker = new DConnectionMaker(); //D제조사 의존
     //ConnectionMaker connectionMaker = new NConnectionMaker(); //N제조사 의존
     
     UserDao dao = new UserDao(connectionMaker); // 오브젝트 제공하여 의존관계 설정
     
     return userDao;
    }
}

public class UserDaoTest {
	public static void main(String[] args) throws ClassNotFoundException, 
     		SQLException{
            
    	UserDao dao = DaoFactory().userDao();//팩토리가 대신 생성
    }
}

  • 클라이언트(UserDaoTest)가 직접 생성하지 않고 팩토리에 요청하여 받아서 사용
    - 클라이언트가 필요에 의해 팩토리를 호출
    - UserDao자신이 어떤 ConnectionMaker를 받을지 모름
        - 심지어는 UserDao 자기자신이 언제 어디서 어떻게 사용되는지 자신은 모름
    ⇒ 제어의 역전이 잘 적용된 형태

◼ 오브젝트 팩토리의 활용

팩토리에 같은 기능이 필요한 다른 클래스들이 들어왔을때 중복을 제거하기 위해 그 기능을 하는 오브젝트를 생성

public class DaoFactory{
	public UserDao userDao{
    	//사용할 ConnectionMaker설정
     ConnectionMaker connectionMaker = new DConnectionMaker(); //D제조사 의존
     //ConnectionMaker connectionMaker = new NConnectionMaker(); //N제조사 의존
     
     UserDao dao = new UserDao(connectionMaker); // 오브젝트 제공하여 의존관계 설정
     
     return userDao;
    }
    
    public AccountDao accountDao() {
    	//사용할 ConnectionMaker설정
     	ConnectionMaker connectionMaker = new DConnectionMaker(); //D제조사 의존
     	//ConnectionMaker connectionMaker = new NConnectionMaker(); //N제조사 의존
        
        return new AccountDao(connectionMaker);
    }
}

//다음 코드가 중복
{
  //사용할 ConnectionMaker설정
  ConnectionMaker connectionMaker = new DConnectionMaker(); //D제조사 의존
  //ConnectionMaker connectionMaker = new NConnectionMaker(); //N제조사 의존
}

// 다음과 같이 수정
public class DaoFactory{
	public UserDao userDao{  
     	return new UserDao(connectionMaker()); 
    }
    
    public AccountDao accountDao() {
        return new AccountDao(connectionMaker());
    }
    
    public ConncetionMaker connectionMaker() {
    	return new DConnectionMaker();
        //return new NConnectionMaker();
    }
}

📄 애플리케이션 컨택스트와 설정정보

◼ 빈(bean)

스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트
- 스프링 빈 : 스프링 컨테이너가 생성, 관계설정, 사용을 제어하는
                 제어의 역전이 적용된 오브젝트

◼ 빈 팩토리(bean factory)

빈의 생성과 관계설정과 같은 제어를 담당하는 IoC 오브젝트

◼ 애플리케이션 컨텍스트(application context)

IoC방식을 따라 만들어진 빈 팩토리(bean factory)
- 빈 팩토리와 동일하지만 IoC엔진의 의미가 좀 더 부각
- 오브젝트 팩토리에서 사용했던 원리와 같은 원리를 사용

@Configuration //애플리케이션 컨텍스트 또는 빈 팩토리가 사용할 설정정보라는 표시
public class DaoFactory{
	
    @Bean //오브젝트 생성을 담당하는 IoC용 메소드라는 표시
    public UserDao userDao{  
     	return new UserDao(connectionMaker()); 
    }
    
    @Bean
    public ConncetionMaker connectionMaker() {
    	return new DConnectionMaker();
        //return new NConnectionMaker();
    }
}

public class UserDaoTest {
	public static void main(String[] args) throws ClassNotFoundException, 
     		SQLException{
        //애플리케이션 컨텍스트 적용    
    	ApplicationContext context = 
        	new AnnotationConfigApplicationContext(DaoFactory.class);
        UserDao dao = 
        		context.getBean("userDao", UserDao.class); //빈 이름만 알면 접근가능
    }
}

  • 팩토리와 동일하게 클라이언트가 필요에 의해 애플리케이션 컨택스트를 호출
  • 애플리케이션 컨텍스트빈의 이름으로 사용할 오브젝트를 알아서 찾음
  • 만들어진 오브젝트를 클라이언트가 받아서 사용

◼ 애플리케이션 컨텍스트의 장점

  • 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다
  • 애플리케이션 컨텍스트는 종합 IoC서비스를 제공해준다
  • 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다


📖 싱글톤 레지스트리와 오브젝트 스코프

📄 오브젝트의 동일성과 동등성

동일성

완전히 같은 오브젝트 '=='연산자로 비교
- 주소값까지 같은 오브젝트

동등성

동일한 정보를 가진 오브젝트 'equals()'메소드로 비교
- 주소값은 다르지만 동일한 정보를 지님

📄 싱글톤

여러번에 걸쳐 오브젝트를 요청해도 매번 동일한 오브젝트가 나오는 것
- 하나의 오브젝트만 만들어서 그것을 공유하는 형태

📒 싱글톤 패턴

오브젝트를 하나만 만들도록 강제하는 패턴
- 하나만 만들어진 오브젝트는 전역적으로 접근가능

  • 한계
    • private 생성자를 가지고 있어 상속불가
    • 테스트 하기가 힘듦
    • 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못함
    • 싱글톤의 사용은 전역상태를 만들어 바람직하지 못함

📄 싱글톤 레지스트리

스프링이 제공하는 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능
- 싱글톤 패턴과 달리 스프링이 지향하는 객체지향적인 설계에 아무런 제약이 없다!

📄 스프링 빈의 스코프

빈이 생성되고, 존재하고, 적용되는 범위
- 기본 스코프는 싱글톤이다 (경우에 따라 다른 스코프를 가질 수 있음)



📖📌📌 의존관계 주입(Dependency Injection)

IoC라는 용어가 폭넓게 사용되는 용어여서 스프링의 IoC 기능을 명확하게 설명하지 못함
- 의존관계 주입(DI)라는 의도가 명확히 드러나는 이름 사용

🔷 의존관계란?

다른 클래스나 모듈이 변화하면 그에 영향을 받아 자신도 변화하는 것
- A가 B에게 의존하고 있다 - B가 변화하면 A에 영향을 미친다
- 의존관계엔 방향성이 존재한다 (A가 변화해도 B에는 영향이 없다)

💡 의존관계 주입(Dependency Injection, DI)이란?

오브젝트 레퍼런스외부로부터 제공(주입)받고 이를 통해 여타 오브젝트와 다이나믹하게 의존관계가 만들어지는 것

  • 조건
    • 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다
      - 이를 위해서 인터페이스에만 의존해야함!
    • 런타임 시점의 의존관계컨테이너나 팩토리 같은 제3의 존재가 결정
    • 의존관계는 사용할 오브젝트에 대한 래퍼런스외부에서 제공(주입) 해줌으로써 만들어짐

📄 UserDao의 의존관계

  • 다시보는 코드
public class DaoFactory{
	public UserDao userDao{  
     	return new UserDao(connectionMaker()); 
    }
}

public class UserDaoTest {
	public static void main(String[] args) throws ClassNotFoundException, 
     		SQLException{
            
    	UserDao dao = DaoFactory().userDao();//팩토리가 대신 생성
    }
}

  • UserDao는 ConnectionMaker에만 의존하고있음
    - DConnectionMaker가 바뀌어도 아무런 영향이 없다
    - 인터페이스에만 의존을 하고있으면 결합도가 낮다
  • DaoFactory가 UserDao와 DConnectionMaker사이의 의존관계를 주입해줌
  • UserDao와 DConnectionMaker는 런타임시점에 의존관계가 형성
    ⇒ 의존관계 주입의 조건 만족!

  • 사전에 DB를 연결하겠다고 설계는 미리 해놨지만 DB코드의 구체적인 내용은 UserDao가 알 수 없음
    - DConnectionMaker는 런타임 시에 의존관계를 맺는 대상 - 의존 오브젝트
    - 의존관계 주입 : 클라이언트와 의존 오브젝트를 연결해주는 작업
                          (클라이언트에 의존 오브젝트를 주입해주는 작업)

🔷 의존관계 검색(Dependency Lookup, DL)

의존관계를 맺는 방법이 외부에서의 주입이 아닌 스스로 검색을 이용
- 자신이 스스로 어떤 클래스의 오브젝트를 선택하는건 아님
- 외부에서 만든 오브젝트를 가져올때, 스스로 컨테이너에게 요청하는 방식

public class UserDao {
	...
    
    //이전코드
    public UserDao(ConnectionMaker connectionMaker){
		this.connectionMaker = connectionMaker;
	}
    
    //의존관계 검색코드 
    public UserDao(){ 										//UserDao가 
		DaoFactory daofactory = new DaoFactory();
    	this.connectionMaker = daoFactory.connectionMaker();//직접 DaoFactory에게 요청
	}
    
    //의존관계 검색코드 - 애플리케이션 컨택스트 사용
    public UserDao() {
    	AnnotationConfigApplicationContext context = 
        	new AnnotationConfigApplicationContext(DaoFactory.class);
        this.connectionMaker = 
        		context.getBean("connectionMaker", ConnectionMaker.class);    
    }
    ...
}

◼ 의존관계 검색과 의존관계 주입

기능자체는 거의 동일함
- 의존관계 주입코드가 더욱 간결
- 의존관계 주입성격이 다른 오브젝트에 의존하지 않음
⇒ 대개는 의존관계 주입을 쓰는게 바람직 하다

  • 의존관계 검색에서는 검색하는 오브젝트 자신스프링의 빈일 필요가 없다
  • 의존관계 주입에서는 주입받는 오브젝트컨테이너가 만드는 빈 오브젝트여야한다

🔷 메소드를 통한 의존관계 주입

◼ 수정자(setter) 메소드를 이용한 주입

파라미터로 전달된 값(외부)인스턴스 변수(내부)에 저장하는것이 핵심기능
- 외부로부터 받은 오브젝트내부의 메소드에서 사용하는 DI방식에 활용 가능

◼ 일반 메소드를 이용한 주입

수정자한번에 하나의 파라미터만 가지는 제약이 존재
- 여러개의 파라미터를 갖는 일반메소드를 통해 DI용으로 사용



❌ XML설정

스프링의 어노테이션 == XML설정정보
- 요즈음은 잘 쓰지 않음


❗ 더욱 상세한 내용을 알고싶으시다면 책을 구매하시는 것을 추천드립니다.

profile
모르는 것 정리하기

0개의 댓글