본문 바로가기
  • 1+1=3
독서/개발관련

[토비의 스프링 vol.1] 1.1~1.4 개방폐쇄원칙과 제어의 역전

by 여스 2021. 12. 11.
반응형

1.1초난감코드

- DAO란

data access object. DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 객체

 

1.1에 나오는 DAO객체에는 문제가 많다.(토비님은 이런 코드면 바로 쫓겨날 정도로 한심한 코드라고 한다)

why?를 생각하라

 

잘 동작하는 코드를 굳이 수정하고 개선해야 하는 이유는 뭘까?

개선했을 때 장점은 무엇인가?

장점들 중 당장에, 그리고 미래에 주는 유익은 뭘까?

이게 객체지향 설계의 원칙과는 무슨 상관이 있을까?

 

스프링을 공부한다는 건 이런 문제를 스스로 제기하고 답을 찾아나가는 과정이다.

 

1.2 DAO의 분리

미래에 어떤 변화가 올지 모른다.

그 변경이 일어날 때 필요한 작업을 최소화하고, 그 변경이 다른 곳에 문제를 일으키지 않도록 해야 함.

이게 바로 분리와 확장을 고려한 설계. == 관심사의 분리

 

1.2.2 중복코드 메소드 추출

DB커넥션을 가져오는 코드가 add()에도 있고 get()에도 있다.

getConnection()으로 분리. 이제 DB연결과 관련된 부분에 변경이 일어날 경우(예를 들면 드라이버 클래스와 URL이 바뀐 경우) getConnection() 안의 코드만 수정하면 된다!

 

1.2.3 변화에 유연한 걸 넘어서, 변화를 반기는 DAO를 만들어보자

내가 UserDao를 너~무 잘 만들어서 팔수 있어. 네이버랑 다음한테 팔 건데, 나의 UserDao소스코드는 공개하고 싶지 않아. 근데 그러면서 네이버랑 다음이 어케 자기들 DB에 연결하여 UserDao를 사용하도록 할까?

- 템플릿 메소드 패턴과 팩토리 메소드패턴

public abstract class UserDao{
	public void add(User user) {
    	Connection c = getConnection();
    	...
    }

	public User get(String id){
    	Connection c = getConnection();
        ...
    }
    
    public abstract Connection getConnection()
    
    public Class NUserDao extends UserDao{
    	public Connection getConnection(){
        	네이버 DB Connection 생성코드
        }
    }
    
     public Class DUserDao extends UserDao{
    	public Connection getConnection(){
        	다음 DB Connection 생성코드
        }
    }

}

위 코드는 탬플릿 메소드 패턴과 팩토리 메소드 패턴을 적용한 것이다

 

-템플릿 메소드 패턴

슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상메소드(or 오버라이딩 가능한 protected 메소드)로 만든 뒤 서브클래스한테 일 떠넘겨서 알아서 해! 라고 한것

 

-팩토리 메소드 패턴

서브클래스에서 구체적인 오브젝트 생성방법을 결정하게 하는 것. 위 예시에선 getConnection()메소들 어케 정의하냐에 따라 서브클래스마다 생성방법이 달라질 수 있도록 한 것임.

 

- 단점(상속의 한계)

만약 NUserDao가 다른 걸 상속하고 있다면 이미??다중상속 불가ㅠㅠ

또한 슈퍼클래스 내부의 변경이 있을 때 모든 서브클래스에 영향을 줄 여지가 있음. 그럼 상속한 애들 다 다시 개발해야 함.

 

1.3 클래스의 분리

클래스를 따로 만들자! 근데 그냥 따로 만들면 new 할때 네이버연결이면 네이버연결 객체만 생성되버림. 다음에서 UserDao클래스를 쓸때엔 다음연결클래스가 new되어야 해....

 

->인터페이스로 해결!

인터페이스를 도입하면 UserDao는 자신이 사용할 클래스가 네이버연결인지 다음연결인지는 이제 내알바가 아님.

글구 생성자에 받는 파라미터 타입을 이 인터페이스로 설정(관계설정 책임이 추가된 것)

지금 뭘한거냐면,

관심을 분리해서 클라이언트(메소드 쓰는곳)에다가 책임을 떠넘긴 것이다.

즉, 클라이언트는 자기가 UserDao를 사용하는 입장이니까 어떤 방식으로 DB연결할지 구체적으로 정할 책임이 있는 것이다

public class UserDao{
	private ConnectionMaker connectoinMaker;
    
    public UserDao(ConnectionMaker connectionMaker){
    	this.connection = connectionMaker;
    }
    
    public void add(User user){
    	Connection c = connectoinMaker.makeConnection();
        ...
    }
    
