추상 클래스
추상 클래스는 완전하지 않은 '추상 메서드'를 포함할 수 있는 클래스입니다. 추상 메서드는 메서드의 선언만 있고 구현부는 없는 메서드로, 추상 클래스를 상속받는 자식 클래스가 반드시 구현해야 하는 메서드입니다.
추상 메소드는 추상 클래스 또는 인터페이스에서 사용 가능한, 껍데기만 있는 메소드라고 생각하면 됩니다.
public abstract class Camera {
public abstract void showMainFeature();
}
위 예시에서 Camera 추상 클래스는 showMainFeature라는 추상 메서드를 갖고 있습니다. 이 메서드는 추상 클래스인 Camera 클래스를 상속받는 자식 클래스가 상속 받을 시, 자식 클래스에서 완성을 해야만 합니다.
추상 클래스의 또 다른 특징으로는 추상 클래스 그 자체만으로 객체를 만들지 못하다는 것입니다.
Camera camera = new Camera <- 추상 클래스이기에 객체 생성 불가
인터페이스
인터페이스는 모든 메서드가 추상 메서드인 형태로, 구체적인 구현 없이 메서드의 '시그니처'만을 정의합니다. 클래스가 인터페이스를 구현할 때는 인터페이스에 정의된 모든 메서드를 구체화해야 합니다.
public interface Detectable {
void detect();
}
위 Detectable 인터페이스는 detect라는 메서드를 정의하고 있으며, 이 인터페이스를 구현하는 모든 클래스는 detect 메서드를 구체적으로 정의해야 합니다.
추상클래스와 인터페이스 구현 예시
public class FactoryCam extends Camera implements Detectable, Reportable {
private Detectable dectecr;
private Reportable reporter;
// 객체를 인스턴스 변수로 설정해 둠
// Dectectable 인터페이스를 구현한 클래스의 객체를 저장할 변수
// Reportable 인터페이스를 구현한 클래스의 객체를 저장할 변수
public void setDectecr(Detectable dectecr) {
this.dectecr = dectecr;
}
public void setReporter(Reportable reporter) {
this.reporter = reporter;
}
@Override
public void showMainFeature() {
System.out.println("화재 감지");
}
@Override
public void detect() {
dectecr.detect();
}
@Override
public void report() {
reporter.report();
}
}
FactoryCam factoryCam = new FactoryCam();
factoryCam.setDectecr(fireDetector);
factoryCam.setReporter(normalReporter);
factoryCam.detect();
factoryCam.report();
FactoryCam클래스는 Camera 추상 클래스를 상속받습니다. 이 클래스는 Camera 클래스의 showMainFeature 추상 메서드를 구현해야 합니다. 이렇게 함으로써, Camera의 모든 하위 클래스가 자신만의 주요 기능을 나타내는 방법을 갖습니다.
FactoryCam은 또한 Detectable과 Reportable 인터페이스를 구현합니다. 이 클래스는 이 각 인터페이스들에 정의된 detect와 report 메서드를 각각 구현해야 합니다. FactoryCam 클래스 내에는 Detectable 타입의 dectecr와 Reportable 타입의 reporter라는 두 개의 인스턴스 변수가 있습니다.
이 변수들은 인터페이스 타입을 참조하므로, 이 인터페이스를 구현하는 어떤 클래스의 인스턴스라도 저장할 수 있습니다. 그 이유는 인터페이스를 참조형 타입으로 사용하면, 그 인터페이스를 구현한 모든 클래스의 인스턴스를 참조 변수에 저장할 수 있으며 이는 다형성 덕분으로 부모 클래스나 인터페이스는 자신을 확장하거나 구현한 하위 클래스들의 인스턴스를 참조할 수 있으며 이 현상은 '업캐스팅'이라고 불리며, 자바의 '다형성을 나타내는 중요한 특징 중 하나입니다.
interface Detectable {
void detect();
}
class FireDetector implements Detectable {
public void detect() {
// 화재 감지 로직
}
}
class AdvancedFireDetector extends FireDetector {
// 고급 화재 감지 로직
}
위의 예시에서 FireDetector와 AdvancedFireDetector는 Detectable 인터페이스를 구현합니다. 따라서 Detectable 타입의 참조 변수는 FireDetector 또는 AdvancedFireDetector 객체를 참조할 수 있습니다.
Detectable detector = new FireDetector(); // 업캐스팅
detector.detect(); // FireDetector의 detect 메소드 호출
detector = new AdvancedFireDetector(); // 업캐스팅
detector.detect(); // AdvancedFireDetector의 detect 메소드 호출
의존성 주입
위의 코드를 통해서 의존성 주입이란 개념을 알고 갈 수 있다. 인터페이스 참조 변수를 만든 뒤에, setter를 통하여서 외부에서 해당 인터페이스를 구현한 객체를 주입 받고 있다. 이를 의존성 주입이라고 한다.
의존성 주입을 하지 않는 경우는 new 연산자를 통하여서 직접 객체를 생성하는 경우가 있는데 이는 결합도를 높이므로 별로 좋지 못한 코드라고 볼 수 있다. 의존성 주입 전과 후의 코드를 비교해가며 알아보자.
public class FactoryCam extends Camera implements Detectable, Reportable {
private Detectable detector = new SpecificDetector(); // 직접 생성
private Reportable reporter = new SpecificReporter(); // 직접 생성
// ...
}
이 경우 FactoryCam 클래스는 SpecificDetector와 SpecificReporter 클래스의 구체적인 구현에 의존합니다. 이는 FactoryCam 클래스가 이들 구체 클래스에 강하게 결합되어 있음을 의미하며, 다른 타입의 Detectable이나 Reportable 구현으로 변경하기 어렵습니다.
public class FactoryCam extends Camera implements Detectable, Reportable {
private Detectable detector;
private Reportable reporter;
public void setDetector(Detectable detector) {
this.detector = detector; // 외부에서 주입
}
public void setReporter(Reportable reporter) {
this.reporter = reporter; // 외부에서 주입
}
// ...
}
여기서는 setDetector와 setReporter 메서드를 통해 Detectable과 Reportable 타입의 객체를 외부에서 주입받습니다. 이 방식으로 FactoryCam 클래스는 구체적인 Detectable과 Reportable 구현에 대해 알 필요가 없으며, 다양한 구현을 유연하게 사용할 수 있습니다.
의존성 주입의 핵심
강한 결합 감소: 클래스가 구체 클래스가 아닌 인터페이스에 의존하도록 만들어 강한 결합을 감소시킵니다.
유연성 향상: 새로운 구현을 쉽게 교체할 수 있어 유연성이 증가합니다.
'Java' 카테고리의 다른 글
| Java (컬렉션 프레임워크) (0) | 2024.01.06 |
|---|---|
| Java (제네릭스) (1) | 2024.01.04 |
| Java (클래스) (2) | 2024.01.02 |
| Java (메서드) (2) | 2024.01.02 |
| Java (배열) (1) | 2023.12.31 |