객체지향 프로그래밍에서 추상화를 구현하는 두 가지 주요 메커니즘인 인터페이스와 추상 클래스는 각각 고유한 특성과 용도를 가지고 있습니다. 이들을 전략적으로 활용하면 더 유연하고 유지보수가 용이한 코드를 작성할 수 있습니다.
인터페이스와 추상 클래스의 차이점
특성 | 추상 클래스 | 인터페이스 |
---|---|---|
정의 | 부모 클래스로 설계된 클래스 | 클래스의 청사진, 추상 메서드 모음 |
선언 | abstract 키워드 사용 |
interface 키워드 사용 |
메서드 유형 | 추상 메서드와 일반 메서드 모두 포함 가능 | 오직 추상 메서드만 포함 (Java 8부터 default, static 메서드 지원) |
변수 유형 | final, non-final, static, non-static 변수 지원 | 오직 static과 final 변수만 지원 |
상속 | extends 키워드 사용, 단일 상속만 지원 |
implements 키워드 사용, 다중 구현 지원 |
추상화 정도 | 부분적 추상화(0-100%) | 완전한 추상화(100%) |
생성자 | 생성자 포함 가능 | 생성자 포함 불가 |
추상 클래스 사용이 적합한 경우
-
공통 기반 클래스가 필요할 때: 상속을 사용하여 관련 클래스들에게 공통 기반 클래스를 제공하고자 할 때 추상 클래스가 적합합니다.
-
비공개 멤버가 필요할 때: 인터페이스의 모든 메서드는 public이어야 하지만, 추상 클래스는 private, protected 멤버를 선언할 수 있습니다.
-
향후 메서드 추가가 예상될 때: 인터페이스에 새 메서드를 추가하면 해당 인터페이스를 구현하는 모든 클래스를 변경해야 하지만, 추상 클래스는 기본 구현을 제공할 수 있어 하위 클래스의 변경이 필요 없습니다.
-
상태 관리가 필요할 때: 추상 클래스는 상태(인스턴스 변수)를 포함할 수 있어 객체의 상태를 관리해야 하는 경우에 유용합니다.
-
컴포넌트 버전 관리가 필요할 때: 추상 클래스를 사용하면 기본 클래스가 업데이트될 때 상속받는 모든 클래스가 자동으로 업데이트됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
abstract class Vehicle {
protected String modelName;
public Vehicle(String modelName) {
this.modelName = modelName;
}
public void startEngine() {
System.out.println(“Engine started for “ + modelName);
}
public abstract void changeGears();
}
|
cs |
인터페이스 사용이 적합한 경우
-
다중 상속이 필요할 때: 자바는 클래스의 다중 상속을 지원하지 않지만, 인터페이스는 다중 구현이 가능합니다.
-
완전한 추상화가 필요할 때: 구현 세부 사항을 완전히 숨기고 순수한 계약만 정의하고자 할 때 인터페이스가 적합합니다.
-
관련 없는 클래스들에게 공통 기능을 제공할 때: 서로 관련이 없는 여러 클래스가 특정 기능을 구현해야 할 때 인터페이스를 사용합니다.
-
보안을 위해: 객체의 중요한 세부 정보만 표시하고 나머지는 숨기고자 할 때 인터페이스를 사용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
interface Animal {
void animalSound();
void sleep();
}
class Pig implements Animal {
public void animalSound() {
System.out.println(“The pig says: wee wee”);
}
public void sleep() {
System.out.println(“Zzz”);
}
}
|
cs |
전략적 조합: 인터페이스와 추상 클래스 함께 사용하기
실제 애플리케이션에서는 인터페이스와 추상 클래스를 함께 사용하여 각각의 장점을 활용할 수 있습니다.
인터페이스 상속
인터페이스는 다른 인터페이스를 확장할 수 있어, 관련 기능을 그룹화하는 데 유용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
interface Animal {
void makeSound();
void sleep();
}
interface Flying extends Animal {
void fly();
void flapWings();
}
class Bird implements Flying {
// Animal과 Flying 인터페이스의 모든 메서드 구현
}
|
cs |
추상 클래스가 인터페이스 구현
추상 클래스가 인터페이스를 구현하고, 일부 메서드에 대한 기본 구현을 제공할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
interface Shape {
double getArea();
double getPerimeter();
}
abstract class AbstractShape implements Shape {
private String color;
public AbstractShape(String color) {
this.color = color;
}
public String getColor() {
return color;
}
// 추상 메서드 추가
public abstract void draw();
}
class Circle extends AbstractShape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
@Override
public double getPerimeter() {
return 2 * Math.PI * radius;
}
@Override
public void draw() {
System.out.println(“Drawing a circle”);
}
}
|
cs |
설계 모범 사례
-
“인터페이스에 프로그래밍하라”: 구체적인 구현보다 추상 인터페이스에 의존하여 유연성을 높입니다.
-
상속보다 합성을 선호하라: 상속은 강한 결합을 만들 수 있으므로, 가능하면 합성을 사용합니다.
-
적절한 추상화 수준 선택: 너무 추상적이거나 구체적이지 않은 적절한 수준을 유지합니다.
-
인터페이스는 작게 유지: 인터페이스 분리 원칙에 따라 인터페이스는 작고 집중적으로 유지합니다.
-
default 메서드 활용: Java 8 이상에서는 인터페이스에 default 메서드를 추가하여 기존 코드를 깨뜨리지 않고 인터페이스를 확장할 수 있습니다.
인터페이스와 추상 클래스는 각각 고유한 장점과 용도를 가지고 있습니다.
인터페이스는 완전한 추상화와 다중 구현을 제공하는 반면, 추상 클래스는 상태 관리와 부분적 구현을 제공합니다.
이들을 전략적으로 조합하여 사용하면 더 유연하고 확장 가능한 객체지향 설계를 구현할 수 있습니다.