티스토리 뷰

현대 소프트웨어 개발에서 **객체지향 프로그래밍(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) 사용 가능

선택 기준

  1. 단순한 규격이 필요하거나, 여러 클래스에 걸쳐 동일한 동작을 보장해야 할 때는 인터페이스를 사용하세요.
  2. 여러 클래스가 많은 양의 코드나 상태를 공유해야 하고, 그중 일부만 다르게 구현해야 한다면 추상 클래스가 적합합니다.

요약

TypeScript에서 추상화는 단순히 문법적인 기능을 넘어, 대규모 애플리케이션의 복잡도를 제어하는 핵심 기술입니다.

  • interface로 유연한 설계를 시작하세요.
  • abstract class로 중복을 제거하고 명확한 계층 구조를 만드세요.
  • 구체적인 구현체보다 추상화된 타입에 의존함으로써 변경에 강한 코드를 작성할 수 있습니다.
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
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
글 보관함