IT 개발노트

Spring-오브젝트와 의존관계 본문

기초튼튼/Spring

Spring-오브젝트와 의존관계

limsungju 2019. 5. 4. 22:20

1. Spring-오브젝트와 의존관계

1.1 DAO(Data Access Object)란
: DAO는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말한다.

1.2 자바빈(JavaBean)
: 자바빈은 원래 비주얼 툴에서 조작 가능한 컴포넌트를 말한다.

이제는 자바빈을 컴포넌트라기보다는 다음 두 가지 관례를 따라 만들어진 오브젝트를 가리킨다.
- 디폴트 생성자 : 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야한다. 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문에 필요하다.
- 프로퍼티 : 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 set으로 시작하는 수정자 메소드(setter)와 get으로 시작하는 접근자 메소드(getter)를 이용해 수정 또는 조회할 수 있다.

1.3 JDBC를 이용하는 작업의 순서
- DB 연결을 위한 Connection을 가져온다.
- SQL을 담은 Statement (또는 PreparedStatement)를 만든다.
- 만들어진 Statement를 실행한다.
- 조회의 경우 SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트에 옮겨준다.
- 작업 중에 생성된 Connection, Statement, ResultSet 같은 리소스는 작업을 마친 후 반드시 닫아준다.
- JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.

위와 같은 형식으로 만든 클래스가 제대로 동작하는지 확인하려면, 먼저 웹 애플리케이션을 만들어 서버에 배치하고, 웹 브라우저를 통해 DAO 기능을 사용해보면 된다.

1.4 main()을 이용한 DAO 테스트 코드
: 만들어진 코드의 기능을 검증하고자 할 때 사용할 수 있는 가장 간단한 방법은 오브젝트 스스로 자신을 검증하도록 만들어주는 것이다.
main() 메소드안에 UserDAO의 오브젝트를 생성해서 add()와 get() 메소드를 검증한다. 먼저 UserDTO 오브젝트를 생성하고 프로퍼티에 값을 넣은 다음에 add() 메소드를 이용해 DB에 등록해본다. Connection 설정과 콛에 모두 이상이 없다면 main() 메소드는 에러 없이 종료된다.


위에 코드는 초난감 코드의 DAO코드이다. 객체지향 기술의 원리에 충실한 스프링 스타일의 코드로 개선해 보자

1.5 관심사의 분리
객체지향의 세계에서는 모든 것이 변한다. 여기서 변한다는 것은 변수나 오브젝트 필드의 값이 변한다는게 아니라 오브젝트에 대한 설계와 이를 구현한 코드가 변한다는 뜻이다. 소프트웨어에서 끝이란 개념은 없다. 사용자의 비즈니스 프로세스와 이에따른 요구사항은 끊임없이 변하고 발전한다. 예를들어 DB 접속용 암호를 변경하라는 요구사항이 생겼는데 이로인해 DAO클래스 수백개를 모두 수정하거나 다른 개발자가 개발한 코드에 변경이 일어날 때마다 내가 만든 클래스도 함께 수정을 해야한다면 너무 번거롭기 때문에 관심이 같은 것끼리 모으로, 관심이 다른 것은 따로 만드는 것이다.

가장 문제가 되는 것은 DB연결을 위한 Connection 오브젝트를 가져오는 부분이다. 현재 DB 커넥션을 가져오는 코드는 다른 관심사와 섞여서 같은 add() 메소드에 담겨 있다. 더 큰 문제는 add() 메소드에 있는 DB 커넥션을 가져오는 코드와 동일한 코드가 get() 메소드에도 중복되어 있다는 점이다. 
새로운 메소드를 생성할 때마다 똑같은 코드를 만들어야 하고, 수정을 할때마다 모든 메소드를 수정해야 한다는 불편한 점이 있다.

중복 코드의 메소드 추출
중복된 DB 연결 코드를  getConnection()이라는 독립적인 메소드로 만들어 두고, 각 DAO 메소드에서는 이렇게 분리한 getConnection() 메소드를 호출해서 DB 커넥션을 가져오게 만든다.

