본문으로 건너뛰기

7.4 추상화와 인터페이스 (Abstraction & Interface)

객체지향 설계에서 추상화(Abstraction) 란 복잡한 현실 세계를 프로그램으로 모델링할 때, 불필요한 세부 사항은 숨기고 핵심적인 공통 속성과 기능만을 추출 하여 뼈대를 만드는 작업입니다.

자바에서는 추상화를 두 가지 도구로 구현합니다.

  1. 추상 클래스 (Abstract Class)
  2. 인터페이스 (Interface)

1. 추상 클래스 (Abstract Class)​

추상 클래스는 미완성 설계도 입니다. 구체적인 구현이 아직 결정되지 않은 메서드(추상 메서드)를 포함하는 클래스입니다.

추상 클래스 선언​

// abstract 키워드로 추상 클래스 선언
public abstract class Shape {
protected String color;

// 일반 생성자 가질 수 있음
public Shape(String color) {
this.color = color;
}

// 추상 메서드: 선언부만, 구현부({}) 없음
// → 자식 클래스가 반드시 구현해야 함
public abstract double area();
public abstract double perimeter();

// 일반 메서드: 구현이 완료된 공통 기능
public void displayInfo() {
System.out.printf("도형: %s, 색상: %s, 넓이: %.2f, 둘레: %.2f%n",
getClass().getSimpleName(), color, area(), perimeter());
}

public String getColor() { return color; }
}

추상 클래스 구현 (자식 클래스)​

// 원
public class Circle extends Shape {
private double radius;

public Circle(String color, double radius) {
super(color); // 부모 생성자 호출
this.radius = radius;
}

@Override
public double area() {
return Math.PI * radius * radius;
}

@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}

// 직사각형
public class Rectangle extends Shape {
private double width;
private double height;

public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}

@Override
public double area() {
return width * height;
}

@Override
public double perimeter() {
return 2 * (width + height);
}
}

// 삼각형
public class Triangle extends Shape {
private double a, b, c; // 세 변의 길이

public Triangle(String color, double a, double b, double c) {
super(color);
this.a = a; this.b = b; this.c = c;
}

@Override
public double area() {
// 헤론의 공식
double s = (a + b + c) / 2;
return Math.sqrt(s * (s-a) * (s-b) * (s-c));
}

@Override
public double perimeter() {
return a + b + c;
}
}

추상 클래스의 특징​

public class AbstractTest {
public static void main(String[] args) {
// Shape shape = new Shape("빨강"); // 컴파일 에러! 추상 클래스 인스턴스화 불가

// 자식 클래스로 인스턴스 생성
Shape circle = new Circle("빨강", 5.0);
Shape rectangle = new Rectangle("파랑", 4.0, 6.0);
Shape triangle = new Triangle("초록", 3.0, 4.0, 5.0);

// 다형성: 부모 타입 배열로 관리
Shape[] shapes = { circle, rectangle, triangle };

for (Shape s : shapes) {
s.displayInfo(); // 각 자식의 area(), perimeter() 호출
}
}
}

출력:

도형: Circle, 색상: 빨강, 넓이: 78.54, 둘레: 31.42
도형: Rectangle, 색상: 파랑, 넓이: 24.00, 둘레: 20.00
도형: Triangle, 색상: 초록, 넓이: 6.00, 둘레: 12.00
추상 클래스의 목적

추상 클래스는 자식 클래스들에게 공통 틀(템플릿) 을 제공합니다. 공통 필드와 구현된 메서드를 재사용하면서, 구체적인 구현이 필요한 메서드는 자식에게 강제합니다. "IS-A 관계"를 표현할 때 사용합니다.

2. 인터페이스 (Interface)​

인터페이스는 추상 클래스보다 더 강력한 추상화를 제공합니다. 클래스들이 반드시 구현해야 할 동작의 규격(계약) 을 정의합니다.

기본 인터페이스 선언​

public interface Printable {
// 모든 상수는 자동으로 public static final
int MAX_SIZE = 1000; // == public static final int MAX_SIZE = 1000

// 모든 메서드는 자동으로 public abstract
void print(); // == public abstract void print()
String getContent(); // == public abstract String getContent()
}

// 인터페이스 구현: implements 키워드
public class Document implements Printable {
private String content;

public Document(String content) {
this.content = content;
}

@Override
public void print() {
System.out.println("[문서 출력]: " + content);
}

@Override
public String getContent() {
return content;
}
}

다중 구현 (Multiple Implementation)​

클래스는 단일 상속만 가능하지만, 인터페이스는 여러 개를 동시에 구현할 수 있습니다.

public interface Flyable {
void fly();
default String getTransportType() { return "공중"; }
}

