Interface
인터페이스
클래스가 구현해야 할 메서드 정의. 다형성 구현의 핵심.
인터페이스
클래스가 구현해야 할 메서드 정의. 다형성 구현의 핵심.
Interface(인터페이스)는 클래스가 반드시 구현해야 할 메서드의 시그니처(이름, 매개변수, 반환 타입)를 정의하는 일종의 계약(Contract)입니다. 인터페이스 자체는 구현 코드를 포함하지 않으며, "무엇을 해야 하는가"만 명시하고 "어떻게 하는가"는 구현 클래스에 위임합니다.
인터페이스의 핵심 가치는 다형성(Polymorphism)과 느슨한 결합(Loose Coupling)을 가능하게 한다는 점입니다. 서로 다른 클래스들이 동일한 인터페이스를 구현하면, 클라이언트 코드는 구체적인 구현체를 몰라도 인터페이스 타입으로 일관되게 다룰 수 있습니다. 이는 코드의 유연성과 확장성을 크게 향상시킵니다.
Java, C#, TypeScript 등 정적 타입 언어에서 인터페이스는 언어 키워드로 직접 지원됩니다. Python이나 Go처럼 명시적 인터페이스가 없는 언어에서도 덕 타이핑(Duck Typing)이나 프로토콜(Protocol)을 통해 유사한 개념을 구현합니다. TypeScript에서는 컴파일 타임에만 존재하며 런타임에는 제거되는 구조적 타이핑을 사용합니다.
실무에서 인터페이스는 의존성 주입(DI), 전략 패턴, 어댑터 패턴 등 다양한 디자인 패턴의 기반이 됩니다. 특히 테스트 주도 개발(TDD)에서는 인터페이스를 통해 목(Mock) 객체를 쉽게 주입할 수 있어, 단위 테스트 작성이 훨씬 수월해집니다.
// TypeScript 인터페이스 예제
// 1. 기본 인터페이스 정의
interface PaymentProcessor {
processPayment(amount: number): Promise<boolean>;
refund(transactionId: string, amount: number): Promise<boolean>;
getTransactionStatus(transactionId: string): Promise<string>;
}
// 2. 인터페이스 구현 - 신용카드 결제
class CreditCardProcessor implements PaymentProcessor {
async processPayment(amount: number): Promise<boolean> {
console.log(`신용카드로 ${amount}원 결제 처리 중...`);
// 실제 카드사 API 호출 로직
return true;
}
async refund(transactionId: string, amount: number): Promise<boolean> {
console.log(`거래 ${transactionId}: ${amount}원 환불 처리`);
return true;
}
async getTransactionStatus(transactionId: string): Promise<string> {
return 'COMPLETED';
}
}
// 3. 인터페이스 구현 - 카카오페이 결제
class KakaoPayProcessor implements PaymentProcessor {
async processPayment(amount: number): Promise<boolean> {
console.log(`카카오페이로 ${amount}원 결제 처리 중...`);
// 카카오페이 API 호출 로직
return true;
}
async refund(transactionId: string, amount: number): Promise<boolean> {
console.log(`카카오페이 환불: ${transactionId}`);
return true;
}
async getTransactionStatus(transactionId: string): Promise<string> {
return 'COMPLETED';
}
}
// 4. 다형성 활용 - 인터페이스 타입으로 처리
class OrderService {
// 구체적인 구현체가 아닌 인터페이스에 의존 (느슨한 결합)
constructor(private paymentProcessor: PaymentProcessor) {}
async checkout(orderId: string, amount: number): Promise<void> {
console.log(`주문 ${orderId} 결제 시작`);
const success = await this.paymentProcessor.processPayment(amount);
if (success) {
console.log('결제 완료!');
}
}
}
// 사용 예시 - 결제 수단 쉽게 교체 가능
const creditCardService = new OrderService(new CreditCardProcessor());
const kakaoPayService = new OrderService(new KakaoPayProcessor());
await creditCardService.checkout('ORD-001', 50000);
await kakaoPayService.checkout('ORD-002', 30000);
"결제 모듈이 특정 PG사에 강하게 결합되어 있어서 교체가 어렵습니다. PaymentProcessor 인터페이스를 정의하고 각 PG사별 구현체를 만들면, 나중에 다른 PG사로 쉽게 전환할 수 있습니다."
"인터페이스와 추상 클래스의 차이는 목적에 있습니다. 인터페이스는 '무엇을 할 수 있는가'라는 능력을 정의하고, 추상 클래스는 '무엇인가'라는 공통된 기반을 제공합니다. Java 8 이후로 인터페이스도 default 메서드를 가질 수 있지만, 여전히 상태(필드)를 가질 수 없다는 차이가 있습니다."
"이 서비스 클래스가 구체 클래스인 MySQLRepository를 직접 생성하고 있네요. Repository 인터페이스를 만들고 생성자 주입으로 받으면, 테스트할 때 InMemoryRepository로 쉽게 교체해서 단위 테스트를 빠르게 돌릴 수 있습니다."
인터페이스 분리 원칙(ISP)을 위반하면 구현체가 사용하지 않는 메서드까지 구현해야 합니다. 역할별로 작은 인터페이스로 분리하세요. 예: Readable, Writable을 각각 정의.
인터페이스는 다중 구현이 가능하고 상태를 가질 수 없습니다. 공통 구현 코드나 상태가 필요하면 추상 클래스를 사용하세요. 둘을 조합하는 것도 좋은 방법입니다.
변수, 매개변수, 반환 타입을 구체 클래스가 아닌 인터페이스로 선언하세요. 이렇게 하면 구현체 교체가 쉬워지고, 테스트와 유지보수가 편해집니다.