자바 (Java) 추상 메서드 & 인터페이스, 왜 쓰는 걸까?

scminy0527·2026년 2월 25일
post-thumbnail

자바에서 추상 클래스와 인터페이스를 배우는 중에 다음과 같이 생각했었다.

처음에는 단순히

  • “강제로 메서드를 만들게 하는 장치”
  • “틀을 만들어놓는 개념”

정도로 이해했지만,
조금 더 고민해보니 이건 단순 문법이 아니라 설계 철학에 가까운 개념이었다.


1. 추상 메서드란?

abstract class Payment {
    abstract void pay();
}

추상 메서드는 몸체가 없는 메서드다.

class CardPayment extends Payment {
    @Override
    void pay() {
        System.out.println("카드 결제");
    }
}

만약 pay()를 구현하지 않으면?

👉 컴파일 에러 발생

즉,

“이 메서드는 반드시 구현해야 한다”

를 개발자에게 강제하는 장치다.


🔥 추상 메서드의 핵심

  • 실행 중 에러가 아니라 컴파일 단계에서 막는다
  • 미완성 상태의 클래스를 방지한다
  • “있어야 할 기능”을 강제한다

2. 인터페이스란?

인터페이스는 “기능의 규약(계약)”이다.

interface Payment {
    void pay();
    void cancel();
    void refund();
}

구현 클래스는 implements로 연결한다.

class CardPayment implements Payment {
    @Override
    public void pay() { }

    @Override
    public void cancel() { }

    @Override
    public void refund() { }
}

인터페이스의 모든 메서드를 구현하지 않으면 역시 컴파일 에러가 발생한다.


📌 왜 매서드 이름을 통일하는 게 중요할까?

만약 특정 결제 기능에 대한 매서드 이름이 다음처럼 제각각이라면:

  • pay()
  • goToPay()
  • doPayment()

사용하는 쪽에서는 혼란이 생긴다.

하지만 인터페이스로 pay()를 강제하면

모든 결제 객체는 같은 이름의 메서드를 가진다

이건 유지보수 관점에서 매우 큰 장점이다.

이게 인터페이스를 이용할 때 얻는 강점이다.


3. 어댑터(Adapter)의 등장

문제는 이런 경우다:

  • 인터페이스에는 3개의 메서드가 있음
  • 어떤 클래스는 1개만 필요함

그래서 등장한 것이 어댑터 패턴이다.

abstract class PaymentAdapter implements Payment {

    @Override
    public void pay() {}

    @Override
    public void cancel() {}

    @Override
    public void refund() {}
}

이제 필요한 메서드만 오버라이딩하면 된다.

class CardPayment extends PaymentAdapter {

    @Override
    public void pay() {
        System.out.println("카드 결제");
    }
}

⚠️ 그런데 여기서 중요한 점

어댑터에 “빈 메서드”를 두면
실수로 호출해도 아무 일도 안 일어날 수 있다.

이건 버그를 숨길 위험이 있다.

그래서 더 안전한 방법은:

@Override
public void refund() {
    throw new UnsupportedOperationException("지원하지 않음");
}

👉 호출되면 즉시 실패(Fail Fast)


4. protected의 의미

인터페이스를 사용 시 좀 더 응용을 해서 템플릿 구조를 쓰면 이런 형태가 된다.

abstract class PaymentAdapter implements Payment {

    @Override
    public final void pay(int amount) {
        validate(amount);
        doPay(amount);
        log();
    }

    protected abstract void doPay(int amount);

    protected void validate(int amount) {}
    protected void log() {}
}

여기서

  • pay()는 외부에서 호출하는 공식 입구 (public + final)
  • doPay()는 자식만 구현 가능한 확장 지점 (protected)

이 구조는:

외부는 흐름을 건드리지 못하고
자식은 필요한 부분만 구현할 수 있게 한다

이게 바로 Template Method 패턴이다.


5. 추상 메서드 vs 예외의 차이

구분추상 메서드예외 던지기
에러 시점컴파일 시점실행 시점
의미반드시 구현지원하지 않음

추상 메서드는 “사전 차단”
예외는 “사후 차단”이다.


6. 더 나은 설계: 인터페이스 분리

환불이 없는 결제 수단이 있다면?

interface Payable { void pay(); }
interface Refundable { void refund(); }

필요한 기능만 구현하면 된다.

이게 인터페이스 분리 원칙(ISP)이다.


🧠 이번에 느낀 점

처음에는

“왜 이렇게 복잡하게 만들지?”

라고 생각했지만,

지금은 이렇게 느껴진다.

  • 추상 메서드는 설계를 강제하는 장치
  • 인터페이스는 기능의 계약
  • 어댑터는 완충 장치
  • protected는 확장 지점
  • final은 흐름 보호

결국 이 모든 것은

“코드를 안전하게 확장하기 위한 장치”

라는 걸 알게 되었다.


📌 마무리

추상 클래스와 인터페이스는
단순 문법이 아니라 설계 도구다.

이제는 문법을 넘어서,

  • 왜 강제하는지
  • 왜 막는지
  • 왜 분리하는지

를 고민해보는 단계로 넘어가는 것 같다.

profile
비전공자에서 시작해 Flutter로 실서비스를 만들었고, 더 깊은 개발을 향해 계속 성장 중인 개발자

0개의 댓글