public interface Swimmable {
void swim();
default String getTransportType() { return "수상"; }
}

public interface Drivable {
void drive();
}

// 여러 인터페이스를 동시에 구현
public class AmphibiousVehicle implements Flyable, Swimmable, Drivable {
@Override
public void fly() { System.out.println("하늘을 납니다!"); }

@Override
public void swim() { System.out.println("물 위를 달립니다!"); }

@Override
public void drive() { System.out.println("도로를 달립니다!"); }

// 두 인터페이스에 동일한 default 메서드가 있으면 반드시 오버라이딩
@Override
public String getTransportType() {
return "육·해·공";
}
}

3. Java 8/9+ 인터페이스 신기능​

Java 8부터 인터페이스에 구현을 포함할 수 있게 되었습니다.

default 메서드 (Java 8+)​

public interface Collection<E> {
// abstract 메서드: 구현 클래스가 반드시 구현
void add(E element);
void remove(E element);
int size();

// default 메서드: 기본 구현 제공 (오버라이딩 선택)
default boolean isEmpty() {
return size() == 0;
}

default void printAll() {
System.out.println("Collection with " + size() + " elements");
}
}

static 메서드 (Java 8+)​

public interface Validator<T> {
boolean validate(T value);

// static 메서드: 인터페이스 이름으로 직접 호출
static <T> boolean validateNotNull(T value) {
return value != null;
}
}

// 호출
boolean result = Validator.validateNotNull("hello"); // true

private 메서드 (Java 9+)​

public interface Logger {
default void info(String message) { log("INFO", message); }
default void error(String message) { log("ERROR", message); }
default void warn(String message) { log("WARN", message); }

// private 메서드: default/static 메서드에서만 사용 가능 (외부 노출 없음)
private void log(String level, String message) {
System.out.printf("[%s] %s%n", level, message);
}
}

4. 함수형 인터페이스와 람다​

함수형 인터페이스(Functional Interface) 는 추상 메서드가 정확히 하나 인 인터페이스입니다. @FunctionalInterface 어노테이션으로 명시합니다.

// 함수형 인터페이스 선언
@FunctionalInterface
public interface Calculator {
int calculate(int a, int b);
// 추상 메서드는 반드시 1개만! (default/static은 여러 개 가능)
}

public class LambdaExample {
public static void main(String[] args) {
// 기존 방식: 익명 클래스
Calculator add = new Calculator() {
@Override
public int calculate(int a, int b) {
return a + b;
}
};

// 람다 방식: 훨씬 간결
Calculator addLambda = (a, b) -> a + b;
Calculator subtractLambda = (a, b) -> a - b;
Calculator multiplyLambda = (a, b) -> a * b;

System.out.println(addLambda.calculate(10, 5)); // 15
System.out.println(subtractLambda.calculate(10, 5)); // 5
System.out.println(multiplyLambda.calculate(10, 5)); // 50

// Java 표준 함수형 인터페이스 활용
java.util.function.Predicate<String> isLong = s -> s.length() > 5;
java.util.function.Function<String, Integer> length = String::length;

System.out.println(isLong.test("Hello")); // false
System.out.println(isLong.test("Hello World!")); // true
System.out.println(length.apply("Java")); // 4
}
}

5. 추상 클래스 vs 인터페이스 비교​

구분추상 클래스 (abstract class)인터페이스 (interface)
상속/구현extends (단일 상속)implements (다중 구현)
변수일반 멤버 변수 선언 가능public static final 상수만
메서드추상 + 일반 메서드 모두 가능기본적으로 추상, default/static 가능
생성자있음없음
접근 제어자모든 접근 제어자 사용 가능public 또는 private (Java 9+)
목적IS-A 관계, 공통 코드 공유CAN-DO 관계, 동작 규격 정의
사용 시기관련 클래스의 공통 부모서로 다른 클래스에 공통 기능 부여

6. 실전 예제: 결제 시스템​

인터페이스를 활용한 실제적인 결제 시스템을 구현합니다.

// 결제 처리 인터페이스
public interface PaymentProcessor {
boolean processPayment(double amount);
void refund(double amount);
String getPaymentMethod();

default void printReceipt(double amount) {
System.out.printf("[영수증] 결제 수단: %s, 금액: %.0f원%n",
getPaymentMethod(), amount);
}
}

