추상 클래스와 인터페이스
객체지향 프로그래밍(OOP)에서 추상 클래스와 인터페이스는 코드의 구조를 명확히 하고 재사용성을 높이기 위해 사용된다. 두 개념 모두 클래스 간의 일관성을 유지하고, 개발자들이 특정 규칙을 따르도록 강제하는 데 사용되지만 그 역할과 사용 목적에는 분명한 차이가 있다.
abstract class
객체지향에서 추상 클래스는 인스턴스화할 수 없으며 상속만 가능하다. 즉, new 예약어를 통해 추상 클래스의 객체를 정의할 수 없는 것이다. 추상 클래스는 공통적인 속성과 동작을 정의하면서도, 일부 메서드는 구현하지 않아 자식 클래스에서 구현하도록 강제한다. 이를 통해 공통 기능을 공유하면서도 유연하게 세부 동작을 다르게 가져갈 수 있다. 또한 속성(상태)를 포함할 수 있기에 클래스 간 기본 데이터를 제공할 수 있다는 점이 특징이다.
자바스크립트가 추상 클래스 문법을 공식적으로 지원하는 것은 아니지만, 아래와 같은 방식으로 유사하게 구현해볼 수 있다.
class AbstractClass {
// 생성자
constructor(property) {
if (new.target === AbstractClass) {
throw new Error("Cannot instantiate an abstract class directly.");
}
this.property = property;
}
// 일반 메서드
printProperty() {
console.log(`Property: ${this.property}`);
}
// 추상 메서드 흉내
abstractMethod() {
throw new Error("abstractMethod() must be implemented in a subclass.");
}
}
타입스크립트에서는 이를 abstract 키워드를 통해 구현한다. 공통적인 속성과 동작을 정의하면서도, 일부 메서드는 구현하지 않는 추상 클래스의 구조를 자바스크립트보다는 타입스크립트가 조금 더 잘 드러낸다고 생각한다.
abstract class AbstractClass {
// 일반 속성
property: string;
// 생성자
constructor(property: string) {
this.property = property;
}
// 일반 메서드
printProperty(): void {
console.log(`Property: ${this.property}`);
}
// 추상 메서드 (구현 없이 선언만 함)
abstract abstractMethod(): void;
}
참고로 추상 클래스를 상속 받는 클래스를 '구체 클래스(concrete class)'라고 부른다.
interface
반면에 인터페이스는 클래스가 따라야 할 규칙이나 계약 관계를 정의하는 데 초점이 맞춰져있다. 인터페이스는 상태를 포함하지 않고, 오직 메서드의 속성과 구조만을 정의하며, 다중 구현이 가능하기 때문에 객체가 다양한 역할을 수행해야 할 때 특히 유용하다. 추상 클래스의 경우 상속을 통해 코드 재사용 문제를 해결하려 한다면, 인터페이스는 구현을 통해 추상화 문제를 해결한다.
이는 [ SOLID의 의존성 역전 원칙 ]에서 제시된 "고수준 모듈(클래스, 컴포넌트 등)이 저수준 모듈에 의존하는 것이 아니라, 둘 다 추상화(인터페이스)에 의존해야 한다"는 원칙을 잘 반영한다. 인터페이스는 이러한 원칙을 구현하는 데 핵심적인 역할을 하며, 코드의 유연성과 확장성을 높이는 데 기여한다. 이를 통해 객체지향 설계에서 추구하는 강한 결합도를 피하고, 유연한 아키텍처를 설계할 수 있다.
자바스크립트는 interface를 제공하지 않기 때문에, 이러한 기능은 오직 타입스크립트에서만 사용할 수 있다.
// 추상화된 인터페이스 정의
interface DB {
connect(): void;
disconnect(): void;
}
// 저수준 모듈: MySQL 데이터베이스 구현
class MySQL implements Database {
connect(): void {
console.log("Connecting to MySQL...");
}
disconnect(): void {
console.log("Disconnecting from MySQL...");
}
}
// 저수준 모듈: PostgreSQL 데이터베이스 구현
class PostgreSQL implements Database {
connect(): void {
console.log("Connecting to PostgreSQL...");
}
disconnect(): void {
console.log("Disconnecting from PostgreSQL...");
}
}
// 고수준 모듈: DatabaseManager는 Database 인터페이스에 의존
class DBManager {
private db: DB;
// 생성자를 통해 의존성 주입
constructor(db: DB) {
this.db = DB;
}
startConnection(): void {
this.db.connect();
}
closeConnection(): void {
this.db.disconnect();
}
}
언제 필요할까
추상 클래스는 여러 클래스가 갖는 공통의 상태와 행동을 공유하며, 하위 클래스에서 중복되는 상태와 행동을 한 곳에서 관리하기 위한 상향식 설계라 할 수 있다. 따라서 클래스가 단독으로 사용되는 경우나 단일한 역할을 수행하는 클래스를 설계한다면 추상 클래스 대신 일반적인 클래스를 사용하는 것이 적합하다고 생각한다.
인터페이스는 반대로 고수준 모듈과 저수준 모듈이 공통적으로 의존할 수단을 마련하는 하향식 설계라 할 수 있다. 때문에 상속보다 조합이 더 적합하거나 한 클래스만 사용하는 단일 구현이라면, 인터페이스를 사용하는 것이 과한 설계가 될 수 있다.
추상 클래스와 인터페이스는 서로 배타적인 것이 아니라, 필요에 따라 조합하여 사용하여 보다 유연하고 명확한 설계를 돕는 상호 보완적 도구이다. 추상 클래스와 인터페이스를 올바르게 이해하고 활용하는 것은 유지보수성과 확장성을 겸비한 객체지향 설계의 시작점이라 해도 과언이 아닌 것이다.
블로그의 정보
Ayden's journal
Beard Weard Ayden