- getConnection() 메소드를 추출해서 중복을 제거한 UserDao


UserDao에 소스코드를 고객사에게 직접 제공하지 않고, 고객에게는 미리 컴파일된 클래스 바이너리 파일만 제공하고 싶다, 과연 이런 경우에 UserDao 소스코드를 N사와 D사에 제공해주지 않고도 고객 스스로 원하는 DB 커넥션 생성 방식을 적용해가면서 UserDao를 사용하게 할 수 있을까?
상속을 통한 방식으로 확장하면된다.
기존 UserDao 코드를 한 단계 더 분리하면 된다. 일단 우리가 만든 UserDao에서 메소드의 구현 코드를 제거하고, getConnection()을 추상 메소드로 만들어놓는다. 추상 메소드라서 메소드 코드는 없지만 메소드 자체는 존재한다. 따라서 add(), get() 메소드에서 getConnection()을 호출하는 코드는 그래도 유지할 수 있다.

기존에는 같은 클래스에 다른 메소드로 분리됐던 DB 커넥션 연결이라는 관심을 이번에는 상속을 통해 서브클래스로 분리해버리는 것이다.

슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법을 디자인 패턴에서 템플릿 메소드 패턴이라고 한다. 서브클래스에서 구체적인 오브젝트를 생성방법을 결정하는 것은 팩토리 메소드 패턴이라고 한다.
상속방식에는 문제점이 있다. 자바에서는 다중 상속이 안될뿐더러 상속이라는 관계 때문에 문제가 생긴다.
상속방식의 문제점은 부모 클래스를 변경하게 되면 상속받은 모든 클래스에 영향이 가기 때문에 부모클래스를 수정할 수 없는 상태가 될 수도 있기 때문이다.

템플릿 메소드 패턴(template method pattern)
: 슈퍼클래스에 기본적인 로직의 흐름(커넥션 가져오기, SQL 생성, 실행, 반환)을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법

상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법이다.
변하지 않는 기능은 슈퍼클래스에 만들어두고 자주 변경되며 확장할 기능은 서브클래스에서 만들도록 한다.
슈퍼클래스에서는 미리 추상 메소드 또는 오버라이드 가능한 메소드를 정의해두고 이를 활요해 코드의 기본 알고리즘을 담고 있는 템플릿 메소드를 만든다. 슈퍼클래스에서 디폴트 기능을 정의해두거나 비워뒀다가 서브클래스에서 선택적으로 오버라이드할 수 있도록 만들어둔 메소드를 훅(hook) 메소드라고 한다.
서브클래스에서는 추상 메소드를 구현하거나, 훅 메소드를 오버라이드하는 방법을 이용해 기능의 일부를 확장한다.

기본 알고리즘 골격을 담은 메소드를 템플릿 메소드라 부른다. 템플릿 메소드는 서브클래스에서 오버라이드 하거나 구현할 메소드를 사용한다.

슈퍼클래스의 메소드를 오버라이드하거나 구현해서 기능을 확장한다. 다양한 확장 클래스를 만들 수 있다.

슈퍼클래스의 메소드를 필요할 때 호출해서 사용하는 것으로, 즉 제어권을 상위 템플릿 메소드에 넘기고 자신은 필요할 때 호출되어 사용되도록 한다. (제어의 역전)
-> 템플릿 메소드는 제어의 역전이라는 개념을 활용해 문제를 해결하는 디자인 패턴이라고 볼 수 있다.

팩토리 메소드 패턴(factory method pattern)
: 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것.

