1. 객체 지향의 캡슐화
객체 지향 프로그래밍의 중요한 원리 중 하나는
내부 인터페이스와 외부 인터페이스를 구분 짓는 것,
즉 캡슐화 입니다.
객체 지향 프로그래밍에서 프로퍼티와 메서드는 2개의 그룹으로 분류됩니다.
- 내부 인터페이스 - 동일한 클래스 내의 다른 메서드에서는 접근할 수 있지만,
클래스 밖에서는 접근 할 수 없는 프로퍼티와 메서드 - 외부 인터페이스 - 클래스 밖에서도 접근 가능한 프로퍼티와 메서드
커피 머신으로 예를 들자면
외형에는 커피를 뽑는 버튼 몇 개, 구멍 몇 개, 화면 하나 정도가 있습니다.
내부에는 디테일한 장치들과 부품들이 전부 들어있습니다.
하지만 내부의 것을 알지 못해도 사용자는 간단한 조작으로 커피 머신을 사용할 수 있습니다.
커피 머신의 내부를 가려주는 보호 커버를 제거하면
사용법이 복잡해지고 감전 같은 위험한 상황이 발생하기도 합니다.
머신 안쪽에 숨어있는 디테일한 장치와 부품들이 내부 인터페이스가 될 수 있습니다.
내부 인터페이스의 세부사항들은 서로의 정보를 이용해서 객체를 동작시킵니다.
커피 머신은 보호 커버에 쌓여 있기 때문에 벗기지 않고는 커피머신 외부에서 내부로 접근할 수 없습니다.
밖에서는 내부를 알 수 없고, 접근할 수 없습니다.
내부 인터페이스의 기능은 외부 인터페이스를 통해야만 사용할 수 있습니다.
이런 특징 덕분에 외부 인터페이스만 알아도 객체를 가지고 무언가를 할 수 있습니다.
객체 내부 동작이 어떤지 몰라도 괜찮다는 점은 큰 장점으로 작용합니다.
자바스크립트에는 두 가지 타입의 객체 필드가 있습니다.
- public : 어디서든지 접근할 수 있으며 외부 인터페이스를 구성합니다.
- private : 클래스 내부에서만 접근할 수 있으며 내부 인터페이스를 구성할 때 사용됩니다.
2. protected 필드
자바스크립트에서는 프로퍼티명 앞에 밑줄 _ ( under score )을 붙여
클래스 자신과 자손 클래스에서만 접근을 허용하는 'protected' 필드를 모방해서 사용할 수 있습니다.
(정식으로 지원하지 않습니다.)
protected 필드는
private와 비슷하지만, 자손 클래스에서도 접근이 가능하다는 차이점이 있습니다.
class CoffeeMachine {
waterAmount = 0; // 물통에 차 있는 물의 양
constructor(power) {
this.power = power;
console.log( `전력량이 ${power}인 커피머신을 만듭니다.` );
}
}
// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);
// 물 추가
coffeeMachine.waterAmount = 200;
위 코드에서 waterAmount 와 power는 public입니다.
원하는 값으로 쉽게 변경 할 수 있는 상태입니다.
waterAmount를 protected 프로퍼티로 바꿔서 통제해보겠습니다.
class CoffeeMachine {
_waterAmount = 0;
set waterAmount(value) {
if (value < 0){
console.log("물의 양은 음수가 될 수 없습니다.")
return
}
this._waterAmount = value;
}
get waterAmount() {
return this._waterAmount;
}
constructor(power) {
this._power = power;
}
}
// 커피 머신 생성
let coffeeMachine = new CoffeeMachine(100);
// 물 추가
coffeeMachine.waterAmount = -10 // 물의 양은 음수가 될 수 없습니다.
getter와 setter를 활용해서 waterAmount 프로퍼티를 protected 프로퍼티로 바꿨습니다.
코드에 따라 waterAmount 프로퍼티는 조건에 부합할 때에만 수정할 수 있습니다.
읽기 전용 프로퍼티
power 프로퍼티는 인스턴스 생성시에만 값을 할당할 수 있어야 합니다.
커피 머신의 전력량은 항상 같아야 겠죠.
이런 경우에 읽기 전용 프로퍼티를 활용할 수 있습니다.
읽기 전용 프로퍼티는 setter를 구현하지 않고, getter만 구현하면 됩니다.
class CoffeeMachine {
constructor(power) {
this._power = power;
}
get power() {
return this._power;
}
}
let coffeeMachine = new CoffeeMachine(100);
console.log(`전력량이 ${coffeeMachine.power}인 커피머신을 만듭니다.`);
coffeeMachine.power = 25 // 코드가 동작 하지 않습니다.
console.log(coffeeMachine.power) // 100
getter만 구현했으므로
인스턴스 생성시를 제외하고 power 프로퍼티를 수정할 수 없습니다.
대부분의 코드에서는 get, set 문법보다 다음과 같이
get.... set.....식의 함수가 선호됩니다.
다수의 인자를 받을 수 있다는 장점이 있습니다.
class CoffeeMachine {
_waterAmount = 0;
setWaterAmount(value) {
if (value < 0){
console.log('물의 양은 음수가 될 수 없습니다.')
return
}
this._waterAmount = value;
}
getWaterAmount() {
return this._waterAmount;
}
}
let coffeeMachine = new CoffeeMachine()
coffeeMachine.setWaterAmount(-1); // '물의 양은 음수가 될 수 없습니다.'
console.log(coffeeMachine.getWaterAmount()) // 0
protected 필드는 상속됩니다.
위 예제를 예로 들어
CoffeeMachine 클래스를 상속 받는 자식 클래스가 있다면
자식 클래스의 메서드에서 this._waterAmount, this.power 를 사용해 프로퍼티에 접근 할 수 있습니다.
protected 필드는 private 필드와는 다르게 상속이 가능하므로 ,
자손 클래스에서도 접근이 가능합니다!!
3. private 필드
프로퍼티나 메소드의 앞에 # 을 붙여 private 필드로 만들 수 있습니다.
class CoffeeMachine {
_waterAmount = 0
#waterLimit = 200;
#checkWater() {
if (this._waterAmount < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
if (this._waterAmount > this.#waterLimit) throw new Error("물이 용량을 초과합니다.");
}
}
let coffeeMachine = new CoffeeMachine();
coffeeMachine.#checkWater(); // Private field '#checkWater' must be declared in an enclosing class
coffeeMachine.#waterLimit = 1000; // Private field '#checkWater' must be declared in an enclosing class
private 필드는 해당 클래스 안에서만 접근 가능하고,
클래스 외부나 자손 클래스에서 접근 할 수 없습니다.
private 프로퍼티와 public 프로퍼티를 동시에 가질 수 있습니다.
class CoffeeMachine {
#waterAmount = 0;
get waterAmount() {
return this.#waterAmount; // 해당 클래스 내에서만 private 프로퍼티에 접근 가능합니다.
}
set waterAmount(value) {
if (value < 0) throw new Error("물의 양은 음수가 될 수 없습니다.");
this.#waterAmount = value; // 해당 클래스 내에서만 private 프로퍼티에 접근 가능합니다.
}
}
let machine = new CoffeeMachine();
machine.waterAmount = 100; // --> setter호출
console.log(machine.waterAmount); // 100 --> getter호출
console.log(machine.#waterAmount) // Error: Private field '#waterAmount' must be declared in an enclosing class
waterAmount 는 public 프로퍼티이고,
private 프로퍼티인 #waterAmount 의 접근자 입니다.
class MegaCoffeeMachine extends CoffeeMachine {
test(){
return this.#waterAmount // SyntaxError: Private field '#waterAmount' must be declared in an enclosing class
}
}
자식 클래스에서는 private 프로퍼티에 직접 접근 할 수 없습니다.
class MegaCoffeeMachine extends CoffeeMachine {}
let megaMachine = new MegaCoffeeMachine();
megaMachine.setWaterAmount(100);
console.log(megaMachine.getWaterAmount()); // 100
자식 클래스에서 private 프로퍼티에 접근하려면
getter, setter를 사용하면 됩니다.
private 필드는 this[name]로 사용할 수 없습니다.
private 필드는 computed property를 사용할 수 없습니다.
class User {
field = 'Bob'
sayHi() {
let fieldName = 'field'
console.log(`Hello, ${this[fieldName]}`);
}
#private = 'God'
test(){
let fieldName = 'private'
console.log(`Hello, ${this[fieldName]}`);
}
}
let user = new User();
user.sayHi() // Hello, Bob
user.test() // Hello, undefined
다양한 시나리오에서 위와 같은 private의 제약사항은 너무 엄격합니다.
자식 클래스에서 부모 클래스의 내부에 접근해야 하는 상황이 있을 수 있기 때문입니다.
언어차원에서 지원하지 않는 protected 필드를 더 자주 사용하는 이유입니다.
요약
1. OOP에서 내부 인터페이스와 외부 인터페이스를 구분하는 것을 캡슐화 (encapsulation) 이라고 한다.
2. 캡슐화의 이점은 다음과 같다.
- 외부 사용자의 내부 로직 조작으로 인한 에러를 방지함
- 외부 사용자에게 알리지 않고도 자유롭게 내부 프로퍼티와 매서드 수정 가능 , 업그레이드 용이
(사용자는 외부 인터페이스만 똑같다면 내부 로직이 바뀐걸 알지 못함) - 구현 세부 사항을 숨길 수 있어 편리해지고, 외부 인터페이스에 대한 문서화가 쉬워짐.
3. 내부 인터페이스를 숨기려면 protected( 모방 가능 ) , private ( 지원 ) 필드를 사용하면 된다.
- protected는 _ 로 시작하며, 정의된 클래스와 자손 클래스에서 접근 가능하다.
- private는 # 으로 시작하며, 정의된 클래스 에서만 접근 가능하다.
'Frontend > JS.info 정리' 카테고리의 다른 글
class - mixin (0) | 2022.06.27 |
---|---|
class - 내장 class의 특성 (0) | 2022.06.24 |
class - static method, property (0) | 2022.06.22 |
class - class extends (상속) (0) | 2022.06.21 |
class - class 기본 문법 (0) | 2022.06.20 |