-
Notifications
You must be signed in to change notification settings - Fork 0
3.3 JDBC 전략 패턴의 최적화 #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
leevigong
wants to merge
1
commit into
main
Choose a base branch
from
leevigong
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| # 3.3 JDBC 전략 패턴의 최적화 | ||
|
|
||
| > 3.2에서 전략 패턴을 적용하여 변하지 않는 부분(컨텍스트)과 변하는 부분(전략)을 분리했다. | ||
| > | ||
| > `deleteAll()` 메소드에서 변하지 않는 JDBC 작업 흐름은 `jdbcContextWithStatementStrategy()` 메소드로, 변하는 PreparedStatement 생성 로직은 `StatementStrategy` 인터페이스를 구현한 `DeleteAllStatement` 클래스로 분리했다. | ||
|
|
||
| ## 3.3.1 전략 클래스의 추가 정보 | ||
|
|
||
| `deleteAll()`과 달리 `add()` 메소드에는 user라는 **부가적인 정보**가 필요하다. 클라이언트가 `AddStatement`에 user 정보를 전달해줘야 하므로, 생성자를 통해 User를 제공받도록 만든다. | ||
|
|
||
| ### AddStatement 클래스 | ||
|
|
||
| ```java | ||
| public class AddStatement implements StatementStrategy { | ||
| User user; | ||
|
|
||
| public AddStatement(User user) { | ||
| this.user = user; | ||
| } | ||
|
|
||
| public PreparedStatement makePreparedStatement(Connection c) throws SQLException { | ||
| 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()); | ||
|
|
||
| return ps; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### add() 메소드 수정 | ||
|
|
||
| 이제 `add()` 메소드에서 `AddStatement`를 생성할 때 User 정보를 전달한다. | ||
|
|
||
| ```java | ||
| public void add(User user) throws SQLException { | ||
| StatementStrategy st = new AddStatement(user); | ||
| jdbcContextWithStatementStrategy(st); | ||
| } | ||
| ``` | ||
|
|
||
| 이렇게 `deleteAll()`과 `add()` 두 군데에 전략 패턴이 적용되었다. 변하지 않는 JDBC 작업 흐름은 컨텍스트 메소드에서 공유하고, 바뀌는 부분(PreparedStatement를 만드는 전략)만 각각의 전략 클래스에서 담당한다. | ||
|
|
||
| ## 3.3.2 전략과 클라이언트의 동거 | ||
|
|
||
| 현재 구조에도 두 가지 문제점이 있다. | ||
|
|
||
| 1. **DAO 메소드마다 새로운 StatementStrategy 구현 클래스를 만들어야 한다.** 기능이 늘어날 때마다 클래스 파일이 계속 증가한다. | ||
| 2. **StatementStrategy에 전달할 User와 같은 부가적인 정보가 있는 경우, 이를 위해 오브젝트를 전달받는 생성자와 이를 저장해둘 인스턴스 변수를 번거롭게 만들어야 한다.** | ||
|
|
||
| ### 해결법 1. 로컬 클래스 | ||
|
|
||
| 전략 클래스를 매번 독립된 파일로 만들지 않고, UserDao의 메소드 안에 **로컬 클래스**로 정의할 수 있다. `AddStatement`는 `add()` 메소드에서만 사용되므로, 메소드 레벨에 정의해도 문제없다. | ||
|
|
||
| ```java | ||
| public void add(User user) throws SQLException { | ||
| class AddStatement implements StatementStrategy { | ||
| User user; | ||
|
|
||
| public AddStatement(User user) { | ||
| this.user = user; | ||
| } | ||
|
|
||
| public PreparedStatement makePreparedStatement(Connection c) throws SQLException { | ||
| 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()); | ||
|
|
||
| return ps; | ||
| } | ||
| } | ||
|
|
||
| StatementStrategy st = new AddStatement(user); | ||
| jdbcContextWithStatementStrategy(st); | ||
| } | ||
| ``` | ||
|
|
||
| 로컬 클래스의 장점은 클래스 파일이 줄어들 뿐 아니라, 메소드 안에서 정의하기 때문에 **자신이 선언된 메소드의 로컬 변수에 직접 접근**할 수 있다는 것이다. `add()` 메소드의 파라미터인 `user` 변수에 직접 접근할 수 있으므로, 생성자를 통해 User를 전달해줄 필요가 없다. 다만 내부 클래스에서 외부의 변수를 사용할 때는 `final`로 선언해줘야 한다. | ||
|
|
||
| ```java | ||
| public void add(final User user) throws SQLException { | ||
| class AddStatement implements StatementStrategy { | ||
| public PreparedStatement makePreparedStatement(Connection c) throws SQLException { | ||
| 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()); | ||
|
|
||
| return ps; | ||
| } | ||
| } | ||
|
|
||
| StatementStrategy st = new AddStatement(); | ||
| jdbcContextWithStatementStrategy(st); | ||
| } | ||
| ``` | ||
|
|
||
| > [!NOTE] | ||
| > **로컬 클래스**는 선언된 메소드 안에서만 사용할 수 있다. 클래스가 내부에 정의되어 있어 코드의 응집도가 높아지고, 해당 메소드의 로컬 변수에 직접 접근할 수 있어 생성자를 통한 데이터 전달이 필요 없어진다. | ||
|
|
||
| ### 해결법 2. 익명 내부 클래스 | ||
|
|
||
| `AddStatement`는 `add()` 메소드에서만 사용할 목적으로 만들어졌다. 좀 더 간결하게 클래스 이름도 제거할 수 있다. | ||
|
|
||
| > 익명 내부 클래스는 선언과 동시에 오브젝트를 생성한다. 이름이 없기 때문에 클래스 자신의 타입을 가질 수 없고, 구현한 인터페이스 타입의 변수에만 저장할 수 있다. | ||
|
|
||
| **add() 메소드의 익명 내부 클래스 적용:** | ||
|
|
||
| ```java | ||
| public void add(final User user) throws SQLException { | ||
| jdbcContextWithStatementStrategy( | ||
| new StatementStrategy() { | ||
| public PreparedStatement makePreparedStatement(Connection c) throws SQLException { | ||
| 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()); | ||
|
|
||
| return ps; | ||
| } | ||
| } | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| **deleteAll() 메소드의 익명 내부 클래스 적용:** | ||
|
|
||
| ```java | ||
| public void deleteAll() throws SQLException { | ||
| jdbcContextWithStatementStrategy( | ||
| new StatementStrategy() { | ||
| public PreparedStatement makePreparedStatement(Connection c) throws SQLException { | ||
| return c.prepareStatement("delete from users"); | ||
| } | ||
| } | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| `DeleteAllStatement`와 `AddStatement` 두 개의 클래스를 별도로 만들 필요 없이, 각 메소드 안에서 익명 내부 클래스로 전략을 직접 구현하여 사용한다. 코드가 간결해지고, 메소드의 로컬 변수를 직접 사용할 수 있어 별도의 생성자도 필요 없다. | ||
|
|
||
| ### 전략 패턴 최적화 과정 정리 | ||
|
|
||
| - 독립된 전략 클래스 (DeleteAllStatement, AddStatement) | ||
| ↓ 클래스 파일이 많아지는 문제 | ||
| - 로컬 클래스 (메소드 안에 클래스 정의) | ||
| ↓ 로컬 변수 직접 접근 가능, 생성자 불필요 | ||
| - 익명 내부 클래스 (이름 없는 클래스, 선언과 동시에 생성) | ||
| ↓ 가장 간결한 형태 | ||
|
|
||
|
|
||
| > 이 과정에서 **변하지 않는 것**(JDBC 작업 흐름, try/catch/finally)과 **변하는 것**(PreparedStatement 생성)의 분리라는 원칙은 계속 유지된다. 달라지는 것은 전략을 정의하고 전달하는 방식이 점점 간결해진다는 것이다. 이 구조는 이후 **템플릿/콜백 패턴**의 기초가 된다. | ||
|
|
||
| ## 질문 | ||
| - 로컬 클래스에서 외부 변수를 참조할 때 `final`로 선언해야 하는 이유는 무엇인가? | ||
| - 익명 내부 클래스 방식과 독립된 전략 클래스 방식은 각각 어떤 경우에 적합한가? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q. 익명 내부 클래스 방식과 독립된 전략 클래스 방식은 각각 어떤 경우에 적합한가?
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q. 로컬 클래스에서 외부 변수를 참조할 때
final로 선언해야 하는 이유는 무엇인가?final선언을 명시적으로 해줘야 했으나, Java 8 부터는 선언하지 않아도 되지만 사실상final인 값이어야만 합니다.