반응형

왜 생겨났을까? "역할"과 "구현"의 분리
코딩을 하다 보면 비슷한 기능을 가진 여러 클래스를 만들어야 할 때가 많습니다. 예를 들어, 게임에 등장하는 몬스터를 만든다고 가정해 봅시다. 고블린, 오크, 드래곤은 모두 '공격'이라는 기능을 가지고 있죠.
초보 개발자라면 아마 각 클래스에 attack()이라는 메서드를 개별적으로 만들 겁니다. 하지만 이렇게 하면 문제가 생깁니다.
- 고블린의 공격 방식은 단검 던지기, 오크는 몽둥이 휘두르기, 드래곤은 불 뿜기 등 각각의 구현 내용이 다릅니다.
- 만약 게임의 새로운 규칙을 추가해서 '모든 몬스터는 독에 걸릴 수 있다'는 기능을 넣으려면, 모든 몬스터 클래스에 poison() 메서드를 추가해야 합니다. 몬스터 종류가 많아질수록 수정해야 할 곳이 기하급수적으로 늘어나겠죠.
이 문제를 해결하기 위해 프로그래밍 세계에서는 "역할(What)"과 "구현(How)"을 분리하는 개념을 도입했습니다. 인터페이스와 추상 클래스는 이 분리를 가능하게 해줍니다.
인터페이스 (Interface) - "역할"을 정의하는 계약서
인터페이스는 클래스가 무엇을 할 수 있는지를 정의하는 청사진 또는 계약서와 같습니다.
- 정의: 메서드의 이름만 있고, 구현 내용(중괄호 {})이 없는 추상 메서드들로만 이루어져 있습니다. (자바 8 이후 default, static 메서드 추가)
- 특징:
- 다중 상속 가능: 여러 개의 인터페이스를 동시에 implements할 수 있습니다. 예를 들어, 공격도 할 수 있고, 날 수도 있는 몬스터를 만들 수 있습니다.
- 강제성: 인터페이스를 implements하는 클래스는 인터페이스에 정의된 모든 메서드를 반드시 구현해야 합니다. 이를 통해 일관성을 유지할 수 있습니다.
- 사용 예시:
public interface Flyable {
void fly();
}
public interface Swimmable {
void swim();
}
public class Bird implements Flyable {
@Override
public void fly() {
System.out.println("Bird is flying.");
}
}
public class Fish implements Swimmable {
@Override
public void swim() {
System.out.println("Fish is swimming.");
}
}
public class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("Duck is flying.");
}
@Override
public void swim() {
System.out.println("Duck is swimming.");
}
}
public class Main {
public static void main(String[] args) {
Flyable bird = new Bird();
bird.fly(); // Bird is flying.
Swimmable fish = new Fish();
fish.swim(); // Fish is swimming.
Duck duck = new Duck();
duck.fly(); // Duck is flying.
duck.swim(); // Duck is swimming.
}
}
추상 클래스 (Abstract Class) - "공통적인 뼈대"를 제공하는 설계도
추상 클래스는 미완성된 설계도와 같습니다. 몇몇 중요한 부분은 미리 만들어 놓았지만, 특정 부분은 자식 클래스에서 직접 완성하라고 남겨둔 형태죠.
- 정의: abstract 키워드가 붙은 클래스로, 추상 메서드와 일반 메서드를 모두 가질 수 있습니다.
- 특징:
- 단일 상속: 오직 하나의 추상 클래스만 extends할 수 있습니다.
- 일반 메서드와 멤버 변수 포함: 공통적으로 사용되는 기능은 미리 구현해 놓아 코드의 중복을 줄일 수 있습니다.
- 객체 생성 불가: 추상 클래스는 불완전하기 때문에 직접 객체를 만들 수 없습니다.
- 사용 예시:
public abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return 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 Woof!");
}
}
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println("Meow!");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog("Buddy");
dog.makeSound(); // Woof Woof!
dog.eat(); // Buddy is eating.
Animal cat = new Cat("Kitty");
cat.makeSound(); // Meow!
cat.eat(); // Kitty is eating.
}
}
인터페이스와 추상 클래스, 무엇이 다른가?
구분 | 인터페이스 (Interface) | 추상 클래스 (Abstract Class) |
목적 | '역할' 정의 (What to do) | '공통 뼈대' 제공 (What and How) |
추상 메서드 | 추상 메서드만 가질 수 있음 (자바 8 이후 default, static 메서드 추가) | 추상 메서드와 일반 메서드 모두 가질 수 있음 |
일반 메서드 | default나 static으로만 가능 | 자유롭게 포함 가능 |
다중 상속 | 여러 개를 implements 할 수 있음 | 한 개만 extends 할 수 있음 |
사용 시점 | 관련 없는 클래스들이 공통된 기능을 가져야 할 때 (ex. 공격 기능, 비행 기능) | 클래스들 간에 공통된 속성이나 기능이 많을 때 (ex. 모든 동물은 '이름'과 '나이'를 가짐) |

- 인터페이스: "이 클래스는 이런 기능을 할 수 있다"라고 역할을 명시하고 싶을 때 사용하세요. 여러 클래스에 걸쳐 기능의 일관성을 강제해야 할 때 유용합니다.
- 추상 클래스: "이 클래스들의 공통적인 골격은 이렇게 만들자"라고 미리 설계하고 싶을 때 사용하세요. 기능뿐만 아니라, **공통된 속성(필드)**도 함께 물려주고 싶을 때 적합합니다.
반응형
'Back_End > Java' 카테고리의 다른 글
자바 ReflectionUtils 리플렉션을 쉽게 활용하는 방법 (3) | 2024.12.16 |
---|---|
자바 컬렉션 (Java Collections) (1) | 2024.12.11 |
객체지향 설계의 5원칙 (SOLID) (0) | 2024.12.11 |
Java에서 equals()와 hashCode() 메서드 오버라이딩 (1) | 2024.12.06 |
ORM(Object-Relational Mapping) 이란? (2) | 2024.12.06 |