팩토리 메소드 패턴도 템플릿 메소드 패턴과 마찬가지로 상속을 통해 기능을 확장하게 하는 패턴이다. 그래서 구조도 비슷하다. 슈퍼클래스 코드에서는 서브클래스에서 구현할 메소드를 호출해서 필요한 타입의 오브젝트를 가져와 사용한다. 이 메소드는 주로 인터페이스 타입으로 오브젝트를 리턴하므로 서브클래스에서 정확히 어떤 클래스의 오브젝트를 만들어 리턴할지는 슈퍼클래스에서는 알지 못한다.
서브클래스는 다양한 방법으로 오브젝트를 생성하는 메소드를 재정의할 수 있다. 이렇게 서브클래스에서 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드를 팩토리 메소드라고 하고, 이 방식을 통해 오브젝트 생성 방법을 나머지 로직, 즉 슈퍼클래스의 기본 코드에서 독립시키는 방법을 팩토리 메소드 패턴이라고 한다.

- 템플릿 메소드 패턴 또는 팩토리 메소드 패턴으로 관심사항이 다른 코드를 분리해내고, 서로 독립적으로 변경 또는 확장할 수 있도록 만드는 것은 간단하면서도 효과적인 방법이다.
하지만 상속을 사용했다는 단점이 있다. (자바에서는 다중상속이 안돼기 때문이다.)

1.6클래스의 분리
이번에는 관심사가 다르고 변화의 성격이 다른 이 두가지 코드를 분리해볼 생각이다. 두 개의 관심사를 본격적으로 독립시키면서 동시에 손쉽게 확장할 수 있는 방법을 알아보자.
지금까지는 성격이 다른, 그래서 다르게 변할 수 있는 관심사를 분리하는 작업을 진행해왔다. 처음에는 독립된 메소드를 만들어서 분리했고, 다음에는 상하위 클래스로 분리했다. 이번에는 아예 상속관계도 아닌 완전히 독립적인 클래스로 만들어 보겠다.
방법은 간단하다 DB 커넥션과 관련된 부분을 서브클래스가 아니라, 아예 별도의 클래스에 담는다. 그리고 이렇게 만든 클래스를 UserDao가 이용하게 하면 된다.
SimpleConnectionMaker라는 새로운 클래스를 만들고 DB생성 기능을 그 안에 넣는다. 그리고 UserDao는 new 키워드를 사용해 SimpleConnectionMaker 클래스의 오브젝트를 만들어두고, 이를 add(), get() 메소드에서 사용하면 된다. 각 메소드에서 매번 새로 만든 클래스를 만들 수도 있지만 그보다는 한번 오브젝트로 만들어서 저장해두고 이를 계속 사용하는 편이 낫다.

DB 커넥션 생성 기능을 독립시킨 SimpleConnectionMaker는 아래와 같이 만든다.

UserDao의 코드가 SimpleConnectionMaker라는 특정 클래스에 종속되어 있기 때문에 상속을 사용했을 때처럼 UserDao 코드의 수정없이 DB 커넥션 생성 기능을 변경할 방법이 없다. 다른 방식으로 DB 커넥션을 제공하는 클래스를 사용하기 위해서는 UserDao 소스코드의

simpleConnectionMaker = new SimpleConnectionMaker();

줄을 직접 수정해야 한다. UserDao의 소스코드를 함께 제공하지 않고는 DB 연결 방법을 바꿀 수 없다는 문제에 직면하게 된다.

클래스를 분리한 경우에도 상속을 이용했을 때와 마찬가지로 자유로운 확장이 가능하게 하려면 두 가지 문제를 해결해야 한다.
첫째로 SimpleConnectionMaker의 메소드가 문제다. 만약 고객사에서 만든 DB 커넥션 제공 클래스는 openConnection()이라는 메소드 이름을 사용했다면 다음과 같이 모두 변경해야한다.

Connection c = simpleConnectionMaker.openConnection();

둘째로 DB 커넥션을 제공하는 클래스가 어떤 것인지를 UserDao가 구체적으로 알고 있어야 한다.
UserDao에 SimpleConnectionMaker라는 클래스 타입의 인스턴스 변수까지 정의해놓고 있으니, 고객사에서 다른 클래스를 구현하려면 어쩔수 없이 UserDao 자체를 다시 수정해야 한다.

