UserDao와 ConnectionMaker는 각각 애플리케이션의 핵심적인 데이터 로직과 기술 로직을 담당하고 있고, DaoFactory는 이런 애플리케이션의 오브젝트들을 구성하고 그 관계를 정의하는 책임을 맡고 있다.
전자가 실질적인 로직을 담당하는 컴포넌트라면, 후자는 애플리케이션을 구성하는 컴포넌트의 구조와 관계를 정의한 설계도 같은 역할을 한다고 볼 수 있다.
설계도라고 하면 간단히 어떤 오브젝트가 어떤 오브젝트를 사용하는지를 정의해놓은 코드라고 생각하면 된다.
이런 작업이 애플리케이션 전체에 걸쳐 일어난다면 컴포넌트의 의존관계에 대한 설계도와 같은 역할을 하게 될 것이다.
DaoFactory를 분리했을 때 얻을 수 있는 장점은 매우 다양하다. 그중에서도 애플리케이션의 컴포넌트 역할을 하는 오브젝트와 애플리케이션의 구조를 결정하는 오브젝트를 분리했다는 데 가장 의미가 있다.
1.4.2 오브젝트 팩토리의 활용
DaoFactory를 좀 더 살펴보자.
DaoFactory에 UserDao가 아닌 다른 DAO의 생성 기능을 넣으면 어떻게 될까?
AccountDao, MessageDao 등을 만들었다고 해보자. 이 경우에 UserDao를 생성하는 userDao() 메소드를 복사해서 accountDao(), messageDao() 메소드를 만든다면 새로운 문제가 발생한다.
ConnectionMaker 구현 클래스의 오브젝트를 생성하는 코드가 메소드마다 반복되는 것이다.
어떤 ConnectinoMaker 구현 클래스를 사용할지를 결정하는 기능이 중복돼서 나타난다고 볼 수 있다.
여러 개의 DAO를 생성하는 메소드가 추가된 리스트 1-16을 살펴보자.
public class DaoFactory {
public UserDao userDao() {
return new UserDao(new DConnectionMaker()); // ConectionMaker 구현 클래스를 선정하고 생성하는 코드의 중복
}
public AccountDao accountDao() {
return new AccountDao(new DConnectionMaker()); // ConectionMaker 구현 클래스를 선정하고 생성하는 코드의 중복
}
public MessageDao messageDao() {
return new MessageDao(new DConnectionMaker()); // ConectionMaker 구현 클래스를 선정하고 생성하는 코드의 중복
}
}
[리스트 1-16 DAO 생성 메소드의 추가로 인해 발생하는 중복]
세 개의 DAO를 만드는 팩토리 메소드 안에 모두 new DConnectionMaker라는 ConnectionMaker 구현 클래스의 인스턴스를 만드는 부분이 반복돼서 나타난다.
이렇게 오브젝트 생성 코드가 중복되는 건 좋지 않은 현상이다. DAO가 더 많아지면 ConnectionMaker의 구현 클래스를 바꿀 때마다 모든 메소드를 일일이 수정해야 하기 때문이다.
중복 문제를 해결하려면 역시 분리해내는 게 가장 좋은 방법이다.
ConnectionMaker의 구현 클래스를 결정하고 오브젝트를 만드는 코드를 별도의 메소드로 뽑아내자.
DAO를 생성하는 각 메소드에서는 새로 만든 ConnectionMaker 생성용 메소드를 이용하도록 수정한다.
이렇게 해두면 아무리 DAO 팩토리 메소드가 많아져도 문제가 없다.
ConnectionMaker의 구현 클래스를 바꿀 필요가 있을 때도 리스트 1-17과 같이 딱 한 군데만 수정하면 모든 DAO 팩토리 메소드에 적용된다.
public class DaoFactory {
public UserDao userDao() {
return new UserDao(connectionMaer());
}
public AccountDao accountDao() {
return new AccountDao(connectionMaer());
}
public MessageDao messageDao() {
return new MessageDao(connectionMaer());
}
public ConnectionMaker connectionMaer() {
return new DConnectionMaker(); // 분리해서 중복을 제거한 ConnectionMaker 타입 오브젝트 생성 코드
}
}
[리스트 1-17 생성 오브젝트 코드 수정]
1.4.3 제어권의 이전을 통한 제어관계 역전
제어의 역전이라는 건, 간단히 프로그램의 제어 흐름 구조가 뒤바뀌는 것이라고 설명할 수 있다.
모든 오브젝트가 능동적으로 자신이 사용할 클래스를 결정하고, 언제 어떻게 그 오브젝트를 만들지를 스스로 관장한다. 모든 종류의 작업을 사용하는 쪽에서 제어하는 구조다.
제어의 역전이란 이런 제어 흐름의 개념을 거꾸로 뒤집는 것이다.
제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 당연히 생성하지도 않는다. 또 자기도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다.
모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.
프로그램의 시작을 담당하는 main()과 같은 엔트리 포인트를 제외하면 모든 오브젝트는 이렇게 위임받은 제어 권한을 갖는 특별한 오브젝트에 의해 결정되고 만들어진다.
제어의 역전 개념은 사실 이미 폭넓게 적용되어 있다. 서블릿을 개발해서 서버에 배포할 수는 있지만, 그 실행을 개발자가 직접 제어할 수 있는 방법은 없다. 서블릿 안에 main() 메소드가 있어서 직접 실행시킬 수 있는 것도 아니다.
대신 서블릿에 대한 제어 권한을 가진 컨테이너가 적절한 시점에 서블릿 클래스의 오브젝트를 만들고 그 안의 메소드를 호출한다.
이렇게 서블릿이나 JSP, EJB처럼 컨테이너 안에서 동작하는 구조는 간단한 방식이긴 하지만 제어의 역전 개념이 적용되어 있다고 볼 수 있다.
제어의 역전 개념이 적용된 예를 디자인 패턴에서도 여럿 찾아볼 수 있다.
제어권을 상위 탬플릿 메소드에 남기고 자신은 필요할 때 호출되어 사용되도록 한다는, 제어의 역전 개념을 사용한다.
탬플릿 메소드는 제어의 역전이라는 개념을 활용해 문제를 해결하는 디자인 패턴이라고 볼 수 있다.
프레임워크도 제어의 역전 개념이 적용된 대표적인 기술이다.
프레임워크가 어떤 것인지 이해하려면 라이브러리와 프레임워크가 어떻게 다른지 알아야 한다.
라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다. 단지 동작하는 중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용할 뿐이다.
반면에 프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다.
보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하도록 만드는 방식이다.
최근에는 톨킷, 엔진, 라이브러리 등도 유행을 따라서 무작정 프레임워크라고 하는데, 이는 잘못된 것이다.
프레임워크에는 분명한 제어의 역전 개념이 적용되어 있어야 한다.
애플리케이션 코드는 프레임워크가 짜놓은 틀에서 수동적으로 동작해야 한다.
자연스럽게 관심을 분리하고 책임을 나누고 유연하게 확장 가능한 구조로 만들기 위해 DaoFactory를 도입했던 과정이 바로 IoC를 적용하는 작업이었다고 볼 수 있다.
IoC를 적용함으로써 설계가 깔끔해지고 유연성이 증가하며 확장성이 좋아지기 때문에 필요할 때면 IoC 스타일의 설계의 코드를 만들어 사용하면 된다.
제어의 역전에서는 프레임워크 또는 컨터이너와 같이 애플리케이션 컴포넌트의 생성과 관계설정, 사용, 생명주기 관리 등을 관장하는 존재가 필요하다.
DaoFactory는 오브젝트 수준의 가장 단순한 IoC 컨테이너 내지는 IoC 프레임워크라고 불릴 수 있다.
단순한 적용이라면 DaoFactory와 같이 IoC 제어권을 가진 오브젝트를 분리해서 만드는 방법이면 충분하겠지만, IoC를 애플리케이션 전반에 걸쳐 본격적으로 적용하려면 스프링과 같은 IoC 프레임워크의 도움을 받는 편이 훨씬 유리하다.
스프링은 IoC를 모든 기능의 기초가 되는 기반 기술로 삼고 있으며, IoC를 극한까지 적용하고 있는 프레임워크다.
UserDaoTest는 기존에 UserDao가 직접 담당하던 기능, 즉 어떤 ConnectionMaker 구현 클래스를 사용할지를 결정하는 기능을 엉겁결에 떠맡았다. UserDao가 ConnectionMaker 인터페이스를 구현한 특정 클래스로부터 완벽하게 독립할 수 있도록 UserDao의 클라이언트인 UserDaoTest가 그 수고를 담당하게 된 것이다.
그런데 원래 UserDaoTest는 UserDao의 기능이 잘 동작하는지를 테스트하려고 만든 것이 아닌가? 그런데 지금은 또 다른 책임까지 떠맡고 있으니 뭔가 문제가 있다. 성격이 다른 책임이나 관심사는 분리해버리는 것이 지금까지 해왔던 주요한 작업이다. 그러니 이것도 분리하자.
이렇게 분리될 기능은 UserDao와 ConnectionMaeker 구현 클래스의 오브젝트를 만드는 것과, 그렇게 만들어진두 개의 오브젝트가 연결돼서 사용될 수 있도록 관계를 맺어주는 것이다.
팩토리
분리시킬 기능을 담당할 클래스를 하나 만들어보겠다. 이 클래스의 역할은 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 것인데, 이런 일을 하는 오브젝트를 흔히 팩토리(factory)라고 한다.
오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 깔끔하게 분리하려는 목적으로 사용하는 것이다. 어떻게 만들지와 어떻게 사용할지는 분명 다른 관심이다.
팩토리 역할을 맡을 클래스는 DaoFactory라고 하자. 그리고 UserDaoTest에 담겨 있던 UserDao, ConnectionMaker 관련 생성 작업을 DaoFactory로 옮기고, UserDaoTest에서는 DaoFatory에 요청해서 미리 만들어진 UserDao 오브젝트를 가져와 사용하게 만든다.
package springbook.uer.dao;
...
public class DaoFactory {
public UserDao userDao() {
// 팩토리의 메소드는 UserDao 타입의 오브젝트를 어떻게 만들고, 어떻게 준비시킬지를 결정한다.
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
return userDao;
}
}
[리스트 1-14 UserDao의 생성 책임을 맡은 팩토리 클래스]
DaoFactory의 UserDao 메소드를 호출하면 DConnectionMaker를 사용해 DB 커넥션을 가져오도록 이미 설정된 UserDao 오브젝트를 돌려준다.
UserDaoTest는 이제 UserDao가 어떻게 만들어지는지 어떻게 초기화되어 있는지에 신경 쓰지 않고 팩토리로부터 UserDao 오브젝트를 받아다가, 자신의 관심사인 테스트를 위해 활용하기만 하면 된다. 이렇게해서 각각이 자신의 책임에만 충실하도록 역할에 따라 분리하는 작업을 마쳤다.
이렇게 수정된 UserDaoTest의 코드는 리스트 1-15와 같다.
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
UserDao dao = new DaoFactory().userDao();
...
}
}
1995년부터 1999년까지는 윈도우 프로그램 개발이 주류였기 때문에 C++에 비해 자바는 아주 열세였다.
1999년도부터 인터넷이 활성화되면서 웹 애플리케이션 구축용 언어로 자바가 급부상했다. 그 이유는 기업체 및 공공기관의 다양한 서버 운영체제에서 단 한 번의 작성으로 모든 곳에서 실행 가능한 언어는 자바뿐이었기 때문이다.
초기의 자바는 가전 제품에 탑재할 프로그래밍 언어로 개발되었지만, 지금은 스마트폰을 비롯해서 각종 장비와 데스크톱에서 실행되는 애플리케이션, 그리고 금융, 공공, 대기업 등의 엔터프라이즈 기업 환경에서 실행되는 서버 애플리케이션을 개발하는 중추적인 언어로 자리매김하고 있다.
1.2.2 자바의 특징
이식성이 높은 언어이다.
이식성이란: 서로 다른 실행 환경을 가진 시스템 간에 프로그램을 옮겨 실행할 수 있는 것
자바 언어로 개발된 프로그램은 소스 파일을 다시 수정하지 않아도, 자바 실행 환경(JRE: Java Runtime Environment)이 설치되어 있는 모든 운영체제에서 실행 가능하다. 따라서 자바 언어는 이식성이 매우 높은 프로그래밍 언어라고 볼 수 있다.
객체 지향 언어이다.
객체 지향 프로그래밍(OOP: Object Oriented Programming) 이란: 프로그램을 개발하는 기법으로 부품에 해당하는 객체들을 먼저 만들고, 이것들을 하나씩 조립 및 연결해서 전체 프로그램을 완성하는 기법
이 때 사용되는 언어를 객체 지향 언어라고 한다.
자바는 100% 객체 지향 언어다. 객체를 만들기 위해 설계도인 클래스를 작성해야 하고, 객체와 객체를 연결하여 목적에 맞는 프로그램을 만들어 낸다. 처음부터 객체를 고려하여 설계되었기 때문에 객체 지향 언어가 가져야 할 캡슐화, 상속, 다형성 기능을 완벽하게 지원하고 있다.
함수적 스타일 코딩을 지원한다.
최근 들어 함수적 프로그래밍이 다시 부각되고 있는데, 대용량 데이터의 병렬 처리 그리고 이벤트 지향 프로그래밍을 위해 적합하기 때문이다.
자바는 함수적 프로그래밍을 위해 람다식을 자바 8부터 지원한다.
람다식을 사용하면 컬렉션의 요소를 필터링, 매핑, 집계 처리하는데 쉬워지고, 코드가 매우 간결해진다.
메모리를 자동으로 관리한다.
C++은 메모리에 생성된 객체를 제거하기 위해 개발자가 직접 코드를 작성해야 한다. 만약 이 작업을 성실하게 해주지 않으면, 프로그램은 불완전해지고 갑자기 다운되는 현상을 겪게 된다.
자바는 개발자가 직접 메모리에 접근할 수 없게 설계되었으며, 메모리는 자바가 직접 관리한다. 객체 생성 시 자동적으로 메모리 영역을 찾아서 할당하고, 사용이 완료되면 쓰레기 수집기(Garbarge Collector)를 실행시켜 자동적으로 사용하지 않는 객체를 제거시켜준다.
따라서 개발자는 메모리 관리의 수고스러움을 덜고, 핵심 기능 코드 작성에 집중할 수 있다.
다양한 애플리케이션을 개발할 수 있다.
자바는 윈도우, 리눅스, 유닉스, 맥 등 다양한 운영체제(OS: Operating System)에서 실행되는 프로그램을 개발할 수 있다.
단순한 콘솔 프로그램에서부터 클라이언트용 윈도우 애플리케이션, 서버용 웹 애플리케이션 그리고 모바일용 안드로이드 앱에 이르기까지 거의 모든 곳에서 실행되는 프로그램을 개발할 수 있다.
자바는 다양한 운영체제에서 사용할 수 있는 개발 도구와 API를 묶어 에디션(Edition) 형태로 정의하고 있다.
Java SE는 자바 프로그램들이 공통적으로 사용하는 자바 가상 기계(JVM: Java Virtual Machine)을 비롯해서 자바 프로그램 개발에 필수적인 도구와 라이브러리 API를 정의한다. 클라이언트와 서버 프로그램에 상관없이 자바 프로그램을 실행하기 위해서는 반드시 Java SE 구현체인 자바 개발 키트 (JDK: Java Development Kit)를 설치해야 한다.
<Java EE (Enterprise Edition - 서버용 애플리케이션 개발 에디션>
Java EE는 분산 환경(네트워크, 인터넷)에서 서버용 애플리케이션을 개발하기 위한 도구 및 라이브러리 API를 정의한다. 서버용 애플리케이션으로는 Servlet/JSP를 이용한 웹 애플리케이션, 분산 처리 컴포넌트인 EJB (Enterprise Java Bean) 그리고 XML 웹 서비스(Web Service) 등이 있다.
멀티 스레드를 쉽게 구현할 수 있다.
멀티 스레드: 하나의 프로그램이 동시에 여러 가지 작업을 처리해야 할 경우와 대용량 작업을 빨리 처리하기 위해 서브 작업으로 분리해서 병렬 처리하기 위해 필요
자바는 스레드 생성 및 제어와 관련된 라이브러리 API를 제공하고 있기 때문에 실행되는 운영체제와 상관없이 멀티 스레드를 쉽게 구현할 수 있다.
동적 로딩을 지원한다.
자바 애플리케이션은 여러 개의 객체가 서로 연결되어 실행되는데, 이 객체들은 클래스로부터 생성된다.
애플리케이션이 실행될 때 모든 객체가 생성되지 않고, 객체가 필요한 시점에 클래스를 동적로딩해서 객체를 생성한다. 또한 개발 완료 후 유지보수(수정)가 발생하더라도 해당 클래스만 수정하면 되므로 전체 애플리케이션을 다시 컴파일할 필요가 없다.
따라서 유지보수를 쉽고 빠르게 진행할 수 있다.
막강한 오픈소스 라이브러리가 풍부하다.
자바는 오픈소스 언어이기 때문에 자바 프로그램에서 사용하는 라이브러리 또한 오픈소스가 넘쳐난다.
검증된 오픈소스 라이브러리를 사용하면 개발 기간을 단축하면서 안전성이 높은 애플리케이션을 쉽게 개발할 수 있다.
많은 회사들이 자바를 선택하는 이유 중의 하나가 막강하고 풍부한 자바 오픈소스 라이브러리가 있기 때문이다.
1.2.3 자바 가상 기계(JVM)
운영체제는 자바 프로그램을 바로 실행할 수 없는데, 그 이유는 자바 프로그램은 완전한 기계어가 아닌, 중간 단계의 바이트 코드이기 때문에 이것을 해석하고 실행할 수 있는 가상의 운영체제가 필요하다. 이것이 자바 가상 기계 (JVM: Java Virtual Machine)이다.
JVM은 실 운영체제를 대신해서 자바 프로그램을 실행하는 가상의 운영체제 역할을 한다.
운영체제별로 프로그램을 실행하고 관리하는 방법이 다르기 때문에 운영체제별로 자바 프로그램을 별도로 개발하는 것보다는 운영체제와 자바 프로그램을 중계하는 JVM을 두어 자바 프로그램이 여러 운영체제에서 동일한 실행 결과가 나오도록 설계한 것이다.
따라서 개발자는 운영체제와 상관없이 자바 프로그램을 개발할 수 있다.
바이트 코드는 모든 JVM에서 동일한 실행 결과를 보장하지만, JVM은 운영체제에 종속적이다.
자바 프로그램을 운영체제가 이해하는 기계어로 번역해서 실행해야 하므로JVM은 운영체제에 맞게 설치되어야 한다.
JVM은 JDK 또는 JRE를 설치하면 자동으로 설치되는데, JDK와 JRE가 운영체제별로 제공된다.
그렇기 때문에 사람과 컴퓨터가 대화하기 위해서는 사람의 언어와 기계어의 연결 고리 역할을 하는 프로그래밍 언어가 필요하다.
프로그래밍 언어는 고급 언어와 저급언어로 구분된다.
고급 언어는 컴퓨터와 대화할 수 있도록 만든 언어 중에서 사람이 쉽게 이해할 수 있는 언어를 말한다.
고급 언어로 작성된 소스는 컴퓨터가 바로 이해할 수 없기 때문에 컴파일 과정을 통해서 0과 1로 이루어진 기계어로 변환 후 컴퓨터가 사용하게 된다.
반대로 저급 언어란 기계어에 가까운 언어를 말하는데, 대표적으로 어셈블리어가 저급 언어에 속한다.
일반적으로 프로그래밍 언어라고 하면 고급 언어를 말하는데, 대표적인 프로그래밍 언어인 C, C++. 자바는 모두 고급 언어에 속한다. 이 언어들로 작성된 내용을 소스라고 부르고, 이 소스는 컴파일러라는 소프트웨어에 의해 기계어로 변환된 후 컴퓨터에서 실행할 수 있게 된다.
우리가 흔히 말하는 프로그램이란 컴퓨터에서 특정 목적을 수행하기 위해 프로그래밍 언어로 작성된 소스를 기계어로 번역한 것을 말한다.