개방 폐쇄 원칙
이전에 알아본 내용이지만 다시 간단하게 알아보자면
변경에 개방적이어야 하는 코드들은 수정과 확장에 열려있어야 하고
변경되지 말아야 하는 코드들은 닫혀있어야 하는 원칙이다.
템플릿
개방 폐쇄 원칙을 적용하여 효과적으로 활용할 수 있도록 하는 방법으로
바뀌는 성질이 다른 코드 중 변경이 거의 없고 일정한 패턴으로 유지되는 부분을
자유롭게 변경되는 부분으로부터 독립시키는 방법이다.
즉, 이전에 학습했던 객체지향적인 리팩토링처럼
변하지 않고 많은 곳에서 중복되는 코드와 자주 변경되는 코드를 분리하는 작업이다.
템플릿 적용하기
1. 변하는 성격이 다른 것 찾기
데이터베이스를 조작하는 코드에서 예시를 든다면
조작을 위한 SQL문과 전체적인 데이터베이스 로직을 처리하는 반복적인 코드가 있다.
여기서 SQL문은 상황에 따라 달라지는 자주 변경되는 코드이고
데이터베이스의 전체적인 로직을 처리하는 코드는 공통적으로 사용되는 코드로
SQL문과 관련된 코드가 변하는 성격이 다른 것이라고 볼 수 있다.
2. 코드에서 분리하기
Connection c = null;
PreparedStatement ps = null;
try {
c = dataSource.getConnection();
ps = c.prepareStatement("delete from users"); // 변하는 부분
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) { try { ps.close(); } catch (SQLException e) {} }
if (c != null) { try {c.close(); } catch (SQLException e) {} }
}
위의 코드에서 변하는 부분인 SQL문을 분리하는 방법 중에서는
여러가지 방법이 있지만 기존 리팩토링 과정에서 써봤던
메서드 추출이라는 방법이 있다.
try {
c = dataSource.getConnection();
ps = makeStatement(c); // 변하는 부분
ps.executeUpdate();
}
private PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps;
ps = c.prepareStatement("delete from users");
return ps
}
자주 바뀌는 SQL 부분의 코드를 메서드로 분리시켰지만
이로 인해 별다른 이득을 보는 것은 없고 그냥 코드를 분리하기만 했다.
실질적으로 재사용되는 코드는 SQL 코드가 아니라
데이터베이스 작업을 처리하는 코드이기 때문이다.
3. 템플릿 메서드 패턴 적용하기
템플릿 메서드 패턴은 상속을 통해 기능을 확장해서 사용한다.
변하지 않는 부분은 슈퍼 클래스에, 변하는 부분은 추상 메서드로 정의하여
서브 클래스에서 자신에 맞게 오버라이드하여 정의하여 쓰게 한다.
private PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps;
ps = c.prepareStatement("delete from users");
return ps;
}
위의 메서드는 변하는 부분에 속하는 코드이니 해당 메서드를 추상 메서드로 선언한다.
abstract public class UserDao {
abstract protected PreparedStatement makeStatement(Connection c) throws SQLException;
}
이제 필요에 따라 해당 추상 클래스를 상속 받는 서브 클래스에
추상 메서드를 구현하여 자유롭게 사용할 수 있다.
public class UserDaoDeleteAll extends UserDao {
protected PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
}
하지만 DAO 로직마다 상속을 통해 새로운 클래스를 매번 만들어야 하기 때문에 좋은 방법이 아니다.
4. 전략 패턴 적용하기
템플릿 메서드 패턴의 장점을 가지면서도 유연하고 확장성이 뛰어난 패턴으로
컨텍스트(변하지 않는 부분) 오브젝트와 전략 오브젝트(변하는 부분)를 아예 둘로 분리하고
이 둘의 오브젝트를 인터페이스를 통해서만 의존하도록 하는 디자인 패턴이다.
즉, 컨텍스트 - 전략 인터페이스 - 전략(전략 인터페이스의 구현 클래스) 구조로 생겼다고 생각할 수 있다.
public interface StatementStrategy {
PreparedStatement makePreparedStatement(Connection c) throws SQLException;
}
위의 코드처럼 전략 인터페이스를 생성하고 이를 구현하는 전략 클래스를 만든다.
public class Delete implements StatementStartegy {
PreparedStatement makePreparedStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
}
만들어진 전략 클래스를 기존의 컨텍스트에서 사용하면 된다.
try {
c = dataSource.getConnection();
StatementStartegy startegy = new Delete();
ps = startegy.makePreparedStatement(c);
ps.executeUpdate();
}
하지만 이는 컨텍스트에 어떤 전략을 적용할지 직접적으로 지정하여
고정된 상태이기 때문에 결코 좋은 방식이 아니다.
5. 클라이언트와 컨텍스트를 분리하기
컨텍스트가 자신이 어떤 전략을 사용할지 모르게 하고
클라이언트가 어떤 전략을 사용할지 정해주어 결합을 낮춰야 하는데
이러한 방식은 1장을 공부할 때 사용했던 DI와 같다.
StatementStartegy startegy = new Delete();
위의 코드는 컨텍스트가 아닌 클라이언트가 가지고 있어야 할 코드이기 때문에
해당 부분을 클라이언트에게 넘겨야 한다.
public void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException {
try {
c = dataSource.getConnection();
ps = startegy.makePreparedStatement(c);
ps.executeUpdate();
}
}
이렇게 코드를 수정하면 컨텍스트가 호출될 때 클라이언트로부터 전략을 전달 받기 때문에
핵심적인 내용만 잘 가지고 있는 코드다.
public void deleteAll() throws SQLException {
StatementStartegy = st = new Delete();
jdbcContextWithStatementStrategy(st);
}
이제 해당 컨텍스트를 호출하는 메서드를 클라이언트라고 볼 수 있는데
클라이언트 메서드에서는 사용할 전략을 선택하여 생성하고
이를 컨텍스트 메서드에 파라미터로 넘겨주면
컨텍스트 메서드는 전달 받은 전략에 맞는 작업을 수행한다.
컨텍스트와 클라이언트를 다른 클래스로 서로 분리하진 않았지만
관심사와 의존관계, 책임을 분리하는 것은 끝났다.
'Back-End > Spring' 카테고리의 다른 글
컨텍스트와 DI (0) | 2023.06.13 |
---|---|
전략 패턴 최적화 하기 (0) | 2023.06.13 |
스프링에 테스트 적용하기 (0) | 2023.06.08 |
JUnit 자세히 알아보기 (0) | 2023.06.08 |
테스트 (0) | 2023.06.07 |