인터페이스와 다형성으로 확장 가능한 구조 만들기

기 원·2025년 4월 16일

Java를 Spring 처럼!

목록 보기
2/5

Java를 Spring 처럼! - 인터페이스와 다형성으로 확장 가능한 구조 만들기


이 시리즈는...

지난 글에서는 클래스, 객체, 메서드, 생성자 주입, IoC(제어의 역전) 개념까지 알아보았습니다...(알아 봤죠??)
이번에는 스프링 구조에서 당연하게 사용되는 인터페이스왜 필요한지를 알아보겠습니다...(알아 볼꺼죠?)


1. 도입 - 이런 상황, 어떻게 할까?

"UserService에 알림 기능을 넣고 싶은데...
이메일도 있고, 문자도 있고, 슬릭도 있고, 뭐 이것저것 있고...
아! 나중엔 카카오톡을 쓸 수도 있어요!!"

클라이언트가 이렇게 말한다면??

지금 당장 선빵을 갈...기는건 아니고요,
우리는 인터페이스 기반 설계로 이미 준비되어 있으니 걱정 없습니다!

처음에는 이렇게 단순할 수 있습니다.

public class UserService {
    private final EmailService emailService = new EmailService();

    public void process() {
        emailService.send("가입 완료!");
    }
}

하지만 이런 구조는 유연하지 않습니다.
→ 새로운 알림 방식을 도입하려면 이 코드를 직접 수정해야 하니까요!


2. 강결합의 문제점

public class SmsService { ... }
public class SlackService { ... }

위와 같은 서비스가 추가될 때마다 UserService 코드는 조건문으로 점점 복잡해지고…

if (type.equals("email")) {
    emailService.send();
} else if (type.equals("sms")) {
    smsService.send();
} ...

이제 서비스를 확장하는 건 ‘수정의 연속’이 됩니다.


3. 인터페이스 도입 - “형태는 고정, 구현은 다양하게”

이럴 때 등장하는 게 바로 인터페이스입니다.

public interface NotificationService {
    void send(String message);
}

그리고 다양한 구현체를 만들어보죠:

public class EmailNotificationService implements NotificationService {
    public void send(String message) {
        System.out.println("이메일 발송: " + message);
    }
}

public class SmsNotificationService implements NotificationService {
    public void send(String message) {
        System.out.println("SMS 발송: " + message);
    }
}

4. 인터페이스 기반으로 바꿔보자!

public class UserService {
    private final NotificationService notificationService;

    public UserService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    public void process() {
        notificationService.send("가입 완료!");
    }
}

구현체가 무엇이든 관계없습니다.
인터페이스만 맞으면 교체도, 테스트도 자유롭습니다!


5. 다형성 - 다양한 모양으로 동작하는 객체

NotificationService service = new SmsNotificationService();
service.send("어서오세요!");
  • 인터페이스 하나
  • 구현체 여러 개
  • 사용할 땐 하나의 타입(인터페이스)만 바라봄

6. OCP 원칙 - 수정에는 닫고, 확장엔 열려라

OCP (Open-Closed Principle)
기존 코드는 수정하지 않고 (Closed)
새로운 기능은 확장 (Open) 할 수 있도록!

UserService는 절대 바꾸지 않고도
SlackNotificationService를 만들어 넣기만 하면 OK!


7. 테스트도 쉬워진다!

public class FakeNotificationService implements NotificationService {
    public void send(String message) {
        System.out.println("[테스트용] 알림: " + message);
    }
}

→ 테스트에서는 진짜 이메일/문자 보내지 않고
가짜 구현체(Fake) 로 대체 가능 = 테스트 안정성 증가!


8. 스프링에서는 어떻게 쓸까?

@Service
public class EmailNotificationService implements NotificationService { ... }

@RequiredArgsConstructor
@RestController
public class UserController {

    private final NotificationService notificationService;

    @PostMapping("/notify")
    public void send() {
        notificationService.send("API 호출됨");
    }
}
  • NotificationService를 구현한 클래스가 자동으로 주입됨
  • 인터페이스만 있으면, 구현은 스프링이 관리!

9. 비교 정리

항목직접 의존 방식인터페이스 기반
구현체 교체코드 수정 필요객체만 바꾸면 됨
테스트실제 서비스 사용Fake/MOCK 가능
유지보수결합도 높음유연하고 확장 쉬움
스프링 호환성낮음DI 구조와 찰떡

10. 오늘의 정리

개념설명
인터페이스기능 명세서, 계약서
구현체실제 동작을 담당하는 클래스
다형성같은 인터페이스로 다양한 행동
OCP기존 코드 수정 없이 기능 확장
테스트 이점가짜 구현체로도 동작 가능
스프링과 연결인터페이스 기반 DI 구조가 핵심
profile
노력하고 있다니까요?

1개의 댓글

comment-user-thumbnail
2025년 4월 16일

와.. 도움 많이 되었습니다 감사합니다

답글 달기