반응형
🎁 인터페이스란 무엇일까?
인터페이스를 쉽게 이해하기 위해 일상 생활의 비유를 들어보겠습니다.
아파트 입주자 계약서 같은 인터페이스
아파트에 입주할 때 계약서를 작성하잖아요? 그 계약서에는 "입주자라면 이런 규칙들을 지켜야 하고, 이런 혜택들을 누릴 수 있습니다"라고 명시되어 있습니다.
인터페이스도 마찬가지입니다!
- "우리 인터페이스를 구현하시면 수영장, 헬스장, 조식뷔폐 등의 여러가지 기능들을 제공드립니다"
- "대신 이 메소드들은 반드시 구현해주셔야 합니다!"
포레스트 검프의 초콜릿 상자 비유
포레스트 검프에서 "Life is like a box of chocolates. You never know what you're gonna get"이라는 명대사가 있죠?
인터페이스도 마찬가지입니다. 겉으로 보기엔 같은 초콜릿 상자(인터페이스)지만, 안에 들어있는 초콜릿(구현체)은 모두 다를 수 있어요.
📋 인터페이스의 정의
인터페이스는 다양한 객체들이 공통적으로 가져야 할 기능들을 규격(메소드 정의)으로 정의해 놓은 설계도 혹은 규약입니다.
이를 통해:
- 객체 간의 느슨한 결합(Loose Coupling)을 가능하게 함
- 다형성을 활용하여 코드를 더 유연하고 확장성 있게 만듦
- 구현할 기능에 대한 일반화를 제공
🔧 인터페이스 구현 방법
기본 구현 문법
public class 구현클래스명 implements interfaceA, interfaceB, interfaceC {
// 인터페이스의 추상 메소드들을 반드시 구현해야 함
}
상속과 인터페이스 구현을 동시에
public class Bird extends Animal implements 날다, 먹다, 소리내다 {
// Animal 클래스 상속 + 3개 인터페이스 구현
}
위 예제에서 Bird 클래스는:
- Animal 클래스의 필드와 기능을 상속받을 수 있음
- 날고, 먹고, 소리내는 기능을 사용할 수 있음
- 중요: 각 인터페이스의 메소드들을 Bird에 맞도록 반드시 구체적으로 구현해야 함
🏗️ 인터페이스의 구성 요소
1. 상수 필드
public interface MyInterface {
public static final int MAX_VALUE = 100;
// 또는 간단히
int MIN_VALUE = 0; // public static final이 자동 추가됨
}
2. 추상 메소드
public interface Drawable {
public void draw(); // public abstract가 자동 추가됨
void move(int x, int y);
}
3. 디폴트 메소드 (Java 8+)
public interface Calculator {
// 추상 메소드
int add(int a, int b);
// 디폴트 메소드 - 구현체에서 선택적으로 오버라이드 가능
default int multiply(int a, int b) {
return a * b;
}
}
4. static 메소드 (Java 8+)
public interface MathUtils {
static int max(int a, int b) {
return a > b ? a : b;
}
}
5. private 메소드 (Java 9+)
public interface Logger {
default void logInfo(String message) {
log(message, "INFO");
}
default void logError(String message) {
log(message, "ERROR");
}
// private 메소드 - 인터페이스 내부에서만 사용
private void log(String message, String level) {
System.out.println(level + ": " + message);
}
}
🔗 인터페이스 상속
인터페이스 간 다중 상속
interface A {
void methodA();
}
interface B {
void methodB();
}
interface C extends A, B { // 다중 상속 가능
void methodC();
}
public class MyClass implements C {
// methodA, methodB, methodC 모두 구현해야 함
@Override
public void methodA() { /* 구현 */ }
@Override
public void methodB() { /* 구현 */ }
@Override
public void methodC() { /* 구현 */ }
}
🔄 인터페이스 타입 변환
자동 타입 변환
// 인터페이스 참조변수에 구현 객체 대입 시 자동 변환
RemoteControl rc = new SmartTelevision();
// SmartTelevision 객체가 RemoteControl 타입으로 자동 변환
상속 관계에서의 자동 변환
class SmartTelevision implements RemoteControl { }
class UltraSmartTV extends SmartTelevision { }
RemoteControl rc = new UltraSmartTV(); // 자동 변환 가능
강제 타입 변환 (캐스팅)
RemoteControl rc = new SmartTelevision();
// SmartTelevision만의 메소드를 사용하려면 강제 변환 필요
if (rc instanceof SmartTelevision) {
SmartTelevision smartTV = (SmartTelevision) rc;
smartTV.internetConnect(); // SmartTelevision만의 메소드
}
🎭 다형성 구현
다형성이란?
사용 방법은 동일하지만 다양한 결과가 나오는 성질
interface Animal {
void makeSound();
}
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("멍멍!");
}
}
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("야옹~");
}
}
// 다형성 활용
Animal[] animals = {new Dog(), new Cat()};
for (Animal animal : animals) {
animal.makeSound(); // 같은 메소드 호출, 다른 결과
}
// 출력: 멍멍! 야옹~
현업에서의 활용
현업에서는 상속보다는 인터페이스를 통한 다형성을 더 많이 사용합니다. 왜냐하면:
- 더 유연한 설계 가능
- 다중 구현 지원
- 결합도를 낮춤
🔧 매개변수의 다형성
인터페이스를 매개변수 타입으로 사용
public class AnimalShelter {
// 매개변수를 인터페이스 타입으로 선언
public void careAnimal(Animal animal) {
animal.makeSound();
// animal.eat(); // Animal 인터페이스에 있는 메소드만 사용 가능
}
}
// 사용 예
AnimalShelter shelter = new AnimalShelter();
shelter.careAnimal(new Dog()); // Dog 객체 전달
shelter.careAnimal(new Cat()); // Cat 객체 전달
shelter.careAnimal(new Bird()); // Bird 객체 전달
💉 의존성 주입 (Dependency Injection)
생성자를 통한 의존성 주입 (권장)
public class AnimalService {
private final Animal animal;
// 생성자를 통한 의존성 주입
public AnimalService(Animal animal) {
this.animal = animal;
}
public void performService() {
animal.makeSound();
}
}
// 사용
AnimalService dogService = new AnimalService(new Dog());
AnimalService catService = new AnimalService(new Cat());
Setter를 통한 의존성 주입 (덜 권장)
public class AnimalService {
private Animal animal;
// Setter를 통한 의존성 주입
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
생성자 주입이 권장되는 이유:
- 객체 생성 시점에 의존성이 확정됨
- 불변성 보장 (final 키워드 사용 가능)
- 의존성이 누락될 위험이 없음
💡 실전 활용 팁
1. 인터페이스 네이밍 컨벤션
// 형용사형 (-able, -ible)
interface Drawable, Comparable, Serializable
// 명사형
interface List, Set, Map
// 동작을 나타내는 경우
interface ActionListener, Observer
2. 인터페이스 분리 원칙 (Interface Segregation Principle)
// 나쁜 예 - 너무 많은 책임을 가진 인터페이스
interface Worker {
void work();
void eat();
void sleep();
void fly(); // 모든 Worker가 날 수 있는 건 아님
}
// 좋은 예 - 역할별로 분리
interface Worker {
void work();
}
interface Eater {
void eat();
}
interface Flyer {
void fly();
}
// 필요한 인터페이스만 구현
class Robot implements Worker {
@Override
public void work() { /* 구현 */ }
}
class Bird implements Worker, Eater, Flyer {
// 모든 메소드 구현
}
🎯 마무리
인터페이스는 Java에서 다형성을 구현하는 핵심 도구입니다.
핵심 포인트:
- 계약서 역할: "이 기능들을 구현하면 이런 혜택을 드립니다"
- 다형성 구현: 같은 방법으로 다양한 결과를 얻을 수 있음
- 유연한 설계: 구현체를 쉽게 교체할 수 있어 확장성이 뛰어남
- 결합도 감소: 구체적인 클래스가 아닌 인터페이스에 의존
현업에서는 인터페이스를 적극 활용하여 유지보수가 쉽고 확장 가능한 코드를 작성합니다. 특히 Spring Framework 같은 프레임워크에서는 인터페이스 기반의 설계가 필수적이에요!
반응형