(Effective Java) Item26 퍼펙트 어택. 일반 저장소

제26조. 원시 유형을 사용하지 마십시오.

일반 다오 만들기

Generic을 사용하여 중복 코드를 제거하는 이점을 보여주기 위해 강의 중에 간단한 저장소를 만들었습니다.

public interface Entity {
    Long getId();
}
public class Account implements Entity {
    private Long id;
    private String username;

    public Account(Long id, String username) {
        this.id = id;
        this.username = username;
    }

    @Override
    public Long getId() {
        return this.id;
    }

    public String getUsername() {
        return username;
    }
}
public class Message implements Entity{
    private Long id;
    private String body;

    @Override
    public Long getId() {
        return id;
    }

    public String getBody() {
        return body;
    }
}

각 개체는 Entity라는 인터페이스를 구현합니다.

public class AccountRepository {
    private Set<Account> accounts;

    public AccountRepository() {
        this.accounts = new HashSet<>();
    }

    public Optional<Account> findById(Long id) {
        return accounts.stream().filter(a -> a.getId().equals(id)).findAny();
    }

    public void add(Account account) {
        this.accounts.add(account);
    }
}
public class MessageRepository {
    Set<Message> messages;

    public MessageRepository() {
        this.messages = new HashSet<>();
    }

    public Optional<Message> findById(Long id) {
        return messages.stream().filter(a -> a.getId().equals(id)).findAny();
    }

    public void add(Message message) {
        this.messages.add(message);
    }
}

개체마다 고유한 리포지토리가 있는 경우 변경된 개체를 제외하고는 리포지토리 코드가 완전히 동일함을 알 수 있습니다.

이 중복 코드는 Generic으로 제거할 수 있습니다.

public class GenericRepository<E extends Entity> {

    private Set<E> entities;

    public GenericRepository() {
        this.entities = new HashSet<>();
    }

    public Optional<E> findById(Long id) {
        return entities.stream().filter(a -> a.getId().equals(id)).findAny();
    }

    public void add(E e) {
        entities.add(e);
    }


}

E의 타입을 Entity에서 상속받은 객체로 제한하고 그렇게 설계함으로써 중복 코드를 제거할 수 있습니다.

public class AccountRepository  extends GenericRepository<Account>{
//    private Set<Account> accounts;
//
//    public AccountRepository() {
//        this.accounts = new HashSet<>();
//    }
//
//    public Optional<Account> findById(Long id) {
//        return accounts.stream().filter(a -> a.getId().equals(id)).findAny();
//    }
//
//    public void add(Account account) {
//        this.accounts.add(account);
//    }
}

그런 다음 AccountRepository에서 모든 코드를 제거하고 GenericRepository를 상속하기만 하면 됩니다!

@Test
void findById() {
    AccountRepository accountRepository = new AccountRepository();
    Account account = new Account(1L, "whiteShip");
    accountRepository.add(account);

    Optional<Account> byId = accountRepository.findById(1L);
    Assertions.assertTrue(byId.isPresent());
}

제네릭을 사용하기 전과 제네릭 리포지토리를 상속한 후에 이 테스트를 실행하면 결과가 동일한 것을 확인할 수 있습니다.

정리하다

대학 졸업 후 항상 제네릭 서체를 만들어 사용하는 경우는 드문 것 같은데 이번 강의를 통해 사용법을 배웠습니다.

제 프로젝트에 이와 비슷한 코드가 많은데 바로 적용하고 싶어요!!

제네릭을 사용한다는 것은 앞으로 다른 엔티티가 추가된다고 해서 같은 코드를 다시 작성할 필요가 없고, GenericRepository<>만 확장하면 되기 때문에 훨씬 편리해 보입니다 ㅎㅎ