    public void get(User user){
    	Connection c = connectoinMaker.makeConnection();
        ...
    }
}

클라이언트에선,,,,

public static void main(String[] args){

	ConnectionMaker connectionMaker = new NConnectionMaker();
}

 

이렇게 인터페이스를 도입하고, 클라이언트의 도움을 얻으면 상속에 비해 훨씬 유리해진다.

다른 Dao클래스에서도 저 ConnectionMaker를 사용할 수도 있는데 그대로 사용하면 되기 때문임.

 

 

1.3.4 원칙과 패턴

- 개방 폐쇄 원칙

클래스나 모듈은 확장에는 열려있어야 하고, 변경에는 닫혀있어야 한다. 와 명언이다. 위 예를 보면, UserDao는 디비 연결방법은 얼마든지 변경할 수 있으며, 동시에 UserDao의 핵심기능은 그런거 영향 안받고 잘 굴러간다.

 

(참고로 객체지향원칙은 SOLID라고 함

SRP : 단일책임원칙

OCP: 개방폐쇄원칙

LSP : 리스코프 치환원칙

ISP : 인터페이스 분리원칙

DIP : 의존관계 역전 원칙)

 

-높은 응집도와 낮은 결합도

개방폐쇄원칙은 곧 높은 응집도와 낮은 결합도라는 말로 설명된다.

UserDao 클래스는 응집도가 높다 왜냐면, 사용자 데이터를 처리하는 기능이 여기저기 흩어지지 않고 dao안에 딱 있으니까 이해하기 쉽고 깔끔.

 

낮은 결합도가 더 민감하다.

결합도란 하나의 오브젝트가 변경이 일어날 때에 관계를 맺고 있는 다른 오브젝트에 변화를 요구하는 정도임. 대충 감이 온다. 위 예시에서 ConnectionMaker라는 인터페이스 도입 덕분에 db연결하는 NConnectoinMaker가 바뀌어도 UserDao는 수정할 게 없는 걸 말한다.

 

1.4 제어의 역전

1.4.1 오브젝트 팩토리

팩토리가 등장하는 것은 테스트를 하는 것에서부터 시작한다.

기존의 UserDaoTest를 보자. 

public class UserDaoTest{
	public static void main(String[] args){
    	ConnectionMaker connectionMaker = new DConnectionMaker();
        
        UserDao dao = new UserDao(connectoinMaker);
        ...
    }
}

보면 문제점이 있다. UserDaoTest는 UserDao만 테스트하고 싶은데, 위에서 ConnectionMaker까지 new하고 있다. 관심사가 하나 더 생겨버린 것이다. 또다른 책임을 맡는 셈이니 문제가 있다.

 

-팩토리의 탄생

걍 객체 생성방법을 결정하고 만들어주는 애다. 오브젝트를 생성하는 역할을 분리해주기 위함이다.

public class DaoFactory{
	public UserDao userDao(){
    	ConnectionMaker connectionMaker = new DConnectionMaker();
        UserDao userDao = new UserDao(connectionMaker);
        
        return userDao;
    }
}

위처럼 팩토리에서 ConnectionMaker를 직접 생성하고, UserDao까지 만들어준다면,

UserDaoTest는 이제 자신의 관심사인 UserDao오브젝트 받아서 테스트만 신경쓰면 된다.

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

 

1.4.3 제어관계의 역전

원래 초기의 초난감코드에서 UserDaoTest의 main()함수에서는 UserDao 클래스의 오브젝트를 직접 생성해서 사용했음.

그게 정상처럼 보여.

그러나 이제 팩토리의 탄생으로 UserDaoTest의 main()에서는 더이상 사용할 클래스를 직접 new하지 않는다. 

초난감 코드를 개선했던 탬플릿 메서드도 마찬가지다. 서브클래스는 추상 UserDao를 상속해서 getConnection()을 구현하였는데, 이 메서드가 언제 사용될지도 모르고, 나중에 슈퍼클래스에서 그 기능이 필요할 때 호출할때 사용됐음. 즉, 제어권을 상위 템플릿 메서드에 넘기고 자신은 필요할 때 호출되어 사용된다.

다시 팩토리를 보자.

UserDao는 자기가 어떤 ConnectionMaker를 쓸지(네이버 할지 다음 할지) 결정권한이 있었으나, 이제 DaoFactory가 결정해서 넘겨준다. 심지어 UserDao자신도 팩토리에 의해 수동적으로 생성된다.

결국 UserDaoTest는 DaoFactory가 만들고 초기화해서 사용하도록 공급해주는 ConnectionMaker만 사용할 수 밖에 없어진다.

 

 

1.5부터는 스프링에서의 IoC이다. 그건 다음글에서 작성하자

반응형

댓글