이런 문제의 근본적인 요인은 UserDao가 바뀔 수 있는 정보, 즉 DB 커넥션을 가져오는 클래스에 대해 너무 많이 알고 있기 때문이다. 어떤 클래스가 쓰일지, 그 클래스에서 커넥션을 가져오는 메소드의 이름이 뭔지까지 알고 있어야한다.
따라서 UserDao는 DB 커넥션을 가져오는 구체적인 방법에 종속되어 버린다.

1.7 인터페이스의 도입
위와같은 문제를 해결하기 제일 좋은 방법은 두 개의 클래스가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 느슨한 연결고리를 만들어주는 것이다. 추상화란 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업이다. 이에 가장 유용한 도구가 바로 인터페이스다.
인터페이스는 자신을 구현한 클래스에 대한 구체적인 정보는 모두 감춰버린다. 결국 오브젝트를 만들려면 구체적인 클래스 하나를 선택해야겠지만 인터페이스로 추상화해놓은 최소한의 통로를 통해 접근하는 쪽에서는 오브젝트를 만들때 사용할 클래스가 무엇인지 몰라도 된다. 인터페이스를 통해 접근하게 하면 실제 구현 클래스를 바꿔도 신경 쓸 일이 없다.

인터페이스에는 어떻게 하겠다는 구현 방법은 나타나 있지 않다. 그것은 인터페이스를 구현한 클래스들이 알아서 결정할 일이다. UserDao가 인터페이스를 사용하게 한다면 인터페이스의 메소드를 통해 알 수 있는 기능에만 관심을 가지면 되지, 그 기능을 어떻게 구현했는지에는 관심을 둘 필요가 없다.

- ConnectionMaker 인터페이스

- ConnectionMaker 구현 클래스

아래는 특정 클래스 대신 인터페이스를 사용해서 DB 커넥션을 가져와 사용하도록 수정한 UserDao코드다.

위와 같이 인터페이스를 적용한 방식으로 한다면 고객사가 DB 접속용 클래스를 다시 만든다고 해도 UserDao의 코드를 하나하나 수정할 일은 없을 것이다. 왜냐하면 인터페이스 참조형 변수를 만들고 사용할 커넥션 클래스의 인스턴스를 생성해서 사용하기 때문이다. 그리고 클래스가 바뀐다고 해도 같은 인터페이스를 구현한다면 같은 메소드를 사용하기 때문에 메소드 이름이 변경될 걱정도 할 필요가 없다.

여전히 문제 하나가 존재하는데, 바로 UserDao의 생성자에서 DConnectionMaker()라고 인스턴스가 지정되어 있다는 점이다. 나중에 NConnectionMaker()로 변경 된다면 UserDao 코드를 줘야한다는 단점이 있다. 즉 아직도 종속적인 부분을 완전히 해결 못한 경우다.

관계설정 책임의 분리
현재는 UserDao 클래스의 main() 메소드가 UserDao 클라이언트라고 볼 수 있다.
좀 더 깔끔하게 구분하기 위해, 아예 UserDaoTest라는 이름의 클래스를 하나 만들고 UserDao에 있던 main() 메소드를 UserDaoTest로 옮겨보자. 그리고 UserDao의 생성자를 수정해서 클라이언트가 미리 만들어둔 ConnectionMaker의 오브젝트를 전달 받을 수 있도록 파라미터를 하나 추가한다. UserDao의 생성자는 이제 아래 코드처럼 바뀌었다.
- 수정한 생성자

클라이언트와 같은 제3의 오브젝트가 UserDao 오브젝트가 사용할 ConnectionMaker 오브젝트를 전달해주도록 만든 것이다. 이렇게 하고나니 UserDao에는 ConnectionMaker의 구체적인 구현 클래스 이름인 DConnectionMaker가 사라졌고, 처음 기대했던 아래의 구조가 반영된 코드가 된 것이다.



