javascript.info, https://ko.javascript.info/private-protected-properties-methods
참고 사이트에 내용을 개인적으로 복습하기 편하도록 재구성한 글입니다.
자세한 설명은 참고 사이트를 살펴보시기 바랍니다.
객체 지향 프로그래밍에서 프로퍼티와 메서드는 두 그룹으로 분류됩니다.
내부 인터페이스(internal interface)
동일한 클래스 내의 다른 메서드에선 접근할 수 있지만, 클래스 밖에선 접근할 수 없는 프로퍼티와 메서드
외부 인터페이스(external interface)
클래스 밖에서도 접근 가능한 프로퍼티와 메서드
커피 머신으로 비유하자면 기계 안쪽에 숨어있는 뜨거운 물이 지나가는 관이나 발열 장치 등이 내부 인터페이스가 될 수 있습니다.
내부 인터페이스의 세부사항들은 서로의 정보를 이용하여 객체를 동작시킵니다. 발열 장치에 부착된 관을 통해 뜨거운 물이 이동하는 것처럼 말이죠.
그런데 커피 머신은 보호 커버에 둘러싸여 있기 때문에 보호 커버를 벗기지 않고는 커피머신 외부에서 내부로 접근할 수 없습니다. 밖에선 세부 요소를 알 수 없고, 접근도 불가능합니다. 내부 인터페이스의 기능은 외부 인터페이스를 통해야만 사용할 수 있습니다.
이런 특징 때문에 외부 인터페이스만 알아도 객체를 가지고 무언가를 할 수 있습니다. 객체 안이 어떻게 동작하는지 알지 못해도 괜찮다는 점은 큰 장점으로 작용합니다.
지금까진 내부 인터페이스와 외부 인터페이스의 개관에 대해 설명해 드렸습니다.
자바스크립트에는 아래와 같은 두 가지 타입의 객체 필드(프로퍼티와 메서드)가 있습니다.
public
어디서든지 접근할 수 있으며 외부 인터페이스를 구성합니다. 지금까지 다룬 프로퍼티와 메서드는 모두public
입니다.
private
클래스 내부에서만 접근할 수 있으며 내부 인터페이스를 구성할 때 쓰입니다.
자바스크립트 이외의 다수 언어에서 클래스 자신과 자손 클래스에서만 접근을 허용하는 "protected"
필드를 지원합니다. protected
필드는 private
과 비슷하지만, 자손 클래스에서도 접근이 가능하다는 점이 다릅니다. protected
필드도 내부 인터페이스를 만들 때 유용합니다. 자손 클래스의 필드에 접근해야 하는 경우가 많기 때문에, protected
필드는 private
필드보다 조금 더 광범위하게 사용됩니다.
자바스크립트는 protected
필드를 지원하지 않지만, protected
를 사용하면 편리한 점이 많기 때문에 이를 모방해서 사용하는 경우가 많습니다.
이제 지금까지 배운 프로퍼티 타입을 사용해 커피머신을 만들어보겠습니다. 실제 커피 머신은 아주 복잡하지만, 여기선 간결성을 위해 간이 모델을 만들겠습니다.
먼저, 간단한 커피 머신 클래스를 만들어보겠습니다.
class CoffeeMachine {
waterAmount = 0; // 물통에 차 있는 물의 양
constructor(power) {
this.power = power;
alert( `전력량이 ${power}인 커피머신을 만듭니다.` );
}
}
// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);
// 물 추가
coffeeMachine.waterAmount = 200;
현재 프로퍼티 waterAmount
와 power
는 public
입니다. 손쉽게 waterAmount
와 power
를 읽고 원하는 값으로 변경하기 쉬운 상태이죠.
이제 waterAmount
를 protected
로 바꿔서 waterAmount
를 통제해 보겠습니다. 예시로 waterAmount
를 0
미만의 값으로는 설정하지 못하도록 만들어 볼 겁니다.
protected
프로퍼티 명 앞엔 밑줄 _
이 붙습니다.
자바스크립트에서 강제한 사항은 아니지만, 밑줄은 프로그래머들 사이에서 외부 접근이 불가능한 프로퍼티나 메서드를 나타낼 때 씁니다.
waterAmount
에 밑줄을 붙여 protected
프로퍼티로 만들어줍시다.
class CoffeeMachine {
_waterAmount = 0;
set waterAmount(value) {
if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
this._waterAmount = value;
}
get waterAmount() {
return this._waterAmount;
}
constructor(power) {
this._power = power;
}
}
// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);
// 물 추가
coffeeMachine.waterAmount = -10; // Error: 물의 양은 음수가 될 수 없습니다.
이제 물의 양을 0
미만으로 설정하면 실패합니다.
power
프로퍼티를 읽기만 가능하도록 만들어봅시다. 프로퍼티를 생성할 때만 값을 할당할 수 있고, 그 이후에는 값을 절대 수정하지 말아야 하는 경우가 종종 있는데, 이럴 때 읽기 전용 프로퍼티를 활용할 수 있습니다.
커피 머신의 경우에는 전력이 이에 해당합니다.
읽기 전용 프로퍼티를 만들려면 setter(설정자)는 만들지 않고 getter(획득자)만 만들어야 합니다.
class CoffeeMachine {
// ...
constructor(power) {
this._power = power;
}
get power() {
return this._power;
}
}
// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);
alert(`전력량이 ${coffeeMachine.power}인 커피머신을 만듭니다.`); // 전력량이 100인 커피머신을 만듭니다.
coffeeMachine.power = 25; // Error (setter 없음)
🔥 getter와 setter 함수
위에서는 get
, set
문법을 사용해서 getter와 setter 함수를 만들었습니다.
하지만 대부분은 아래와 같이 get...
/set...
형식의 함수가 선호됩니다.
class CoffeeMachine {
_waterAmount = 0;
setWaterAmount(value) {
if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
this._waterAmount = value;
}
getWaterAmount() {
return this._waterAmount;
}
}
new CoffeeMachine().setWaterAmount(100);
다소 길어보이긴 하지만, 이렇게 함수를 선언하면 다수의 인자를 받을 수 있기 때문에 좀 더 유연합니다(위 예시에선 인자가 하나뿐이긴 하지만요).
반면 get
, set
문법을 사용하면 코드가 짧아진다는 장점이 있습니다. 어떤걸 사용해야 한다는 규칙은 없으므로 원하는 방식을 선택해서 사용하세요.
🔥 protected
필드는 상속됩니다.
class MegaMachine extends CoffeeMachine
로 클래스를 상속받으면, 새로운 클래스의 메서드에서 this._waterAmount
나 this._power
를 사용해 프로퍼티에 접근할 수 있습니다.
이렇게 protected
필드는 아래에서 보게 될 private
필드와 달리, 자연스러운 상속이 가능합니다.
private
프로퍼티와 메서드는 제안(proposal) 목록에 등재된 문법으로, 명세서에 등재되기 직전 상태입니다.
private
프로퍼티와 메서드는 #
으로 시작합니다. #
이 붙으면 클래스 안에서만 접근할 수 있습니다.
물 용량 한도를 나타내는 private
프로퍼티 #waterLimit
과 남아있는 물의 양을 확인해주는 private
메서드 #checkWater
를 구현해봅시다.
class CoffeeMachine {
#waterLimit = 200;
#checkWater(value) {
if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
if (value > this.#waterLimit) throw new Error("물이 용량을 초과합니다.");
}
}
let coffeeMachine = new CoffeeMachine();
// 클래스 외부에서 private에 접근할 수 없음
coffeeMachine.#checkWater(); // Error
coffeeMachine.#waterLimit = 1000; // Error
#
은 자바스크립트에서 지원하는 문법으로, private
필드를 의미합니다. private
필드는 클래스 외부나 자손 클래스에서 접근할 수 없습니다.
private
필드는 public
필드와 상충하지 않습니다. private
프로퍼티 #waterAmount
와 public
프로퍼티 waterAmount
를 동시에 가질 수 있습니다.
#waterAmount
의 접근자 waterAmount
를 만들어봅시다.
class CoffeeMachine {
#waterAmount = 0;
get waterAmount() {
return this.#waterAmount;
}
set waterAmount(value) {
if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
this.#waterAmount = value;
}
}
let machine = new CoffeeMachine();
machine.waterAmount = 100;
alert(machine.#waterAmount); // Error
protected
필드와 달리, private
필드는 언어 자체에 의해 강제된다는 점이 장점입니다.
그런데 CoffeeMachine
을 상속받는 클래스에선 #waterAmount
에 직접 접근할 수 없습니다. #waterAmount
에 접근하려면 waterAmount
의 getter와 setter를 통해야 합니다.
class MegaCoffeeMachine extends CoffeeMachine {
method() {
alert( this.#waterAmount ); // Error: CoffeeMachine을 통해서만 접근할 수 있습니다.
}
}
다양한 시나리오에서 이런 제약사항은 너무 엄격합니다. CoffeeMachine
을 상속받는 클래스에선 CoffeeMachine
의 내부에 접근해야 하는 정당한 사유가 있을 수 있기 때문이죠. 언어 차원에서 protected
필드를 지원하지 않아도 더 자주 쓰이는 이유가 바로 여기에 있습니다.
🔥 private
필드는 this[name]
로 사용할 수 없습니다.
private
필드는 특별합니다.
알다시피, 보통은 this[name]
을 사용해 필드에 접근할 수 있습니다.
class User {
...
sayHi() {
let fieldName = "name";
alert(`Hello, ${this[fieldName]}`);
}
}
하지만 private
필드는 this[name]
으로 접근할 수 없습니다. 이런 문법적 제약은 필드의 보안을 강화하기 위해 만들어졌습니다.