본문 바로가기
책 정리/토비의 스프링

1장 오브젝트와 의존관계

by 이석준석이 2021. 1. 19.

github.com/robin00q/tobyspring

 

robin00q/tobyspring

Contribute to robin00q/tobyspring development by creating an account on GitHub.

github.com

Bean

  • Default 생성를 갖고있어야 한다. (프레임워크가 리플렉션을 통해서 오브젝트를 생성하기 때문)

 

관심사의 분리

  • 관련이 있는 내용들을 한 객체로 모으고, 다른것은 떨어뜨려 서로 영향을 주지 않도록 하자.

1. 토비의 스프링에 있는 UserDao 관심사의 분리하기

 

1. 상속을 통한 확장

  • N, D사의 Connection을 위한 NUserDao, DUserDao 로 분리한다.
  • 템플릿 메소드 패턴을 기반으로
    • abstract Class인 UserDao 와 abstract method 인 getConnection() 을 하위에서 구현하도록 하여 관심사를 분리하였다.

Template Method 패턴

해당 그림에 대한 코드는 아래와 같을 것으로 예상된다.

abstract class UserDao {
	abstract getConnection();
}

class NUserDao extends UserDao {
	getConnection() {
    	Connection nCon = ...// N사의 jdbcUrl, username, password를 가진 Connection 생성
    	return nCon;
    }
}

class DUserDao extends UserDao {
	getConnection() {
    	Connection dCon = ...// D사의 jdbcUrl, username, password를 가진 Connection 생성
    	return dCon;
    }
}

 

위의 코드를 보면 팩토리 메소드 패턴도 갖고있기 때문에 토비의스프링 1장에서의 상속을 통한 확장은 아래와같은 그림을 띄게된다.

 

Factory Method Pattern

 

  • 단점
    • 생성과 사용을 명확하게 분리했으나,
      • UserDao와 NUserDao, DUserDao 사이의 의존성이 너무 강하기때문에, UserDao 클래스에 변화에 대해서 NUserDao, DUserDao 모두에게 영향을 끼치게 되므로, 유지보수가 어렵게 된다.
    • Connection 또한 마찬가지이다.

2. 클래스 분리 및 인터페이스 도입

 

  • 추상화를 통해서 Connection 생성을 위한 인터페이스를 정의한 뒤 사용하는 방식으로 변경해보자

Interface

그림만 보면 명확하게 분리된 듯 하지만 사실은 코드내에 어떤 Connection을 사용하는지 드러난다.

public class UserDao {

    private ConnectionMaker connectionMaker;

    public UserDao() {
    	// 여기서 new DConnectionMaker() 가 드러나게 된다.
        connectionMaker = new NConnectionMaker();
    }
}

public interface ConnectionMaker {
	getConnection();
}

public class NConnectionMaker {
	getConnection() { return nCon; }
}

public class DConnectionMaker {
	getConnection() { return dCon; }
}

 

명확한 그림은 위의그림이 아닌 아래와같은 그림이기 때문이다.

interface2

단점

  • 어떤 Connection을 사용하느냐에 따라서, UserDao 내부의 코드는 변경되어야 한다.

3. 관계설정 책임을 클라이언트에 위임

 

  • 위의 방식들로는 UserDao의 코드를 변경하지 않고서는 Connection을 변경할 수 없는 문제가 있다.
  • 클라이언트를 사용해서 클라이언트가 UserDao에게 어떤 Connnection 을 사용하라고 하도록 위임하자.

클라이언트에 위임

public class UserDaoTest {
	public static void main(String args[]) {
    	// 클라이언트가 어떤 Connection 을 사용하라고 말해준다.
    	UserDao userDao = new UserDao(new DConnectionMaker());
    }
}

public class UserDao {

    private ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker) {
    	// UserDao 내에서 NConnection / DConnection 에 대한 코드는 없다.
    	this.connectionMaker = connectionMaker;
    }
}

public interface ConnectionMaker {
	getConnection();
}

public class NConnectionMaker {
	getConnection() { return nCon; }
}

public class DConnectionMaker {
	getConnection() { return dCon; }
}

 

장점

  • 위의 코드를 보면 이제 더이상 UserDao 에서는 NConnection / DConnection 을 생성하는 것은 사라졌으며, 그 역할을 UserDaoTest에 위임하여 UserDaoTest 가 수행한다.
  • 새로운 커넥션을 만들고 싶다면, XConnection 클래스를 생성하여 만들면되며, UserDao의 코드는 변경이 필요없다.
    • 단지 UserDaoTest 의 코드만 변경되면 될 뿐이다.

