interface와 abstract class는 추상화를 구현하는 방법이다.
그래서
interface와 abstract class를 알아보기 전에 추상화가 뭔지부터 알아보려고한다.
추상화는 객체의 공통된 핵심 역할과 기능을 정의하는 객체지향의 특징이다.
이제 추상화가 뭐고, 왜 필요한지 알아봤으니 추상화를 구현하는 방법(interface, abstract class)에 대해서 알아보자
interface는 객체가 수행해야 할 공통된 역할과 기능을 정의한 타입이다.
class CardPayment {
void pay() {
System.out.println("카드 결제");
}
}
class OrderService {
private CardPayment payment = new CardPayment();
void order() {
payment.pay();
}
}
결제 방식이 1개일 때는 문제가 없다.
하지만
요구사항(결제 방식)이 추가된다면..?
class OrderService {
private CardPayment cardPayment = new CardPayment();
private KakaoPay kakaoPay = new KakaoPay();
void order(String type) {
if (type.equals("CARD")) {
cardPayment.pay();
} else if (type.equals("KAKAO")) {
kakaoPay.pay();
}
}
}
OrderService가 결제 방식의 모든 종류를 알고 있고 새로운 결제 수단을 추가 할 때마다 OrderService를 수정해줘야한다. (OCP 위반)
interface Payment {
void pay();
}
class OrderService {
private Payment payment;
OrderService(Payment payment) {
this.payment = payment;
}
void order() {
payment.pay();
}
}
OrderService는 결제 방식이 몇 개인지 자체를 모른다.
-> 결제방식이 늘어나도 OrderService를 수정할 필요가 없다.
그래서 interface를 사용하면 확장성있게 개발을 할 수 있다.
서로 다른 구현을 하나의 타입으로 묶어 동일한 방식으로 다루기 위해 필요하다.
구현 변경이 클라이언트 코드에 영향을 주지 않도록 하기 위해 필요하다.
객체를 구현이 아니라 책임과 의미 기준으로 설계하기 위해 필요하다.
abstract class는 추상 메서드와 구현 메서드를 함께 가질 수 있는 클래스 타입이다.
요구사항
모든 결제는 결제 시작 로그, 실제 결제 처리, 결제 완료 로그 순서로 동작해야 한다.
class CardPayment {
void pay() {
System.out.println("결제 시작");
System.out.println("카드 결제 처리");
System.out.println("결제 완료");
}
}
class KakaoPayment {
void pay() {
System.out.println("결제 시작");
System.out.println("카카오페이 결제 처리");
System.out.println("결제 완료");
}
}
공통 로직(결제 시작/완료)이 모든 클래스에 중복되고, 결제 흐름이 바뀌면 모든 클래스를 수정해야하는 문제점이 있다.
abstract class Payment {
public final void pay() {
beforePay();
doPay(); // 하위 클래스가 구현
afterPay();
}
protected void beforePay() {
System.out.println("결제 시작");
}
protected void afterPay() {
System.out.println("결제 완료");
}
protected abstract void doPay();
}
pay()의 전체 흐름을 상위 클래스에서 고정시키고, 실제 결제 방식만 하위 클래스에 위임하도록한다.
하위 클래스 구현
class CardPayment extends Payment {
@Override
protected void doPay() {
System.out.println("카드 결제 처리");
}
}
class KakaoPayment extends Payment {
@Override
protected void doPay() {
System.out.println("카카오페이 결제 처리");
}
}
이렇게 abstract class를 적용하면
공통 로직 제거, 결제(여기서는 결제) 흐름 한 곳에서 관리, 전체 프로세스의 일관성 보장 등의 장점이 있다.
중복 코드를 제거하고 기본 동작을 제공하기 위해 사용한다.
뼈대를 상위 클래스에서 통제하기 위해 사용한다.
잘못된 구현을 컴파일할 때 방지하기 위해 사용한다.
interface와 abstract class의 차이는 크게 목적, 구현 제공 여부, 자유도와 통제, 확장구조, 설계 의도로 나눌 수 있다.
목적
구현 제공 여부
자유도와 통제
확장 구조
설계 의도