springboot를 통해 개발하다보면 abstract와 interface 어떤 차이점이 있기에 따로 사용하게 될까요?
추상 클래스와 인터페이스의 특징
각각에 대한 특징을 한번 알아보겠습니다.
추상 클래스 | 인터페이스 | |
사용 키워드 | abstract | interface |
사용 가능 변수 | 제한 없음 | static final(상수) |
가능한 접근 제어자 | 제한없음 | public |
상속 키워드 | extends | implements |
다중 상속 여부 | 불가능 | 가능 |
조금 더 각각에 대해 알아보겠습니다.
추상 클래스
추상클래스는 하위 클래스의 공통점만을 모아서 만든 추상화하여 만든 클래스를 의미합니다.
또한 다중 상속이 불가능하여 단일 상속만 허용합니다.
추상 클래스는 추상 메소드 외에 일반 클래스처럼 일반적인 필드, 메서드, 생성자를 가질 수 있습니다.
친구들과 신체 검사를 하러간다면, 친구들은 전부 검사 대상이라는 객체로 묶을 수 있으며, 그 검사 대상은 사람이라는 객체로 묶을 수 있습니다. 그럼, 사람은 추상 클래스로 이름, 키, 몸무게로 필드를 가지고, 검사 대상은 시력, 청력 등의 수치를 가진 객체로 만들 수 있습니다.
인터페이스
인터페이스는 모든 메서드를 public으로 정의해야하고, 모든 필드를 public static final인 상수로 정의해야합니다.
게다가, 클래스간 다중 구현을 지원하기 때문에 의존성이 분리되어있습니다. 따라서 마커를 하는 용도를 구분하는 용도로 사용되기도 합니다.
그렇다면 추상클래스와 인터페이스 어떻게 사용될까요?
인터페이스는 정의된 메서드를 각 클래스의 목적에 맞게 기능을 재구현하는 클래스로 사용되고,
추상 클래스는 자신의 기능들을 하위 클래스로 확장시켜나갈때 주로 사용됩니다.
인터페이스 사용 예
1. 어플리케이션의 기능을 정의해야하지만 그 구현 방식이나 대상에 대해 추상화 할 때
2. 서로 관련성이 없는 클래스들을 묶고 주고 싶을때
3. 다중 상속을 통한 추상화 설계를 해야할때
4. 특정 데이터 타입의 행동을 명시할 때, 어디서 그 행동이 구현되는지 특정하지 않을 때
5. 클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장할 때
좋은 예가 있어서 가져와봤습니다.
// 추상 클래스 (조상 클래스)
abstract class Creature { }
// 추상 클래스 (부모 클래스)
abstract class Animal extends Creature { }
abstract class Fish extends Creature { }
// 자식 클래스
class Parrot extends Animal { }
class Tiger extends Animal { }
class People extends Animal { }
class Whale extends Fish { }
문명에서 동물과 물고기가 있습니다.
만일 수영 동작을 하는 swimming()메서드를 각 자식 클래스에 추가하려고 할때,
이때 나중에 확장을 위해 추상화 원칙을 따라야 한다고 한다. 그러면 부모나 조상 클래스에 추상 메소드를 추가해야 하는데, 수영은 고래(Whale) 과 사람(People)만 할수 있으니 이를 동시에 포함하는 Creature 추상 클래스에서 추상 메소드를 추가해야 합니다. (호랑이와 앵무새는 수영을 못한다고 가정한다)
하지만 Creature 추상 클래스에 추상 메소드를 추가하면, 곧 이를 상속하는 모든 자손/자식 클래스에서 반드시 메소드를 구체화 해야한다는 규칙 때문에 실제로 수영을 못하는 호랑이와 앵무새 클래스에서도 메소드를 구현해야 하는 강제성이 생기게 됩니다.
// 추상 클래스 (조상 클래스)
abstract class Creature {
abstract void swimming(); // 수영 동작을 하는 추상 메소드
}
// 추상 클래스 (부모 클래스)
abstract class Animal extends Creature { }
abstract class Fish extends Creature { }
// 자식 클래스
class Parrot extends Animal {
void swimming() {} // 앵무새는 수영을 할수 없지만 상속 관계로 인해 강제적으로 메소드를 구현해야하는 사태가 일어난다.
}
class Tiger extends Animal {
void swimming() {} // 호랑이는 수영을 할수 없지만 상속 관계로 인해 강제적으로 메소드를 구현해야하는 사태가 일어난다.
}
class People extends Animal {
void swimming() {
// ...
}
}
class Whale extends Fish {
void swimming() {
// ...
}
}
물론 메소드를 선언하기만 하고 빈칸으로 놔두면 되기는 하지만, 이는 객체 지향 설계에 위반될 뿐만 아니라 나중에 유지보수 면에서도 마이너스 적인 효과가 됩니다.
따라서 상속에 얽매히지 않는 인터페이스에 추상 메소드를 선언하고 이를 구현(implements) 하면서 자유로운 타입 묶음을 통해 추상화를 이루게 해야합니다.
abstract class Creature { }
abstract class Animal extends Creature { }
abstract class Fish extends Creature { }
// 수영 동작 추상 메소드를 따로 인터페이스를 만들어 넣는다.
interface Swimmable {
void swimming();
}
class Tiger extends Animal { }
class Parrot extends Animal { }
class People extends Animal implements Swimmable{ // 인터페이스를 구현함으로써 동작이 필요한 클래스에만 따로 상속에 구애받지않고 묶음
@Override
public void swimming() {}
}
class Whale extends Fish implements Swimmable{ // 인터페이스를 구현함으로써 동작이 필요한 클래스에만 따로 상속에 구애받지않고 묶음
@Override
public void swimming() {}
}
이렇게 구현하면 swimmalbe이 각 동물에 상속된는 형태가 아니게 되고, 필요한 클래스에만 따로 상속을 해서 사용할 수 있게 됩니다.
'기술 스텍 > Java' 카테고리의 다른 글
[Java] Error & Exception에 대한 정리 (1) | 2024.06.26 |
---|---|
[Java] 접근 제어자 (2) | 2024.04.21 |
[Java] Reflection이란? (4) | 2024.04.13 |
[Java] 컴포지션이란? (60) | 2024.04.12 |
[Java] Interned String (1) | 2024.04.05 |