- 유틸리티 클래스 또는 상수를 저장하는 용도의 클래스는 private 생성자를 선언하여 인스턴스화되거나 상속되는 것을 방지하자.
public final class MessageConstants {
public static final String ADD_BOOK_START = "도서 등록 메뉴로 넘어갑니다.";
public static final String ADD_BOOK_END = "도서 등록이 완료되었습니다.";
private MessageConstants() {
}
}
- 공통되는 행동이 있을 경우, 함수형 인터페이스를 활용해보자. (Runnble, Supplier...)
- 상태 패턴
- 객체의 상태에 따라 행동을 유연하게 변경할 필요가 있을 때 효과적으로 사용될 수 있는 패턴이다.
- 예시 : Book 이라는 객체는 '대여 가능, 대여중, 분실됨, 도서 정리중'과 같은 상태를 가진다.
- '대여 가능 상태일때만 대여 처리하고, 대여중/정리중/분실됨 상태일 때는 예외를 발생시키도록 한다' 라는 요구 사항이 있을 경우, 아래 코드처럼 Book의 상태에 따라 각각 다른 로직이 수행되도록 구현할 수 있다.
- 하지만 상태의 가짓수가 늘어난다면? (도서 출고중, 도서 배송중, 보존서고에 보관중.. 등등)
- else if가 점점 많아지고, 한눈에 로직을 파악하기가 어려워진다
@Override
public void borrowBook(BorrowBookRequestDto request) {
Book book = repository.findById(request.getId()).orElseThrow(BookNotFoundException::new);
updateBookToAvailableAfterOrganizing(book);
if (book.isAvailable()) {
book.borrow();
repository.save(book);
} else if (book.isBorrowed()) {
throw new BookAlreadyBorrowedException();
} else if (book.isLost()) {
throw new BookLostException();
} else if (book.isOrganizing()) {
throw new BookUnderOrganizingException();
}
}
BookState 에 상태 패턴을 적용한다면?
public interface State {
BookStateType getType();
void borrow();
void returned();
void lost();
State organize();
}
public class AvailableState implements State {
@Override
public BookStateType getType() {
return BookStateType.AVAILABLE;
}
@Override
public void borrow() {
// success
}
@Override
public void returned() {
throw new BookException(ErrorCode.BOOK_ALREADY_AVAILABLE);
}
@Override
public void lost() {
// success
}
@Override
public State organize() {
return this;
}
}
public class Book {
// fields...
public void borrow() {
state.borrow();
state = new BorrowedState();
}
}
public class LibraryManagerServiceImpl {
@Override
public void borrowBook(BorrowBookRequestDto request) {
Book book = bookRepository.findById(request.getId()).orElseThrow(() -> new BookException(BOOK_NOT_FOUND));
book.borrow();
bookRepository.save(book);
}
}
도서 대여를 할 때, 대여라는 행동을 Book 객체 내부에서 처리하도록 위임한다.
각 상태는 독립적으로 변경되므로, 새로운 상태를 추가하거나 기존 상태를 변경할 때 다른 상태에 영향을 미치지 않는다.
+ 가장 흥미로웠던건 특정 행위에 종속된 필드(ex.returnedAt) 를 Book 내부가 아닌 OrganizingState 클래스 내부로 캡슐화해줄 수 있다는 점이다.
- 장점 : 새로운 상태를 추가하는 것이 용이하며, 기존 코드의 변경없이 새로운 상태 클래스를 추가할 수 있다. 상태별로 로직이 분리되어 있어 응집도가 높고 결합도가 낮다.
- 단점 : 상태가 많아질수록 클래스도 많아져 관리가 복잡해질 수 있다.
- dto 클래스는 Java 17의 record 를 활용하여 간결하게 작성할 수 있다.
- 싱글톤 패턴
이번 미션에서는 별도의 데이터베이스 연결없이 메모리 기반로 DB를 사용했다. 책을 추가할 때마다 고유한 아이디 값을 부여하기 위해, 아이디 값을 생성하는 IdGenerator를 만들었다. AtomicLong과 syncronized 키워드를 활용하여 동시에 책을 등록해도 중복된 아이디가 부여되지 않도록 작성했다. Lazy Initialization 방식을 사용했는데, Holder 방식도 있다고 한다.
public class IdGenerator {
private static volatile IdGenerator instance;
private final AtomicLong id = new AtomicLong(0L);
private IdGenerator() {
}
public static IdGenerator getInstance() {
if (instance == null) {
synchronized (IdGenerator.class) {
if (instance == null) {
instance = new IdGenerator();
}
}
}
return instance;
}
public void initialize(Long initId) {
id.set(initId);
}
public Long generateId() {
return id.incrementAndGet();
}
}
스프링 같은 프레임워크 없이 처음 만들어본 순수 자바 어플리케이션이어서 처음에는 많이 헤맸던 것 같다.
콘솔 입출력 구현부터 난관이었음..
반복되는 로직을 어떻게 깔끔하게 정리할지 많이 고민해볼 수 있었고,
특히 구조적인 문제점을 해결하는 데에 디자인 패턴을 활용할 수 있다는 게 흥미로웠다.
'데브코스' 카테고리의 다른 글
빙터파크 팀 프로젝트 회고 (0) | 2024.01.18 |
---|---|
Spring Security 멀티 필터체인으로 철벽 보안 구성하기 - multiple SecurityFilterChain, multiple Spring Security Configurations (0) | 2024.01.17 |
[데브코스] 1월 2주차 회고 - 팀 프로젝트 마지막 스프린트 (0) | 2024.01.17 |
[데브코스] 1월 1주차 회고 (1) | 2024.01.07 |
HTTP 완벽 가이드 스터디 회고 (1) | 2023.12.28 |