Sep. 2014 Youn-Hee Han http://link.koreatech.ac.kr Chapter 1. 오브젝트와 의존관계 Sep. 2014 Youn-Hee Han http://link.koreatech.ac.kr
1.1 초난감 DAO
1.1.1 User User 도메인 객체 User.java 자바빈(Javabeans) setter getter DB 테이블 디폴트 생성자 프로퍼티 setter getter DB 테이블 package book30.ch01.domain; public class User { String id; String name; String password; public String getId() { return id; } public void setId(String id) { this.id = id; public String getName() { return name; public void setName(String name) { this.name = name; public String getPassword() { return password; public void setPassword(String password) { this.password = password; DROP TABLE IF EXISTS `users`; CREATE TABLE users ( id varchar(10) primary key, name varchar(20) not null, password varchar(10) not null )
1.1.1 User 자바빈 다음 두 가지 관례에 따라 만들어진 객체 1) 디폴트 생성자 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다. 프레임워크에서 리플렉션(Reflection)을 이용하여 객체를 생성할 때 디폴트 생성자가 활용된다 2) 프로퍼티 자바빈이 노출하는 이름을 가진 속성 프로퍼티는 set으로 시작하는 수정자 메소드 (setter)와 get으로 시작하는 접근자 메소드 (getter)를 이용해 수정 또는 조회할 수 있다.
1.1.2 UserDao UserDao DAO (Data Access Object) DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하는 객체 public class UserDao { public void add(User user) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook?characterEncoding=UTF-8", “spring", "book"); 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(); }
1.1.1 UserDao UserDao DAO (Data Access Object) public User get(String id) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook?characterEncoding=UTF-8", “spring", "book"); 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; }
1.1.3 main()을 이용한 테스트 코드 테스트 코드 잘 동작하는 위 코드를 수정하고 개선해야 하는 이유? 스파게티 코드 (Spaghetti Code) public static void main(String[] args) throws ClassNotFoundException, SQLException { UserDao dao = new UserDao(); User user = new User(); user.setId("whiteship"); user.setName("백기선"); user.setPassword("married"); dao.add(user); System.out.println(user.getId() + " 등록 성공"); User user2 = dao.get(user.getId()); System.out.println(user2.getName()); System.out.println(user2.getPassword()); System.out.println(user2.getId() + " 조회 성공"); }
1.2 DAO의 분리
1.2.1 관심사의 분리 관심사의 분리 (Separation of Concerns) 개발자에게 요구되는 역량 관심이 같은 사항들은 하나의 객체 또는 주변 객체로 모음 관심이 다른 사항들은 가능한 따로 떨어져서 서로 영향을 주지 않도록 분리 소프트웨어 공학의 모듈화 (Modulization)와 유사 개발자에게 요구되는 역량 미래의 변화를 어떻게 대비하는가? 비지니스 로직의 변화는 집중된 한가지 관심에 대해 일어나지만 그에 따른 코딩 작업은 한 곳에 집중되지 않는 경우가 많다. 그러한 로직의 변화에 대처하여 자유롭고 편리하게 변경, 발전, 확장시킬 수 있는 역량 필요
1.2.2 커넥션 만들기의 추출 UserDao에서의 관심사항 예 Refactoring (리펙토링) DB 연결을 위한 커넥션 설정 SQL 쿼리문 생성 및 실행 리소스 반환 Refactoring (리펙토링) 외부의 동작방식에는 변화 없이 내부 구조를 변경해서 코드를 재구성하는 작업 또는 기술 코드 내부 설계가 개선되어 코드를 이해하기가 편해지고 이후 변화에 효율적으로 대응가능 생산성 및 코드 품질이 올라감 DB 연결을 위한 커넥션 설정 리펙토링 Extract Method (메소드 추출)
1.2.2 커넥션 만들기의 추출 중복 코드를 단일 메소드로 추출 (Extract Method)
1.2.3 DB커넥션 만들기의 독립 변화에 유연히 대처가 가능한 DAO 상황설명 본인이 만든 UserDao가 유명해져서 N사와 D사에서 구매 주문이 들어옴. N사와 D사가 각기 다른 종류의 DB를 사용하고 있어서 DB 커넥션을 가져오는 방법이 상이함. DB 커넥션을 가져오는 방법은 납품 이후에도 변경될 가능성이 있음. N사와 D사에 소스코드는 공개하지 않고 Binary 파일만을 제공하려고 함. N사와 D사 스스로 원하는 DB 커넥션 생성 방식을 적용해가면서 UserDao를 사용하게 하려고 함.
1.2.3 DB커넥션 만들기의 독립 변화에 유연히 대처가 가능한 DAO 상속 (Inheritance)를 통한 확장
1.2.3 DB커넥션 만들기의 독립 변화에 유연히 대처가 가능한 DAO 상속 (Inheritance)를 통한 확장
1.2.3 DB커넥션 만들기의 독립 변화에 유연히 대처가 가능한 DAO 상속 (Inheritance)를 통한 확장 새로운 DB 연결방법을 적용해야 할 때에는 UserDao를 상속하는 새로운 클래스 작성하여 사용
1.2.3 DB커넥션 만들기의 독립 템플릿 메소드 패턴 (Template Method Pattern) 슈퍼클래스에 변하지 않는 기본 로직을 구현하고 자주 변경되거나 확장이 이후에 요구될 수 있는 일부 기능을 추상 메소드나 오버라이딩이 가능한 protected 메소드로 구현 이러한 메소드는 hookMethod 라고도 불리운다. 서브클래스에서 hookMethod를 필요에 맞게 구현하여 사용 팩토리 메소드 패턴 (Factory Method Pattern)이라고도 함
1.2.3 DB커넥션 만들기의 독립 팩토리 메소드 패턴 (Factory Method Pattern) 서브클래스에서 구체적인 객체 생성 방법을 결정하게 하는 코딩 방법 getConnection()을 통해 만들어진 Connection 객체의 종류가 달라질 수 있는 것을 목적으로 함 슈퍼클래스인 UserDao에서는 자신이 사용할 Connection 인터페이스 타입의 객체라는 것 이외에는 관심을 두지 않는다.
1.2.3 DB커넥션 만들기의 독립 디자인 패턴 개발자들 사이의 언어 “UserDao에 팩토리 메소드 패턴을 적용해서 getConnection()을 분리합시다.”
1.2.3 DB커넥션 만들기의 독립 상속 (Inheritance)를 통한 확장의 단점 다중 상속 문제 NUserDao가 UserDao외에 다른 클래스도 상속을 받아야 할 때 다중 상속 허용 안됨 슈퍼클래스와 서브클래스의 긴밀한 결합 문제 슈퍼 클래스에 변경이 생기면 서브 클래스도 함께 수정해야 할 필요가 종종 있음 생성한 서브클래스는 다른 클래스에는 적용될 수 없음. 만약 UserDao 외에 DAO 클래스가 계속 만들어진다면 거의 동일한 구현코드를 가지는 새로운 서브클래스를 함께 계속 만들어 주어야 함
1.3 DAO의 확장
1.3.1 클래스의 분리 두 개의 별다른 관심 사항 데이터 엑세스 로직을 어떻게 만들 것인가? DB연결을 어떻게 만들 것인가? SQL 쿼리 문 구성? DB에 저장할 정보? DB에서 꺼내올 정보? 어떤 드라이버를 사용할 것인가? 연결 URL? 연결시 사용할 계정 정보?
1.3.1 클래스의 분리 클래스의 완전 분리 두 개의 독립된 클래스로 분리
1.3.1 클래스의 분리 클래스의 완전 분리 두 개의 독립된 클래스로 분리 makeNewConnection() throws ClassNotFoundException, SQLException {
1.3.1 클래스의 분리 클래스의 완전 분리 두 개의 독립된 클래스로 분리 앞 코드의 문제점 근본적인 문제의 원인 UserDao 클래스 코드가 SimpleConnectionMaker라는 특정 클래스에 종속 DB 커넥션 생성 방법을 변경한 새로운 클래스를 사용하기 위해서는 UserDao의 소스도 함께 변경해야 함. 결국 N사와 D사에 UserDao의 소스코드도 함께 제공해야 함. 근본적인 문제의 원인 UserDao에서 DB 커넥션을 가져오기 위한 정보 (클래스 이름, 메소드 이름)를 너무 많이 알고 있기 때문임 따라서 UserDao가 DB 커넥션을 제공하는 클래스에 종속됨 simpleConnectionMaker = new SimpleConnectionMaker(); … Connection c = simpleConnectionMaker.makeNewConnection();
1.3.2 인터페이스의 도입 인터페이스의 역할 추상화 (Abstraction) 두 개의 클래스가 서로 긴밀하게 종속되지 않도록 중간에 추상적인 느슨한 연결고리를 만들어 줄 수 있음 추상화 (Abstraction) 다수의 것들에 대해 공통적인 성격을 뽑아내어 따로 분리해 내는 작업
1.3.2 인터페이스의 도입 인터페이스를 도입한 코드 인터페이스 정의 인터페이스 구현 클래스
1.3.2 인터페이스의 도입 인터페이스를 도입한 코드 인터페이스를 도입한 UserDao 개선
1.3.2 인터페이스의 도입 인터페이스의 도입 여전히 존재하는 문제점 UserDao에 어떠한 구현 클래스를 사용하는지 결정하는 생성자 호출 코드가 여전히 남아 있음 connectionMaker = new DConnectionMaker(); 클래스 사이에 불필요한 의존관계를 가지는 구조
1.3.3 관계설정 책임의 분리 주목해야 할 관심(Concern) 사항 위 관심사를 UserDao에서 분리하는 것이 목표 UserDao에서 ConnectionMaker의 어떠한 특정 구현 클래스를 사용해야 하나? 위 관심사를 UserDao에서 분리하는 것이 목표 그렇다면, 관계 설정 관심사를 담당할 클래스는? UserDao를 사용하는 클라이언트. 즉, Main 함수를 지니고 있는 클래스 UserDao는 이러한 관계 설정에 있어서 수동적이 됨 UserDao가 클라이언트로 부터 관계 설정을 받아 내기 위한 방법 → 생성자 또는 메소드 활용
1.3.3 관계설정 책임의 분리 관심사항의 철저한 분리 UserDao에서 새롭게 구현된 생성자 관계설정 책임이 추가된 DserDAO 클라이언트인 UserDaoTest 클래스와 main() 메소드
1.3.3 관계설정 책임의 분리 클라이언트인 UserDaoTest 클래스의 main() 메소드 역할 UserDao에서 새롭게 구현된 생성자 UserDao와 ConnectionMaker 구현 클래스와의 런타인 (Runtime) 객체 의존관계 설정 이제부터 UserDao는 SQL을 생성하여 자료를 조회하고 변경하는 작업에만 집중할 수 있다. DB Connection 연결 방법이나 전략에는 전혀 고민 없음
1.3.3 관계설정 책임의 분리 관계설정 책임을 담당한 UserDaoTest 클래스가 추가된 구조 UserDao 생성자는 ConnectionMaker 인터페이스 타입으로 객체를 전달받기 때문에 ConnectionMaker를 구현한 어떠한 클래스의 객체라도 전달 받을 수 있다. 하지만, UserDao 객체는 전달받은 ConnectionMaker 객체가 어떠한 객체인지 전혀 알지도, 알 필요도 없다.
1.3.4 원칙과 패턴 개방 폐쇄 원칙 (OCP, Open-Closed Principle) 클래스나 모듈은 확장에는 열려있어야 하고 변경에는 닫혀있어야 한다. UserDao에 영향을 주지 않고 인터페이스를 사용하여 DB 연결 방법 기능 확장에는 열려있다. 자신의 핵심 기능을 구현한 코드는 그러한 변화에 전혀 영향을 주지 않는다.
1.3.4 원칙과 패턴 높은 응집도 (High Coherence) 낮은 결합도 (Low Coupling) 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있어야 한다. ConnectionMaker는 자체의 응집력을 유지하면서 확장되고 발전할 수 있다. 이러한 발전이 UserDao등의 다른 클래스에 전혀 영향을 주지 않는다. 낮은 결합도 (Low Coupling) 책임과 관심사가 서로 다른 객체 또는 모듈끼리는 느슨하게 연결된 형태 (서로간의 독립성)를 유지해야 한다. 결합도 (Coupling) 하나의 객체에 변경이 일어날 때 관계를 맺고 있는 다른 객체에 변화를 요구하는 정도 UserDao와 ConnectionMaker간의 관계는 매우 느슨하게 연결되어 있다.
1.3.4 원칙과 패턴 전략 패턴 (Strategy Pattern) 클래스를 구현할 때 필요에 따라 변경이 필요한 모듈은 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 클래스를 필요에 따라서 변경하여 (전략을 변경하여) 사용하는 디자인 패턴 어떠한 모듈 (UserDao)를 사용하는 클라이언트(UserDaoTest)는 그 모듈이 사용할 전략을 그 모듈의 생성자/메소드를 통해 제공 이 예제에서 전략은? ConnectionMaker를 구현한 클래스, 예를 들어 DConnectionMaker