리팩토링이란 ?
: 리팩토링은 기존의 코드를 외부의 동작방식에는 변화 없이 내부 구조를 변경해서 재구성하는 작업 또는 기술을 말한다.
리팩토링을 하면 코드 내부의 설계가 개선되어 코드를 이해하기가 더 편해지고, 변화에 효율적으로 대응할 수 있다.
결국 생산성은 올라가고, 코드의 품질은 높아지며, 유지보수하기 용이해지고, 견고하면서도 유연한 제품을 개발할 수 있다.

원칙과 패턴
- 개방 폐쇄 원칙 (OCP, Open-Closed Principle)

: 클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.
인터페이스를 통해 제공되는 확장 포인트는 확장을 위해 활짝 개방되어 있다. 반면 인터페이스를 이용하는 클래스는 자신의 변화가 불필요하게 일어나지 않도록 굳게 폐쇄되어 있다.

- 객체지향 설계 원칙(SOLID)
: 객체지향 설계 원칙은 객체지향의 특징을 잘 살릴 수 있는 설계의 특징을 말한다.
원칙이라는 건 어떤 상황에서든 100% 지켜져야 하는 절대적인 기준이라기보다는, 예외는 있겠지만 대부분의 상황에 잘 들어맞는 가이드라인과 같은 것이다.

디자인 패턴은 특별한 상황에서 발생하는 문제에 대한 좀 더 구체적인 솔루션이라고 한다면,
객체지향 설계 원칙은 좀 더 일반적인 상황에서 적용 가능한 설계 기준이라고 볼 수 있다.

http://www.nextree.co.kr/p6960/

- 높은 응집도
: 응집도가 높다는 것은 변화가 일어날 때 해당 모듈에서 변하는 부분이 크다는 것으로 설명할 수 있다.
즉 변경이 일어날 때 모듈의 많은 부분이 함께 바뀐다면 응집도가 높다고 말할 수 있다.

- 낮은 결합도
: 책임과 관심사가 다른 오브젝트 또는 모듈과는 낮은 결합도, 즉 느슨하게 연결된 형태를 유지하는 것이 바람직하다.
느슨한 연결은 관계를 유지하는 데 꼭 필요한 최소한의 방법만 간접적인 형태로 제공하고, 나머지는 서로 독립적이고 알 필요도 없게 만들어주는 것이다. 결합도가 낮아지면 변화에 대응하는 속도가 높아지고, 구성이 깔끔해지고 또한 확장하기에도 매우 편리하다.

결합도란 '하나의 오브젝트가 변경이 일어날 때에 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도'라고 한다.

- 전략 패턴(Stage Pattern)
: 전략 패턴은 개방 폐쇄 원칙의 실현에도 가장 잘 들어 맞는 패턴이라고 볼 수 있따. 전략 패턴은 자신의 기능 맬락(context)에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴이다.

- 상속구조를 통해 성격이 다른 관심사항을 분리한 코드를 만들어내고, 서로 영향을 덜 주도록 하기.
-> 다중상속 문제, 클래스명이 바뀌는 등 여러가지 문제 발생
-> 인터페이스 도입 ( 클래스명이 바뀌어도 인터페이스의 메소드만 사용하기 때문에 코드를 수정할 필요가 없다.)

스프링 IoC의 용어 정리

- 빈(bean)
: 빈 또는 빈 오브젝트는 스프링이 IoC방식으로 관리하는 오브젝트라는 뜻이다. 관리되는 오브젝트라고 부르기도 한다. 주의할 점은 스프링을 사용하는 애플리케이션에서 만들어지는 모든 오브젝트가 다 빈은 아니라는 사실이다. 그 중에서 스프링이 직접 그 생성과 제어를 담당하는 오브젝트만을 빈이라고 부른다.

