トビーのSpring[1章-1.3章]学習資料公開
35065 ワード
1.1. 超恥ずかしいDAO
- UserDao 클래스는 사용자 정보를 DB에 넣고 관리할 수 있는 기능을 가지고 있다.
- 등록, 수정, 삭제, 조회등 기본적인 CRUD 구조를 가진다.
- JDBC 커넥션을 기반으로 한다.
- User.java
// Domain
public class User {
String id;
String name;
String password;
...
}
- BadUserDao.java
public class BadUserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Class.forName("org.h2.Driver");
Connection c = DriverManager.getConnection(
"jdbc:h2:tcp://localhost:1521/test", "sa", ""
);
PreparedStatement ps = c.prepareStatement(
"inser 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();
}
public void update(User user) throws ClassNotFoundException, SQLException {
...
}
public void delete(User user) throws ClassNotFoundException, SQLException {
...
}
public User get() throws ClassNotFoundException, SQLException {
...
return null;
}
}
- 이 난감한 DAO가 제대로 동작된다는 가정하에, 각 메소드들은 add와 같은 구조로 동작한다고 생각하고 진행하도록 한다.
- 구현한 Dao를 테스트하는 클라이언트
@SpringBootTest
class TobySpringExApplicationTests {
@Test
void main() throws SQLException, ClassNotFoundException {
BadUserDao badUserDao = new BadUserDao();
User user = new User();
user.setId("test-01");
user.setName("wonjune");
user.setPassword("pwd01");
badUserDao.add(user);
}
}
1.1.1. もんだいぶんせき
- 이 DAO를 구성하는 메소드들은 각각 Connection을 가지고 있다.
- 현재는 4개의 메소드만 존재하지만, 미래에 추가적으로 더 생길수도 있다.
- Connection을 변경해야할 일이 있을 경우, 모든 메소드에 Connection 로직을 일괄 변경해줘야 한다.
- 중복코드가 발생한다.
- add 메소드의 경우 관심사는 DB에 User 정보를 INSERT 작업을 하는 것이다. 하지만 단순 INSERT 작업만을 수행하지 않고 DB를 연결하는 등 여러 서로 다른 관심사가 한 메소드 내에서 집중되어 있다.
1.2. 分離DAO
1.2.1. 興味を引き出す
1. DB 커넥션
2. 사용자 정보 바인딩 후, SQL 실행
3. 리소스 반환 (Connection.close())
1.2.2. 冗長コードのメソッド化-再構築
- 가장 먼저 고려해볼 수 있는 것은 각 메소드 내에 중복되어 있는 Connection 부분을 가져와 메소드화 하여 사용하는 것이다.
public class BadUserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
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();
}
public void update(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
}
public void delete(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
}
public User get() throws ClassNotFoundException, SQLException {
Connection c = getConnection();
return null;
}
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("org.h2.Driver");
Connection c = DriverManager.getConnection(
"jdbc:h2:tcp://localhost:1521/test", "sa", ""
);
return c;
}
}
- 이와 같이 메소드로 Connection을 불러올 수 있게끔 해준다면 전보다는 나은 코드가 된다.
改造する内部構造を変更してモーションの再構築
コードの内部設計を改善し、コードの理解を容易にする
変化に効率的に対応
1.2.3. DB接続の作成とは独立
- 위의 과정은 변화에 대응하는 수준
- 해당 DAO의 로직은 건드리지 않고 Connection만 확장성을 고려하여 리팩토링 한다.
- 요구사항
- UserDao의 변경 없이, 두 가지 이상 DB 커넥션 타입을 가진다.
1.2.3.1. 継承によるUserDaoの拡張
- UserDao.java(추상 클래스)
public abstract class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}
- 구현 코드는 제거되고 추상 메소드로 바뀌었다.
- 메소드의 구현은 서브클래스가 담당한다.
- NUserDao.java
public class NUserDao extends UserDao {
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
// NUserDao만의 커넥션 알고리즘
...
return connection;
}
}
- DUserDao.java
public class DUserDao extends UserDao {
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
// DUserDao만의 커넥션 알고리즘
...
return connection;
}
}
- DAO의 핵심 기능인 등록, 수정, 삭제, 조회 기능은 UserDao에서 구현 (DAO 핵심 기능을 관심사로 담당)
- N, D UserDao는 각 각 Connection을 어떤 방식으로 가져갈 것인가에 대해 관심을 가지고 Overriding하여 해당 connection 관심사만 처리
- 이와 같은 설계 방식을 **템플릿 메소드 패턴**이라고 한다.
テンプレートメソッドアレイスーパークラスに基本論理フローを作成します.
すなわち,共通の機能を実現する.
その中のサブクラスでは,それぞれの必要に応じて実現できるように,上書き可能な保護方法や抽象方法を用いることができる.
ファクトリメソッドモード
オブジェクトをサブクラスに部分的に委任するモードを作成します.
1.3. 拡張DAO
抽象クラスを作成し、継承するサブクラスで変更が必要な部分を置き換えることができます.これは、異なる変更の性質を分離して、それらに影響を及ぼさずに必要な時点ごとに独立して変更できるためです.
1.3.1. クラスの分離
- 두 개의 관심사를 본격적으로 독립 시키면서 동시에 손쉽게 확장할 수 있는 방법
public abstract class UserDao {
private SimpleConnectionMaker simpleConnectionMaker;
public UserDao() {
simpleConnectionMaker = new SimpleConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.makeNewConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.makeNewConnection();
return null;
}
}
- 이렇게 된다면 두가지 문제가 생긴다.
1. 만약 connection에 대한 요구사항이 변경되어 openConnection() 메소드를 써야할 경우, DAO에 전체 Connection 변경 필요
2. DB 커넥션을 제공하는 클래스가 어떤 것인지 UserDao가 구체적으로 알고 있어야 한다.
따라서 UserDao는 DB 커넥션을 가져오는 구체적인 방법에 종속되어 버린다.
1.3.2. インタフェースの導入
インタフェースとは、
何かをする機能だけを定義しました!!
-クラスを分割するときに問題を解決する良い方法は、2つのクラス間の緊密な接続を回避するために抽象的なばらばらな接続ループを中間に作成することです.
- ConnectionMaker.java
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException,
SQLException;
}
- 개선한 UserDao.java
public abstract class UserDao {
private ConnectionMaker connectionMaker;
public UserDao() {
connectionMaker = new DConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
...
}
}
- 문제점
- 인터페이스를 통해 추상화를 하여 커넥션을 상황에 따라서 구현 클래스를 바꿔 쓸 수 있게 되었지만, UserDao 내에서 직접 커넥션 구현체를 지정하여 선언하므로 UserDao와 ConnectionMaker가 여전히 높은 결합도를 가지고 있다.
1.3.3. 関係を確立する責任を分離する
- 여전히 UserDao에는 어떤 ConnectionMaker 구현 클래스를 사용할지 결정하는 코드가 있다.
- 이 때문에 인터페이스를 이용한 분리에도 불구하고 여전히 UserDao의 변경 없이는 DB 커넥션 기능의 확장이 자유롭지 못하다.
- 여전히 DB 커넥션에 대한 관심사항이 UserDao에 남아있는 상태이다.
ちゅうかん整理
- 잠깐 정리를 해보자면, UserDao는 물론 독립적으로 실행될 수 있지만, 조금 더 넓은 개념으로 보자면 **어떤 클라이언트에 의해 사용되는 서비스** 역할을 한다고 볼 수 있다.
- 즉, 클라이언트에 의해 호출이 되므로, 클라이언트가 주 제어권자가 되어 UserDao에 어떤 DB 커넥션을 사용할지 결정하도록 해보자.
- UserDao가 외부에서 동적으로 Connection을 부여받을 수 있도록 코드 구조를 개선해보자.
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
...
}
1.3.4. 原則とモデル
ソフトウェア開発の知恵、原則、設計モデル、実践方法
ボブ・マーティンジャー
オープンクローズの原則(OCP、Open Closed Principle)
- 클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.
- 높은 응집도와 낮은 결합도
単一責任の原則
- 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 한다.
リスコフ交換の原則(LSP、Liskov代替の原則)
- 하위 클래스는 반드시 상위 클래스와 대체 가능 해야 한다.
インタフェース分離の原則
- 클라이언트의 세분화된 내용과 같은 세분화된 인터페이스를 만들자.
依存関係逆転の原則
- A: 고수준(High-Level)의 모듈은 저수준(Low-Level)의 모듈에 의존하면 안된다. 둘 다 추상화에 의존해야한다.
- B: 추상은 세부사항에 의존해서는 안된다. 추상에 의존해야 한다.
参考資料すべての開発者が理解しなければならないSOLIDの原則-第1編|Doublem。org
戦略モデル
- 자신의 기능 맥락(context)에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴
参考資料設計モードポリシーモード:Victore
Reference
この問題について(トビーのSpring[1章-1.3章]学習資料公開), 我々は、より多くの情報をここで見つけました https://velog.io/@devsigner9920/토비의-스프링-1장-1.3장-스터디-발표-자료テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol