디자인 패턴 스터디를 진행하면서 다시 한 번 객체지향을 공부하게 됐다. 내용 중에 추상 클래스와 인터페이스가 나왔고 그 차이점에 대해서 스터디에서 이야기를 나눴었다. 그래서 정리할 겸 다시 공부한 내용을 적어본다...!
abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public abstract void sound(); // 추상 메소드
public void sleep() {
System.out.println(name + "이(가) 잠을 잡니다.");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void sound() {
System.out.println(name + "이(가) 멍멍 짖습니다.");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
public void sound() {
System.out.println(name + "이(가) 야옹 웁니다.");
}
}
public class AnimalExample {
public static void main(String[] args) {
Animal dog = new Dog("뽀삐");
Animal cat = new Cat("나비");
dog.sound(); // 출력: 뽀삐이(가) 멍멍 짖습니다.
dog.sleep(); // 출력: 뽀삐이(가) 잠을 잡니다.
cat.sound(); // 출력: 나비이(가) 야옹 웁니다.
cat.sleep(); // 출력: 나비이(가) 잠을 잡니다.
// 추상 클래스로 직접 객체 생성 시도 (컴파일 에러 발생)
// Animal animal = new Animal("동물");
}
}
예외
: Java 8부터 인터페이스에 디폴트 메서드(default method)와 정적 메서드(static method)를 포함할 수 있게 됨interface Shape {
double getArea();
double getPerimeter();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getArea() {
return Math.PI * radius * radius;
}
public double getPerimeter() {
return 2 * Math.PI * radius;
}
}
class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double getArea() {
return width * height;
}
public double getPerimeter() {
return 2 * (width + height);
}
}
public class ShapeExample {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
System.out.println("원의 면적: " + circle.getArea());
System.out.println("원의 둘레: " + circle.getPerimeter());
System.out.println("사각형의 면적: " + rectangle.getArea());
System.out.println("사각형의 둘레: " + rectangle.getPerimeter());
// 인터페이스로 직접 객체 생성 시도 (컴파일 에러 발생)
// Shape shape = new Shape();
}
}
interface MyInterface {
int MAX_VALUE = 100; // public static final int MAX_VALUE = 100;
String DEFAULT_NAME = "John"; // public static final String DEFAULT_NAME = "John";
}
class MyClass implements MyInterface {
public void printValues() {
System.out.println("최댓값: " + MAX_VALUE);
System.out.println("기본 이름: " + DEFAULT_NAME);
// 인터페이스의 멤버 변수는 값을 변경할 수 없습니다.
// MAX_VALUE = 200; // 컴파일 에러 발생
}
}
public class InterfaceVariableExample {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.printValues();
System.out.println("최댓값: " + MyInterface.MAX_VALUE);
System.out.println("기본 이름: " + MyInterface.DEFAULT_NAME);
}
}
실행결과
최댓값: 100
기본 이름: John
최댓값: 100
기본 이름: John
디폴트 메서드 (Default Method)
- 인터페이스에서 기본 구현을 제공하는 메서드
- 키워드 default를 사용하여 정의
- 인터페이스를 구현하는 클래스에서 디폴트 메서드 오버라이드 가능
- 디폴트 메서드를 사용하면 인터페이스에 새로운 메서드를 추가할 때 기존 구현 클래스에 영향을 주지 않고 기능을 추가 가능
정적 메서드 (Static Method)- 인터페이스에서 정적 메서드를 정의 가능
- 키워드 static을 사용하여 정의
- 정적 메서드는 인터페이스 이름을 통해 직접 호출 가능
interface MyInterface {
void abstractMethod();
default void defaultMethod() {
System.out.println("디폴트 메서드 호출");
}
static void staticMethod() {
System.out.println("정적 메서드 호출");
}
}
class MyClass implements MyInterface {
public void abstractMethod() {
System.out.println("추상 메서드 구현");
}
}
public class InterfaceMethodExample {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.abstractMethod();
obj.defaultMethod();
MyInterface.staticMethod(); // 직접 호출
}
}
실행결과:
추상 메서드 구현
디폴트 메서드 호출
정적 메서드 호출
추상 클래스와 인터페이스의 확연한 차이점을 추상 클래스는 공통 코드를 구현하고, 추상 메소드를 상속 받아서 직접 구현하여서 사용할 수 있다는 점이라고 생각했는데 인터페이스에서도 default 메소드를 이용하여 공통 코드를 구현할 수 있겠다 싶다.
확실한 차이는 다중 상속과 단일 상속인 것 같고, 추상 클래스는 is-a 관계, 인터페이스는 has-a 관계이므로 관계에 맞게 사용할 수 있게 고려하여 사용해야겠다.
또한 인터페이스는 주로 행동(behavior)을 정의하는 데 사용하고, 추상 클래스는 상태(state)와 행동(behavior)을 모두 가질 수 있다고 한다.
인터페이스는 주로 행동에 대한 규약을 정의하고, 다형성을 활용하기 위해 사용되는 반면
추상 클래스는 상속 계층 구조를 형성하고, 공통된 기능을 구현하여 코드 재사용성을 높이는 데 사용된다.
결국은 목적에 맞게 설계해서 사용하는 게 가장 중요하겠다 라는 게 오늘의 교훈!