- 빈 팩토리(bean factory)
: 스프링의 IoC를 담당하는 핵심 컨테이너를 가리킨다. 빈을 등록하고, 생성하고, 조회하고 돌려주고, 그 외에 부가적인 빈을 관리하는 기능을 담당한다. 보통은 이 빈 팩톨리를 바로 사용하지 않고 이를 확장한 애플리케이션 컨텍스트를 이용한다. BeanFactory라고 붙여쓰면 빈 팩토리가 구현하고 있는 가장 기본적인 인터페이스의 이름이 된다. 이 인터페이스에 getBean()과 같은 메소드가 정의되어 있다.

- 애플리케이션 컨텍스트(application context)
: 빈 팩토리를 확장한 IoC 컨테이너다. 빈을 등록하고 관리하는 기본적인 기능은 빈 팩토리와 동일하다. 여기에 스프링이 제공하는 각종 부가 서비스를 추가로 제공한다. 빈 팩토리라고 부를 때는 주로 빈의 생성과 제어의 관점에서 이야기하는 것이고, 애플리케이션 컨텍스트라고 할 때는 스프링이 제공하는 애플리에키션 지원 기능을 모두 포함해서 이야기하는 것이라고 보면된다. 스프링에서는 애플리케이션 컨텍스트라는 용어를 빈 팩토리보다 더 많이 사용한다. ApplicationContext라고 적으면 애플리케이션 컨텍스트가 구현해야 하는 기본 인터페이스를 가리키는 것이기도 하다.
ApplicationContext는 BeanFactory를 상속한다.

- 설정정보/설정 메타정보(configuration metadata)
: 스프링의 설정정보란 애플리케이션 컨텍스트 또는 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보를 말한다. 이는 구성정보 내지는 형상정보라는 의미다. 실제로 스프링의 설정정보는 컨테이너에 어떤 기능을 세팅하거나 조정하는 경우에도 사용하지만, 그보다는 IoC 컨테이너에 의해 관리되는 애플리케이션 오브젝트를 생성하고 구성할 때 사용된다. 애플리케이션의 형상정보라고 부르기도 한다. 또는 애플리케이션의 전체 그림이 그려진 청사진(blueprints)라고도 한다.

- 컨테이너(container) 또는 IoC 컨테이너
: IoC 방식으로 빈을 관리한다는 의미에서 애플리케이션 컨텍스트나 빈 팩토리를 컨테이너 또는 IoC 컨테이너라고도 한다. 후자는 주로 빈 팩토리의 관점에서 이야기하는 것이고, 그냥 컨테이너 또는 스프링 컨테이너라고 할 때는 애플리케이션 컨텍스트를 가리키는 것이라고 보면된다. 컨테이너라는 말 자체가 IoC의 개념을 담고 있기 때문에 이름이 긴 애플리케이션 컨텍스트 대신에 스프링 컨테이너라고 부르는 걸 선호하는 사람들도 있다. 또 컨테이너라는 말은 애플리케이션 컨텍스트보다 추상적인 표현이기도 하다. 애플리케이션 컨텍스트는 그 자체로 ApplicationContext 인터페이스를 구현한 오브젝트를 가리키기도 하는데, 애플리케이션 컨텍스트 오브젝트는 하나의 애플리케이션에서 보통 여러 개가 만들어져 사용된다. 이를 통틀어서 스프링 컨테이너라고 부를 수 있다.
때로는 컨테이너라는 말을 떼고 스프링이라고 부를 때도, 바로 이 스프링 컨테이너를 가리키는 것일 수 있다. 예를 들어 '스프링에 빈을 등록하고'라는 식으로 말하는 경우에 스프링이라는 말은 스프링 컨테이너 또는 애플리케이션 컨텍스트를 가리키는 말이다.

- 스프링 프레임워크
: 스프링 프레임워크는 IoC컨테이너, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말할 때 주로 사용한다. 그냥 스프링이라고 중여서 말하기도 한다.

'기초튼튼 > Spring' 카테고리의 다른 글

Spring Directory Guide  (1) 2022.09.08