icon안동민 개발노트

접근 제어자 (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' 클래스 내에서만 접근 가능합니다.

접근 제어자 사용 시 주의사항

  1. private과 protected 멤버는 컴파일 시간에만 강제됩니다. JavaScript로 컴파일된 후에는 일반 프로퍼티로 변환되므로 런타임에는 접근이 가능할 수 있습니다.
  2. 구조적 타이핑으로 인해, 두 클래스가 같은 구조를 가지면 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

  1. 기본적으로 모든 멤버를 private으로 시작하고, 필요한 경우에만 public으로 변경하세요.
  2. protected는 상속 관계에서 필요한 경우에만 사용하세요.
  3. public API는 신중하게 설계하고, 한번 공개된 후에는 쉽게 변경하지 마세요.
  4. 내부 구현 세부사항은 private으로 유지하여 캡슐화를 강화하세요.
  5. readonly를 사용하여 불변성을 보장하세요.
  6. 접근자 메서드(getter/setter)를 통해 private 데이터에 대한 제어된 접근을 제공하세요.
  7. 컴파일 후 JavaScript에서의 접근 가능성을 인지하고, 중요한 정보는 추가적인 보안 조치를 취하세요.

 접근 제어자는 타입스크립트에서 객체 지향 프로그래밍의 핵심 원칙을 구현하는 강력한 도구입니다.

 이를 효과적으로 활용하면 더 안전하고 유지보수가 용이한 코드를 작성할 수 있습니다.