인터페이스(Interface)와 추상 클래스(Abstract Class)의 차이점은 무엇인가요?

김상욱·2024년 11월 20일

인터페이스(Interface)와 추상 클래스(Abstract Class)의 차이점은 무엇인가요?

목적
인터페이스는 기본적으로 클래스가 구현해야 하는 행동의 규약을 정의. 다중 상속을 지언하며, 구현 클래스가 특정 동작을 반드시 구현하도록 강제
추상 클래스는 공통적인 속성과 동작을 정의하며 이를 상속받은 클래스에서 이를 구체화하거나 재사용하도록 합니다.

구현 여부
인터페이스는 모든 메서드가 기본적으로 추상 메서드로 default와 static 메서드가 추가되었지만 제한적이다.
추상 클래스는 추상 메서드와 일반 메서드를 모두 포함할 수 있습니다. 일반 필드와 상수를 모두 선언할 수 있음.

상속 및 구현
인터페이스는 다중 구현을 지원하며 한 클래스가 여러 인터페이스를 동시에 구현할 수 있다.
추상 클래스는 단일 상속만 가능하며 한 클래스가 여러 추상 클래스를 상속 받을 수 없음.

접근 제어자
인터페이스는 메서드는 항상 public abstract로 선언되며 생략해도 자동으로 적용. private 메서드를 포함할 수 있으나 인터페이스 내부의 코드 재사용을 위한 목적
추상 클래스는 접근 제어자의 제한이 없다.

사용 사례
인터페이스는 서로 연관 없는 클래스 간 공통 동작을 정의할 때 사용. ex) Comparable, Runnable 등
추상 클래스는 서로 밀접하게 연관된 클래스 간 공통적인 상태(필드)와 동작(메서드)를 정의할 때 사용.
ex) Animal 클래스에서 공통 필드(name)와 메서드(move()) 정의 후 이를 상속받은 Dog, Cat 등

public interface Animal {
    void makeSound(); // 추상 메서드
}

public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}
public abstract class Animal {
    String name; // 일반 필드

    public Animal(String name) {
        this.name = name;
    }

    public abstract void makeSound(); // 추상 메서드

    public void eat() { // 일반 메서드
        System.out.println(name + " is eating.");
    }
}

public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

1. 인터페이스 활용: 유연한 설계

실습 목표: 서로 다른 구현체를 인터페이스로 통합하고, 다형성을 활용해 동작을 제어하기

  • 내용: 동물의 행동을 정의하는 Animal 인터페이스를 설계한 뒤, 개, 고양이, 새 등 다양한 구현체를 만들어 봅니다.
  • 요구사항:
    1. Animal 인터페이스에 makeSound()move() 메서드 정의.
    2. 각 동물이 고유한 소리를 내고 움직이는 방식을 다르게 구현.
    3. List<Animal>에 여러 동물을 추가한 뒤, 루프를 돌면서 각 동물의 동작을 실행.
  • 확장 실습:
    • JSON 데이터를 읽어 동물 객체를 생성한 후 동작하도록 구현 (Jackson 라이브러리 사용).

실습 코드 예제:

public interface Animal {
    void makeSound();
    void move();
}

public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
    @Override
    public void move() {
        System.out.println("Dog runs.");
    }
}

public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
    @Override
    public void move() {
        System.out.println("Cat jumps.");
    }
}

public class AnimalTest {
    public static void main(String[] args) {
        List<Animal> animals = List.of(new Dog(), new Cat());
        for (Animal animal : animals) {
            animal.makeSound();
            animal.move();
        }
    }
}

2. 추상 클래스 활용: 공통 동작 및 속성 정의

실습 목표: 상속을 통해 공통 로직을 재사용하고 필요한 부분만 구체화하기

  • 내용: 추상 클래스 Shape를 만들어 공통적인 속성(예: 색상)과 동작(면적 계산)을 정의하고, 사각형, 원 등의 구체적인 도형 클래스를 구현.
  • 요구사항:
    1. 추상 클래스 Shape에 색상 필드와 calculateArea() 추상 메서드 정의.
    2. 사각형, 원 등의 도형 클래스에서 각자의 면적 계산 로직 구현.
    3. 도형 리스트를 순회하며 면적을 출력.
  • 확장 실습:
    • 입력받은 도형 정보를 바탕으로 객체를 동적으로 생성.
    • 도형 데이터를 DB에 저장하고 불러오는 기능 추가.

실습 코드 예제:

public abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    public abstract double calculateArea();

    public void displayInfo() {
        System.out.println("Color: " + color + ", Area: " + calculateArea());
    }
}

public class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * 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 calculateArea() {
        return width * height;
    }
}

public class ShapeTest {
    public static void main(String[] args) {
        List<Shape> shapes = List.of(
            new Circle("Red", 5.0),
            new Rectangle("Blue", 4.0, 6.0)
        );

        for (Shape shape : shapes) {
            shape.displayInfo();
        }
    }
}

3. Spring 기반 실습: 인터페이스와 추상 클래스 적용

실습 목표: 스프링의 의존성 주입(DI)과 다형성을 활용한 설계 학습

  • 내용: 서비스 레이어에 인터페이스를 적용하여 확장성과 유연성을 높이고, 구체적인 구현을 추상화.
  • 요구사항:
    1. PaymentService 인터페이스를 정의하고, KakaoPay, NaverPay 등의 구현체를 작성.
    2. 스프링에서 @Component@Autowired를 사용하여 구현체를 주입.
    3. 컨트롤러에서 다양한 결제 방식을 사용할 수 있도록 설계.
  • 확장 실습:
    • YAML 파일로 구현체를 변경하는 설정(@Primary, @Qualifier) 학습.
    • 인터페이스를 활용한 테스트 코드 작성 (Mock 객체 사용).

실습 코드 예제:

public interface PaymentService {
    void pay(int amount);
}

@Component
public class KakaoPayService implements PaymentService {
    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using KakaoPay.");
    }
}

@Component
public class NaverPayService implements PaymentService {
    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using NaverPay.");
    }
}

@RestController
public class PaymentController {
    private final PaymentService paymentService;

    @Autowired
    public PaymentController(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    @GetMapping("/pay")
    public String pay(@RequestParam int amount) {
        paymentService.pay(amount);
        return "Payment completed.";
    }
}

4. 프로젝트 실습 아이디어

  • 도서 관리 시스템:
    • 도서와 회원 데이터를 추상 클래스 또는 인터페이스로 모델링.
    • 다양한 회원 등급(일반, VIP)을 다형성을 활용해 구현.
  • 결제 게이트웨이 서비스:
    • 여러 결제 방식을 인터페이스로 정의하고 구현.
    • REST API를 통해 주문-결제 흐름 처리.
  • 배달 서비스 플랫폼:
    • 배달 방식(도보, 자전거, 차량)을 인터페이스로 분리.
    • 각 배달 방식에 따른 배송 시간 및 요금 계산 로직 구현.

5. 추가 학습 포인트

  • JUnit 테스트 작성:
    • 인터페이스와 추상 클래스를 Mocking하여 테스트하는 방법 학습.
  • 디자인 패턴:
    • 템플릿 메서드 패턴: 추상 클래스 활용.
    • 전략 패턴: 인터페이스 활용.
  • 리팩토링 연습:
    • 기존 코드를 인터페이스 기반 설계로 변경.

위 실습을 통해 인터페이스와 추상 클래스의 실제 활용 방법을 경험하고, 이를 백엔드 개발 과정에서 유연하게 적용하는 능력을 키울 수 있습니다.

0개의 댓글