ITEM 64
적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라.
객체의 실제 클래스를 사용해야 할 상황은 '오직' 생성자로 생성할 때뿐이다.
좋은 예
List<String> stringList = new ArrayList<>();
나쁜 예
ArrayList<String> stringList = new ArrayList<>();
상위 인터페이스가 있음에도 불구하고 구체타입 즉 구현클래스 타입으로 선언하게 된다면 해당 타입을 사용하고 있는 클래스 내부에는 구현클래스에 대한 강한 의존관계가 생긴다.
즉 구현클래스타입을 가지고있는 클래스는 구현클래스만을 위한 전용클래스로 전락하기 좋다.
물론 인터페이스 타입으로 선언해도 의존관계는 생긴다.
하지만 인터페이스 타입을 사용하게 되면 인터페이스의 하위타입의 구현클래스로 얼마든지 교체하여 사용할 수 있다.
단 구현 클래스가 인터페이스의 일반 규악 이외의 특별한 기능을 제공하거나 주변코드가 이 기능에 기대어 동작한다면 새로운 클래스도 반드시 같은 기능을 제공해야 한다.
- 예시 -
DEV 환경 -> 메모리 DB를 사용한다.
PRODUCT 환경 -> RDB(Mysql) 를 사용한다.
DB 의 기능은 save , get 만 있다고 가정한다.
Interface Repository
public interface Repository<T> {
T save(T t);
Optional<T> get(Long id);
}
Memory DB Impl
public class MemoryMemberRepo implements Repository<Member> {
private static final HashMap<Long ,Member> memoryDB = new HashMap<>();
private static Long key = 1L;
@Override
public Member save(Member member) {
memoryDB.put(key,member);
key++;
return member;
}
@Override
public Optional<Member> get(Long id) {
if(memoryDB.containsKey(key)){
return Optional.of(memoryDB.get(key));
}
return Optional.empty();
}
}
RDB Impl
public class RDBMemberRepo implements Repository<Member> {
//TODO DB 커넥션 연결함.
@Override
public Member save(Member member) {
//TODO : DB에 저장 했음
}
@Override
public Optional<Member> get(Long id) {
//TODO : DB 검색해서 가져옴
}
}
위와 같은 가정의 환경에서 만약 상위 인터페이스가 있음에도 불구하고 Service 에서 구현 클래스의 타입을 사용한다면
Service 는 MemoryDB, RDB 둘 중 하나만의 기능에만 의존하게 된다.
public class MemberService {
private MemoryMemberRepo repo = new MemoryMemberRepo(); // MemoryDB을 위한 Serivce 가 되었다.
public Member save(Member member){
return repo.save(member);
}
public Member getMember(Long id){
Optional<Member> member = repo.get(id);
return member.orElseThrow(IllegalArgumentException::new);
}
}
하지만 인터페이스를 참조하고 있다면?
public class MemberService {
private final Repository<Member> repo;
public MemberService(Repository<Member> repo) {
this.repo = repo;
}
// 기능 생략
}
외부에서 MemberService 를 생성할때 어떠한 구현체를 주입하냐에 따라 해당 MembeService 는 RDB를 사용할지 Memory DB 를 사용할지 결정하게 된다.
인터페이스 타입으로 사용하는 습관을 길러두면 프로그램이 훨씬 유연해질것이다.