// 신용카드 결제
public class CreditCardProcessor implements PaymentProcessor {
private String cardNumber;
private double limit;
private double balance;

public CreditCardProcessor(String cardNumber, double limit) {
this.cardNumber = cardNumber;
this.limit = limit;
this.balance = 0;
}

@Override
public boolean processPayment(double amount) {
if (balance + amount > limit) {
System.out.println("[신용카드] 한도 초과! 결제 실패");
return false;
}
balance += amount;
System.out.printf("[신용카드] 카드 %s으로 %.0f원 결제 완료%n",
cardNumber.substring(cardNumber.length() - 4), amount);
printReceipt(amount);
return true;
}

@Override
public void refund(double amount) {
balance -= amount;
System.out.printf("[신용카드] %.0f원 환불 완료. 현재 사용액: %.0f원%n",
amount, balance);
}

@Override
public String getPaymentMethod() { return "신용카드"; }
}

// 카카오페이 결제
public class KakaoPayProcessor implements PaymentProcessor {
private String userId;
private double walletBalance;

public KakaoPayProcessor(String userId, double walletBalance) {
this.userId = userId;
this.walletBalance = walletBalance;
}

@Override
public boolean processPayment(double amount) {
if (walletBalance < amount) {
System.out.println("[카카오페이] 잔액 부족! 결제 실패");
return false;
}
walletBalance -= amount;
System.out.printf("[카카오페이] %s님 %.0f원 결제 완료. 잔액: %.0f원%n",
userId, amount, walletBalance);
printReceipt(amount);
return true;
}

@Override
public void refund(double amount) {
walletBalance += amount;
System.out.printf("[카카오페이] %.0f원 환불. 잔액: %.0f원%n",
amount, walletBalance);
}

@Override
public String getPaymentMethod() { return "카카오페이"; }
}

// 네이버페이 결제
public class NaverPayProcessor implements PaymentProcessor {
private double points;

public NaverPayProcessor(double points) {
this.points = points;
}

@Override
public boolean processPayment(double amount) {
if (points < amount) {
System.out.println("[네이버페이] 포인트 부족! 결제 실패");
return false;
}
points -= amount;
System.out.printf("[네이버페이] %.0f 포인트 사용. 잔여 포인트: %.0f%n",
amount, points);
printReceipt(amount);
return true;
}

@Override
public void refund(double amount) {
points += amount;
System.out.printf("[네이버페이] %.0f 포인트 환불. 잔여 포인트: %.0f%n",
amount, points);
}

@Override
public String getPaymentMethod() { return "네이버페이"; }
}

// 결제 서비스 (어떤 결제 수단이든 동일한 코드로 처리 — 다형성!)
public class PaymentService {
private PaymentProcessor processor;

public PaymentService(PaymentProcessor processor) {
this.processor = processor;
}

// 결제 수단을 교체 가능 (OCP: 확장에 열려 있음)
public void setProcessor(PaymentProcessor processor) {
this.processor = processor;
}

public void checkout(double amount) {
System.out.println("\n=== 결제 진행 ===");
boolean success = processor.processPayment(amount);
if (success) {
System.out.println("결제가 성공적으로 완료되었습니다.");
} else {
System.out.println("결제에 실패하였습니다. 다른 결제 수단을 이용해주세요.");
}
}
}

// 실행 테스트
public class PaymentTest {
public static void main(String[] args) {
PaymentProcessor creditCard = new CreditCardProcessor("1234-5678-9012-3456", 500000);
PaymentProcessor kakaoPay = new KakaoPayProcessor("user123", 50000);
PaymentProcessor naverPay = new NaverPayProcessor(30000);

PaymentService service = new PaymentService(creditCard);
service.checkout(150000); // 신용카드 결제

service.setProcessor(kakaoPay);
service.checkout(30000); // 카카오페이 결제

service.setProcessor(naverPay);
service.checkout(50000); // 네이버페이 결제 실패 (잔액 부족)
}
}

출력:

=== 결제 진행 ===
[신용카드] 카드 3456으로 150000원 결제 완료
[영수증] 결제 수단: 신용카드, 금액: 150000원
결제가 성공적으로 완료되었습니다.

=== 결제 진행 ===
[카카오페이] user123님 30000원 결제 완료. 잔액: 20000원
[영수증] 결제 수단: 카카오페이, 금액: 30000원
결제가 성공적으로 완료되었습니다.

=== 결제 진행 ===
[네이버페이] 포인트 부족! 결제 실패
결제에 실패하였습니다. 다른 결제 수단을 이용해주세요.

요약​

개념핵심 내용
추상화공통 속성/기능 추출, 불필요한 세부사항 은닉
추상 클래스abstract 선언, 인스턴스화 불가, 추상 메서드 강제 구현
인터페이스완전한 추상화, 다중 구현, 동작 규격 정의
default 메서드Java 8+, 인터페이스에 기본 구현 추가
함수형 인터페이스추상 메서드 1개, @FunctionalInterface, 람다와 사용
추상 클래스 vs 인터페이스IS-A 관계 vs CAN-DO 관계