접근 제어자 (public, private, protected)
타입스크립트의 접근 제어자는 클래스 멤버의 가시성과 접근성을 제어하는 중요한 기능입니다.
이를 통해 객체 지향 프로그래밍의 핵심 원칙인 캡슐화를 구현할 수 있습니다.
타입스크립트는 세 가지 주요 접근 제어자를 제공합니다.
public, private, protected.
public 접근 제어자
public
은 기본 접근 제어자로, 명시적으로 선언하지 않아도 모든 클래스 멤버에 적용됩니다.
public 멤버는 클래스 외부에서 자유롭게 접근할 수 있습니다.
class Example {
public name: string;
age: number; // 암시적으로 public
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const ex = new Example("John", 30);
console.log(ex.name); // 정상 작동
public을 명시적으로 선언하는 것은 코드의 의도를 명확히 하고 가독성을 높이는 데 도움이 될 수 있습니다.
private 접근 제어자
private
멤버는 해당 클래스 내부에서만 접근할 수 있습니다.
이를 통해 클래스의 내부 구현을 숨기고 정보 은닉을 실현할 수 있습니다.
class BankAccount {
private balance: number;
constructor(initialBalance: number) {
this.balance = initialBalance;
}
deposit(amount: number): void {
this.balance += amount;
}
getBalance(): number {
return this.balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// console.log(account.balance); // 오류: 'balance'는 private이며 'BankAccount' 클래스 내에서만 접근 가능합니다.
JavaScript의 private 필드(#)와 달리 TypeScript의 private은 컴파일 시간에만 강제되며 런타임에는 일반 속성으로 변환됩니다.
protected 접근 제어자
protected
멤버는 해당 클래스와 그 하위 클래스에서만 접근할 수 있습니다.
이는 상속 관계에서 유용하게 사용됩니다.
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
bark(): void {
console.log(`${this.name} barks`); // 정상 작동
}
}
const dog = new Dog("Buddy");
dog.bark(); // "Buddy barks"
// console.log(dog.name); // 오류: 'name'은 protected이며 'Animal' 클래스와 그 하위 클래스 내에서만 접근 가능합니다.
생성자 매개변수와 접근 제어자
타입스크립트는 생성자 매개변수에 접근 제어자를 사용하여 간결하게 클래스 프로퍼티를 초기화할 수 있습니다.
class Person {
constructor(public name: string, private age: number) {}
}
// 위 코드는 다음과 동일합니다.
// class Person {
// public name: string;
// private age: number;
// constructor(name: string, age: number) {
// this.name = name;
// this.age = age;
// }
// }
readonly 수식어
readonly
수식어는 프로퍼티를 읽기 전용으로 만듭니다.
이는 다른 접근 제어자와 함께 사용될 수 있습니다.
class Config {
public readonly API_KEY: string;
private readonly SECRET: string;
constructor(apiKey: string, secret: string) {
this.API_KEY = apiKey;
this.SECRET = secret;
}
}
const config = new Config("my-api-key", "my-secret");
console.log(config.API_KEY); // "my-api-key"
// config.API_KEY = "new-key"; // 오류: 읽기 전용 프로퍼티에 할당할 수 없습니다.
정적(static) 멤버와 접근 제어자
정적 멤버에도 접근 제어자를 적용할 수 있습니다.
이는 클래스 수준의 정보를 보호하는 데 유용합니다.
class Utility {
private static count: number = 0;
public static increment(): void {
Utility.count++;
}
public static getCount(): number {
return Utility.count;
}
}
Utility.increment();
console.log(Utility.getCount()); // 1
// console.log(Utility.count); // 오류: 'count'는 private이며 'Utility' 클래스 내에서만 접근 가능합니다.
접근 제어자 사용 시 주의사항
- private과 protected 멤버는 컴파일 시간에만 강제됩니다. JavaScript로 컴파일된 후에는 일반 프로퍼티로 변환되므로 런타임에는 접근이 가능할 수 있습니다.
- 구조적 타이핑으로 인해, 두 클래스가 같은 구조를 가지면 private 멤버가 있어도 호환될 수 있습니다.
class A {
private x: number;
constructor(x: number) { this.x = x; }
}
class B {
private x: number;
constructor(x: number) { this.x = x; }
}
let a: A = new B(5); // 오류가 발생하지 않음
접근 제어자를 통한 코드 안정성 향상
접근 제어자를 적절히 사용하면 클래스의 내부 구현을 숨기고, 외부에서의 잘못된 접근을 방지할 수 있습니다.
이는 코드의 안정성과 유지보수성을 향상시킵니다.
class SafeList {
private items: number[] = [];
add(item: number): void {
this.items.push(item);
}
get(index: number): number | undefined {
if (index >= 0 && index < this.items.length) {
return this.items[index];
}
return undefined;
}
get length(): number {
return this.items.length;
}
}
const list = new SafeList();
list.add(5);
console.log(list.get(0)); // 5
console.log(list.length); // 1
// list.items.push(10); // 오류: 'items'는 private이며 'SafeList' 클래스 내에서만 접근 가능합니다.
이 예제에서 SafeList
클래스는 내부 배열을 private으로 유지하면서 안전한 메서드를 통해서만 데이터에 접근할 수 있도록 합니다.
설계 원칙과 Best Practices
- 기본적으로 모든 멤버를 private으로 시작하고, 필요한 경우에만 public으로 변경하세요.
- protected는 상속 관계에서 필요한 경우에만 사용하세요.
- public API는 신중하게 설계하고, 한번 공개된 후에는 쉽게 변경하지 마세요.
- 내부 구현 세부사항은 private으로 유지하여 캡슐화를 강화하세요.
- readonly를 사용하여 불변성을 보장하세요.
- 접근자 메서드(getter/setter)를 통해 private 데이터에 대한 제어된 접근을 제공하세요.
- 컴파일 후 JavaScript에서의 접근 가능성을 인지하고, 중요한 정보는 추가적인 보안 조치를 취하세요.
접근 제어자는 타입스크립트에서 객체 지향 프로그래밍의 핵심 원칙을 구현하는 강력한 도구입니다.
이를 효과적으로 활용하면 더 안전하고 유지보수가 용이한 코드를 작성할 수 있습니다.