Skip to content

Latest commit

 

History

History
157 lines (90 loc) · 3.61 KB

64_객체는_인터페이스를_사용해_참조하라_김재준.md

File metadata and controls

157 lines (90 loc) · 3.61 KB

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 를 사용할지 결정하게 된다.

결론

인터페이스 타입으로 사용하는 습관을 길러두면 프로그램이 훨씬 유연해질것이다.