2. 객체지향 5원칙 중, OCP

 

1. OCP (개방 폐쇄 원칙)

  • 확장에는 열려있어야 하며, 변경에는 닫혀있어야 한다.
    • 위의 코드에서 본 것 처럼, 새로운 Connection을 생성할 때, 확장에는 열려있으며 그 확장에 대해서 UserDao는 수정되지 않는다.

3. 팩토리 만들기

 

UserDaoTest 가 UserDao 의 생성을 맡아왔다.

  • 따로 UserDao를 만들기만 하는, 팩토리를 정의하도록 하자.
public class DaoFactory {
    public ConnectionMaker connectionMaker() {
        return new DConnectionMaker();
    }

    public UserDao userDao() {
        UserDao userDao = new UserDao(connectionMaker());
        return userDao;
    }
}

public class UserDaoTest {
	public static void main(String args[]) {
    	UserDao userDao = new DaoFactory().userDao();
		// ...
    }
}

4. 제어의 흐름

 

  • 현재 제어의 흐름은 UserDaoTest 에서 시작해서, 객체를 생성한 뒤 진행된다.
  • 제어의 역전
    • 클래스의 생성 주체를 다른곳에서 관리한다는 뜻이다.
    • 위의 코드도 제어의 역전이 적용되어있다.
      • UserDao 와 Connection은 이제 DaoFactory에 의해 결정되는 수동적인 존재가 되었다.

5. DaoFactory 스프링 빈 등록

 

ApplicationContext

  • @Configuration 어노테이션과 @Bean 어노테이션을 통해서 UserDao, ConnectionMaker 를 스프링 빈으로 등록한다.
@Configuration
public class DaoFactory {
    @Bean
    public ConnectionMaker connectionMaker() {
        return new DConnectionMaker();
    }

    @Bean
    public UserDao userDao() {
        UserDao userDao = new UserDao(connectionMaker());
        return userDao;
    }
}

 

등록된 빈들은 이제, Spring ApplicationContext에 의해서 관리되게 되며, 별다른 설정없이 빈으로 등록한 경우 싱글톤이 보장된다.

  • ApplicationContext 는 싱글톤을 저장하고 관리하기 때문에 싱글톤 레지스트리이기도 하다.

 

Why Using singleton?

  • 서버 어플리케이션을 운영하며, 매번 Request 마다 객체를 생성하여 관리를 한다면
    • Garbage Collection 에서의 손해가 일어날 수 있다.

 

싱글톤은 불편하여 안티패턴으로 불리는데 왜 singleton?

  • private 생성자이므로, 상속이 불가능하며, 테스트가 어렵다.
  • 서버 환경에서 여러개로 분산되어있다면 완벽한 싱글톤으로의 관리가 아니다.

 

스프링 싱글톤 레지스트리는 이를 해결해준다.

  • 싱글톤임을 스프링 레지스트리가 보장해주기 때문에
    • 일반 객체처럼 사용할 수 있다.
      • public 생성자를 가질 수 있다.
      • 디자인패턴을 적용할 수 있다.

6. 의존관계 주입

 

인터페이스를 통한 클래스간의 의존성을 제거한 뒤, 클래스는 인터페이스에 의존하게 한 후, IoC 컨테이너에 의해서 파라미터 등으로 의존관계를 런타임에 주입해 주는 것.


7. DataSource 인터페이스 적용

 

ConnectionMaker 를 사용했지만

  • 자바에는 DB 커넥션을 가져오는 오브젝트의 기능을 추상화한 DataSource 인터페이스가 있다.
  • 토비의 스프링에서는 SimpleDriverDataSource 구현체를 사용하여 학습한다. ( 나는 hikari 로 했다.) 
    • jdbc driver, url, id, pass를 쉽게 설정할 수 있다.
@Configuration
public class DaoFactory {
	//...

    @Bean
    public DataSource dataSource() {
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDriverClassName("org.h2.Driver");
        hikariDataSource.setJdbcUrl("jdbc:h2:tcp://localhost/~/tobyspring");
        hikariDataSource.setUsername("sa");
        hikariDataSource.setPassword("");
        return hikariDataSource;
    }
    
    //...
}

 

 

'책 정리 > 토비의 스프링' 카테고리의 다른 글

6.1. AOP  (0) 2021.02.02
5. 서비스 추상화  (0) 2021.02.01
4. 예외  (0) 2021.01.30
3. 템플릿  (0) 2021.01.23
2. 테스트  (0) 2021.01.23