티스토리 뷰

현대 소프트웨어 개발에서 **객체지향 프로그래밍(OOP)**은 복잡한 로직을 관리 가능한 단위로 나누는 강력한 도구입니다. 그중에서도 **추상화(Abstraction)**는 불필요한 세부 사항을 숨기고 핵심적인 기능만을 노출하여 코드의 재사용성과 유지보수성을 높이는 핵심 개념입니다.
TypeScript에서는 interface와 class(특히 abstract class)를 통해 이 추상화를 완벽하게 구현할 수 있습니다.
1. 추상화(Abstraction)란 무엇인가?
추상화는 "무엇을(What)" 하는지는 정의하지만, "어떻게(How)" 하는지는 정의하지 않는 것을 의미합니다. 사용자는 내부 로직이 어떻게 돌아가는지 몰라도, 제공된 인터페이스만 보고 기능을 사용할 수 있게 됩니다.
왜 추상화가 필요한가?
- 코드 결합도 감소: 구현체가 바뀌어도 인터페이스가 동일하면 사용하는 쪽의 코드를 수정할 필요가 없습니다.
- 가독성 향상: 핵심 비즈니스 로직에 집중할 수 있습니다.
- 확장성: 새로운 기능을 추가할 때 기존 구조를 유지하며 쉽게 확장할 수 있습니다.
2. 인터페이스(Interface)를 통한 추상화
인터페이스는 객체의 **구조(Shape)**를 정의합니다. TypeScript에서 인터페이스는 오직 타입 체크를 위해서만 존재하며, 컴파일 후에는 자바스크립트 코드에서 사라집니다.
예제 1: 알림 시스템(Notification System)
다양한 알림 방식(이메일, SMS, 카카오톡)이 있지만, 모든 알림 서비스는 '메시지 전송'이라는 공통 기능을 가집니다.
// 추상화된 규격 정의
interface NotificationService {
send(recipient: string, message: string): void;
getProviderName(): string;
}
// 구체적인 구현 1: 이메일
class EmailService implements NotificationService {
send(recipient: string, message: string): void {
console.log(`[Email] To: ${recipient}, Content: ${message}`);
}
getProviderName(): string {
return "AWS SES";
}
}
// 구체적인 구현 2: SMS
class SMSService implements NotificationService {
send(recipient: string, message: string): void {
console.log(`[SMS] To: ${recipient}, Content: ${message}`);
}
getProviderName(): string {
return "Twilio";
}
}
// 사용 예시
function notifyUser(service: NotificationService, user: string, msg: string) {
console.log(`Using Service: ${service.getProviderName()}`);
service.send(user, msg);
}
const myEmail = new EmailService();
notifyUser(myEmail, "user@example.com", "반갑습니다!");
3. 추상 클래스(Abstract Class)를 통한 추상화
인터페이스가 구조만 정의한다면, 추상 클래스는 구현 코드와 추상 메서드를 동시에 가질 수 있습니다. new 키워드로 직접 인스턴스를 생성할 수 없으며, 반드시 상속을 통해 완성되어야 합니다.
예제 2: 결제 시스템 가공 (Payment Gateway)
모든 결제는 '로그 기록'이나 '공통 검증' 로직을 공유하지만, 실제 결제 승인 방식은 수단마다 다릅니다.
abstract class PaymentProcessor {
// 공통 로직 (구현부 포함)
protected logTransaction(amount: number) {
console.log(`결제 시도 기록: ${amount}원 - ${new Date().toISOString()}`);
}
// 추상 메서드 (자식 클래스에서 반드시 구현해야 함)
abstract process(amount: number): void;
abstract refund(amount: number): void;
}
class CreditCardProcessor extends PaymentProcessor {
process(amount: number): void {
this.logTransaction(amount);
console.log(`신용카드 API 호출: ${amount}원 승인 완료`);
}
refund(amount: number): void {
console.log(`신용카드 API 호출: ${amount}원 환불 처리`);
}
}
class PayPalProcessor extends PaymentProcessor {
process(amount: number): void {
this.logTransaction(amount);
console.log(`PayPal 계정 확인 및 ${amount}원 이체 완료`);
}
refund(amount: number): void {
console.log(`PayPal 이체 취소 API 호출`);
}
}
4. 실무형 종합 예제: 데이터 저장소(Repository) 패턴
데이터베이스가 MySQL이든 MongoDB이든, 비즈니스 로직(Service)은 동일한 인터페이스를 통해 데이터에 접근해야 합니다.
interface User {
id: number;
name: string;
}
// 추상화된 저장소 규격
interface UserRepository {
save(user: User): void;
findById(id: number): User | undefined;
}
// 메모리 기반 구현 (테스트용)
class MemoryUserRepository implements UserRepository {
private users: User[] = [];
save(user: User): void {
this.users.push(user);
console.log("메모리에 사용자 저장 완료");
}
findById(id: number): User | undefined {
return this.users.find(u => u.id === id);
}
}
// 실제 DB 기반 구현 (예시)
class DatabaseUserRepository implements UserRepository {
save(user: User): void {
console.log(`DB Query: INSERT INTO users VALUES (${user.id}, '${user.name}')`);
}
findById(id: number): User | undefined {
console.log(`DB Query: SELECT * FROM users WHERE id = ${id}`);
return { id, name: "Database User" };
}
}
// 서비스 로직은 Repository의 구체적 종류에 의존하지 않음
class UserService {
constructor(private repo: UserRepository) {}
register(user: User) {
if (this.repo.findById(user.id)) {
console.log("이미 존재하는 사용자입니다.");
return;
}
this.repo.save(user);
}
}
// 실행
const productionService = new UserService(new DatabaseUserRepository());
productionService.register({ id: 1, name: "홍길동" });
5. 인터페이스 vs 추상 클래스, 무엇을 선택할까?
구분인터페이스 (Interface)추상 클래스 (Abstract Class)| 목적 | 객체의 구조/규격 정의 | 공통 기능 공유 및 기본 틀 제공 |
| 구현 코드 | 불가능 (정의만 가능) | 가능 (구현 메서드 포함 가능) |
| 다중 상속/구현 | 여러 인터페이스 구현 가능 | 단 하나만 상속 가능 |
| 필드 사용 | 프로퍼티 선언만 가능 | 접근 제어자(private, protected) 사용 가능 |
선택 기준
- 단순한 규격이 필요하거나, 여러 클래스에 걸쳐 동일한 동작을 보장해야 할 때는 인터페이스를 사용하세요.
- 여러 클래스가 많은 양의 코드나 상태를 공유해야 하고, 그중 일부만 다르게 구현해야 한다면 추상 클래스가 적합합니다.
요약
TypeScript에서 추상화는 단순히 문법적인 기능을 넘어, 대규모 애플리케이션의 복잡도를 제어하는 핵심 기술입니다.
- interface로 유연한 설계를 시작하세요.
- abstract class로 중복을 제거하고 명확한 계층 구조를 만드세요.
- 구체적인 구현체보다 추상화된 타입에 의존함으로써 변경에 강한 코드를 작성할 수 있습니다.
'Frontend > Typescript' 카테고리의 다른 글
| 맵드 타입(Mapped Types) 완벽 가이드: 기존 타입을 기반으로 새 타입 생성하기 (0) | 2026.02.20 |
|---|---|
| 유틸리티 타입(Utility Types) 활용기: Partial, Pick, Omit, Readonly 등 실무 필수 타입 (0) | 2026.02.19 |
| TypeScript 열거형(Enum) vs const 객체: 성능과 가독성 측면에서의 비교 분석 (0) | 2026.02.19 |
| 타입 가드(Type Guard): typeof, instanceof, is를 활용한 안전한 타입 좁히기 (0) | 2026.02.19 |
| 유니온(Union)과 인터섹션(Intersection): 타입을 유연하게 조합하는 아키텍처 (0) | 2026.02.19 |
- Total
- Today
- Yesterday
- Rag
- HBM
- MSA
- TypeScript
- AI
- It용어
- LLM
- 스마트안경
- react
- CSR
- 엣지컴퓨팅
- sLLM
- SSR
- on-device ai
- 구글
- java
- Javascript
- 카카오
- 협력
- CSS
- 웹기초
- prompt engineering
- HTML
- Nextjs
- 멀티모달
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |