객체지향 프로그래밍을 설계할 때 아래와 같이 5가지 원칙을 지켜야한다라는 말을 들어본 적 있을 것이다.
- 단일 책임 원칙(SRP)
- 개방 폐쇄 원칙(OCP)
- 리스코프 치환 원칙(LSP)
- 인터페이스 분리 원칙(ISP)
- 의존 역전 원칙(DIP)
CS를 공부하면서 배웠을 SOLID 원칙인데 솔직히 이론적인 설명만 읽어서는 온전히 이해가 되지 않는 원칙들이 몇 개 있었다. 이번에 김영한님의 spring 강의를 들으면서 개방 폐쇄 원칙을 잘 지킨 사례를 발견하여 블로그에 기록해두려 한다.
개방 폐쇄 원칙(OCP)
이론적인 설명은 다음과 같다.
코드의 수정에는 폐쇄되어 있지만 확장에는 개방되어 있어야 한다. 즉, 유지 보수할 때 코드의 수정은 최대한 지양하면서 확장은 용이하게 할 수 있도록 설계해야 한다.
언뜻 이해되는 듯 하면서도 자세히 들여다보면 확장은 쉬우면서 수정은 안 되게 한다는 것이 어떻게 할 수 있을까 잘 이해되진 않았다. 하지만 이번 예시로 이 원칙에 대해 확실히 이해할 수 있었다.
1. 추상적인 인터페이스를 설계하고 이를 구현한 구현체로 repository 구현
우선, 이번 강의에서 설계한 repository의 클래스와 인터페이스이다.
사진과 같이 인터페이스로 추상화를 해두고 이를 JdbcMemberRepository와 MemoryMemberRepository 각각 상속받아 구현하였다. 각 코드의 일부를 보자.
2. 서버 메모리에 저장하는 MemoryMemberRepository와 DB에 저장하는 JdbcMemberRepository
// MemoryMemberRepository
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
.
.
.
}
// JdbcMemberRepository
public class JdbcMemberRepository implements MemberRepository {
private final DataSource dataSource;
public JdbcMemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Member save(Member member) {
String sql = "insert into member(name) values(?)";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, member.getName());
pstmt.executeUpdate();
rs = pstmt.getGeneratedKeys();
if (rs.next()) {
member.setId(rs.getLong(1));
} else {
throw new SQLException("id 조회 실패");
}
return member;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
.
.
.
}
전체 코드는 너무 길어서 두 repository에서 회원 가입하는 부분만 추출해왔다. 이것저것 많은 내용이 적혀있지만 두 repository의 차이를 설명하면 MemoryMemberRepository는 HashMap을 이용해서 데이터를 저장하고 JdbcMemberRepository는 DB에 저장하는 형태이다.
3. Spring의 의존성을 관리하는 SpringConfig로 확장은 용이하게 수정은 지양
// SpringConfig
@Configuration
public class SpringConfig {
private final DataSource dataSource;
@Autowired
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
return new JdbcMemberRepository(dataSource);
}
}
SpringConfig라는 스프링의 의존성을 관리하는 파일이 있다. 여러분들이 Spring을 이용해 BE 설계를 한 경험이 있다면 Controller -> Service -> Repository 혹은 DAO로 이어지는 구조를 구현해봤을 가능성이 높다.
이 SpringConfig의 코드를 보면 memberService는 memberRepository와 의존관계가 되어있음을 확인할 수 있고 memberRepository의 경우 아까 위에서 확인한 MemoryMemberRepository를 주석처리하고 JdbcMemberRepository와 연결한 것을 확인할 수 있다.
바로 이러한 구조가 개방 폐쇄 원칙을 잘 지킨 사례이다.
데이터는 당연히 서버 메모리가 아닌 DB에 저장해야 한다. 즉, MemoryMemberRepository는 굳이 필요하지 않은 repository였다. 하지만 개발자가 DB에 연결하기 전이나 간단한 테스트를 위해 서버 메모리에서 기본적인 회원가입 과정을 구현하고 테스트 하기위해 MemoryMemberRepository를 구현했고 이후 DB에 데이터를 저장하기 위해 JdbcMemberRepository를 구현했다.
이때, 기존에 작성한 MemoryMemberRepository를 수정하지 않으면서도 확장에 용이하게 JdbcMemberRepository를 구현한 후 의존 관계를 MemoryMemberRepository에서 JdbcMemberRepository로 변경하여 확장은 용이하게 수정은 최대한 지양하는 개방 폐쇄 원칙을 잘 지킨 설계를 하였다.
참고자료
스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
'CS > 디자인 패턴과 프로그래밍 패러다임' 카테고리의 다른 글
SOLID 원칙 - 의존 역전 원칙 (0) | 2024.12.30 |
---|---|
객체지향 프로그래밍 (0) | 2024.06.24 |
프로그래밍 패러다임 (0) | 2024.06.24 |
싱글톤 패턴(Singleton Pattern) (0) | 2024.06.21 |
디자인 패턴 (0) | 2024.06.19 |