[Spring] 확장 및 클래스의 분리

public abstract class UserDao {
	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = getConnection();

		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 {
		Connection c = getConnection();
		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;
	}

	abstract protected Connection getConnection() throws ClassNotFoundException, SQLException ;
}

위 코드는 일부기능을 추상화 메소드로 만들고, 해당 클래스를 상속받아 통하여 확장성 있게 바꾸었다.

하지만 이 방법은 상속을 사용했다는 단점이 있다.

문제점

1. 상속의 한계로 이미 UserDao가 다른 목적을 위해 상속을 사용하고 있다면? 단지 커넥션을 가져오는 방법을 분리하기 위해 상속 구조로 만들어버리면, 후에 다른 목적으로 UserDao에 상속을 적용하기 힘들다.

2.서브클래스는 슈퍼클래스의 기능을 직접 사용할 수 있다. 따라서 슈퍼클래스 내부의 변경이 있을 때 모든 서브클래스를 함께 수정하거나 다시 개발해야 할 수도 있다. 반대로 그런 불편을 주지 않기 위해 슈퍼클래스가 더 이상 변화하지 않도록 제약을 가해야 할 지도 모른다.

3. 확장된 기능인 DB커넥션을 생성하는 코드를 다른 DAO클래스에 적용할 수 없다는 것도 큰단점이다. 만약 다른 DAO클래스 들이 계속 만들어진다면 그때는 상속을 통해서 만들어진 getConnection의 구현 코드가 매 DAO클래스마다 중복돼서 나타나는 심각한 문제가 발생한다.

 


클래스 분리

처음에는 독립된 메소드로 분리했고, 다음에는 상하위 클래스로 분리했다. 이번에는 상속관계도 아닌 완전히 독립적인 클래스로 분리하자.

 

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

	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = this.simpleConnectionMaker.getConnection();

		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 {
		Connection c = this.simpleConnectionMaker.getConnection();
		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;
	}
 }

UserDao는 상속을 통한 방법을 쓰지 않으니 더이상 abstract일 필요가 없다.

생성자를 통하여 SimpleConnectionMaker의 오브젝트를 받아 이를 add/get 메소드에서 사용하면 된다.

 

하지만 UserDao코드가 SimpleConnectionMaker라는 특정 클래스에 종속되어 있는 형태로, 상속을 사용했을 때 처럼 UserDao코드의 수정없이 DB커넥션 생성기능을 변경할 방법이 없다.

 

문제점

1.  확장성 있는 DB커넥션을 제공하는 클래스를 사용하기 위해서는 생성자의 커넥션 생성객체를 만드는 코드를 수정해야 한다.

2. UserDao에서 DB커넥션을 가져오는 클래스에 대하여 모든 것을 알고있어야한다. 어떤 클래스가 사용되어야 할지, 어떤 메소드를 호출해야하는지 까지

 


인터페이스 도입

클래스를 분리하면서도 해결할 수 있는 방법.

가장 좋은 해결책은 두 개의 클래스가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 느슨한 연결고리를 만들어 주는것.

추상화란? 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업이다.

이를 위해 자바가 제공해주는 것은 인터페이스 이다.

*인터페이스는 자신을 구현한 클래스에 대한 구체적인 정보는 모두 감춰버린다.

즉, 오브젝트를 만들려면 구체적인 클래스 하나를 선택해야 겠지만 인터페이스로 추상화 해놓은 최소한의 통로를 통해 접근하는 쪽에서는 오브젝트를 만들 때 사용할 클래스가 무엇인지 몰라도 된다.

 

public interface ConnectionMaker {

	public abstract Connection makeConnection() throws ClassNotFoundException,
			SQLException;

}
public class UserDao {
	private ConnectionMaker connectionMaker;
	
	public UserDao(ConnectionMaker simpleConnectionMaker) {
		this.connectionMaker = simpleConnectionMaker;
	}

	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = this.connectionMaker.makeConnection();

		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 {
		Connection c = this.connectionMaker.makeConnection();
		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;
	}

위와 같이 ConnectionMaker의 구체적인 기능을 구현한 클래스를 만들고 UserDao에서 사용한다면, Connection기능을 뜯어 고치더라도

UserDao를 고칠일은 없다.

 

하지만 아직 DConnectionMaker라는 클래스를 직접호출하여 오브젝트를 생성하는 코드가 남아있다. 이를 없애보자.

 


관계설정 책임의 분리

UserDao 오브젝트가 DConnectionMaker 오브젝트를 사용하게 하려면 두 클래스의  오브젝트 사이에 런타임 사용관계 또는 링크, 또는 의존관계라고 불리는 관계를 맺어주면 된다.

public UserDao(ConnectionMaker simpleConnectionMaker) {
		this.connectionMaker = simpleConnectionMaker;
}

connectionMaker를 생성하는 책임을 UserDao를 사용하는 Client에게 넘겨줌으로서 UserDao는 connectionMaker오브젝트를 결정해야하는 책임에서 벗어났다.

 


개방 폐쇄 원칙

개방 폐쇄 원칙은 깔끔한 설계를 위해 적용 가능한 객체지향 설계 원칙 중의 하나이다.

즉, 클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

UserDao는 DB 연결 방법이라는 기능을 확장하는 데는 열려 있다. 동시에 UserDao 자신의 핵심 기능을 구현한 코드는 그런 변화에 영향을 받지 않고 유지 할 수 있으므로 변경에는 닫혀 있다고 말 할 수 있다.

 

댓글

Designed by JB FACTORY