• [TypeScript] 객체지향의 특징 1편 ( 캡슐화[Encapsulation] , Getter & Setter )

    2021. 3. 11.

    by. KimBangg

    캡슐화 ( Encapsulation )

     

    1-1 캡슐화란 무엇인가?

    캡슐화가 가지는 이미지를 생각해보면, 우리가 먹는 '알약'과 같이 다양한 것들이 하나의 캡슐 안에 들어감으로써 내용물을 보지 못하는 상태를 의미한다.

     

     

    1-2 캡슐화를 왜 하는건데?

    필자는 타입스크립트를 접하기 전에도, 자바에 그래도 가장 큰 관심이 있어 수업도 듣고, 나름의 주언어로 사용을 했었다.

     

    그때 당시에는 컴퓨터를 배운지 얼마 안된 시점이였던터라, 언어가 주는 느낌과 목적을 생각 할 겨를이 없었기에 " 굳이 왜 그걸 돌려가며 접근을 해야하는지?" 에 대한 이해가 부족했었다.

     

    하지만, 이번 타입스크립트 강의를 보고 정리하는 과정을 통해, 다시금 캡슐화의 필요성을 느꼈는데  그것은 바로 " 유효하지 않은 접근"을 막기 위함이라고 생각한다.

     

    말로만 설명하면 이해가 잘 안되니, 코드를 통해 설명을 해보겠다 :)

     

    1-3 코드와 예시

    김동카페를 만들어서 커피를 팔려고 하는데, 이때 1샷에 들어가는 원두의 양인 "BEANS_GRAM_FOR_SHOT" 과 총 원두의 양을 나타내는 

    "coffeebeans" 이 있다.

     

    이 때, 누군가 마음대로 1샷에 들어가는 원두의 양을 7 -> 20으로 바꾸고 원두를 300KG 주문을 했다고 가정을 해보면 커피는 너무 써서 마시지 못할 것이고, 원두는 너무 많아 사용 하지 못하고, 버리게 될 것이다.

     

    이러한 현상을 방지하기 위해서, constructor를 통한 coffeebeans의 설정도 private 화 하여 외부로의 진입을 금지하고,

    오직 fillCoffeeBeans라는 함수를 거쳐서 오게 한다면 beans의 개수를 음수로 주는 경우 또는 너무 많이 주는 형태를 막을 수 있다.

     

    즉 이러한 " 불필요하고 유효 하지 않은 접근"을 막기 위해 캡슐화를 하는 것이다.

     

    {
        type CoffeeCup = {
            shots: number;
            hasMilk: boolean;
        }
        
        // public 기본적으로 설정이 된다.
        // private 외부 접근이 절대 불가능
        // protected 외부 접근은 안되고, 자식 클래스에서 조작이 가능하다.
        class CoffeeMaker {
            private static BEANS_GRAM_PER_SHOT : number = 7; // class level => 오브젝트별로 새로 생성되지 않는다.
            private coffeeBeans : number = 0; // istance(object)
    
            private constructor(coffeeBeans: number) {
                this.coffeeBeans = coffeeBeans * CoffeeMaker.BEANS_GRAM_PER_SHOT;
            }
    
            // static을 통해 Object를 열어 주었다는건, 생성자를 통한 접근을 원치 않는 것.
            // 그리하여 constructor를 private으로 
            static makeMachine(coffeeBeans:number):CoffeeMaker {
                return new CoffeeMaker(coffeeBeans);
            }
    
            fillCoffeeBeans(beans : number) {
                if ( beans < 0 ) {
                    throw new Error("value for beans should be greater than zero");
                } 
                this.coffeeBeans += beans * CoffeeMaker.BEANS_GRAM_PER_SHOT;
            }
    
            makeCoffee(shots: number): CoffeeCup {
                if (this.coffeeBeans < shots * CoffeeMaker.BEANS_GRAM_PER_SHOT) {
                    throw new Error('Not enought coffee beans!');
                } 
                this.coffeeBeans -= shots & CoffeeMaker.BEANS_GRAM_PER_SHOT;
                return {
                    shots,
                    hasMilk: false,
                }
            }
        }
    
    	// Constructor를 이용 가능하게 한 경우.
        //const maker = new CoffeeMaker(3);
        
        const maker = CoffeeMaker.makeMachine(32);
    
    }

     

    2. Getter & Setter

     

    2-1 Getter & Setter 그게 뭐야?

     

    Getter & Setter는  기존에 사용했던 객체화를 비교적 단순화 하기 위한 목적과 최초 설정을 하면 변수가 변경 되지 않는 문제점을 해결 하기 위해 사용이 된다.

     

    하단의 코드를 보면, User라는 Class를 Instance화 하여 사용 하려고 한다.

     

    2-2 코드와 예시

     

    이 때, 설정한 이름은 Kim Donghyeon 으로 한 이후에 user.first = park을 통해 Park Donghyeon으로 바꿔 출력하면 

    우리의 예상과는 다르게 Kim Donghyeon이 출력 된다.

     class User {
            firstName : string;
            lastName : string;
            fullName : string;
    
            get fullName(): string {
                return `${this.firstName} ${this.lastName}`
            }
    
            private internalAge = 4;
    
            get age(): number {
                return this.internalAge;
            }
    
            set age(num:number) {
                if ( num < 0 ) {
                    throw Error('Age must be more than 0');
                }
                this.internalAge = num;
            }
             
            constructor(firstName: string, lastName: string) {
               this.firstName = firstName;
               this.lastName = lastName;
               this.fullName = `${firstName} ${lastName}`;
            }
        }
    
        const user = new User('kim', 'donghyeon');
        console.log(user.fullName);
        user.firstName = 'park';
        console.log(user.fullName);
    }

     

    이 때 Getter 사용하면 별도의 선언 없이, constructor에 선언 해줌으로써 코드를 단순화하고 이를 getter를 통해 반환하는 편의를 얻을 수 있다.

     

    age 에서는 geter & setter를 사용 하였는데, 이를 보면 이해가 더욱 더 잘될겁니다 :)

     class User {
            // private firstName : string;
            // private lastName : string;
            //fullName : string;
    
            get fullName(): string {
                return `${this.firstName} ${this.lastName}`
            }
    
            private internalAge = 4;
    
            get age(): number {
                return this.internalAge;
            }
    
            set age(num:number) {
                if ( num < 0 ) {
                    throw Error('Age must be more than 0');
                }
                this.internalAge = num;
            }
             
            constructor(private firstName: string, private lastName: string) {
                // this.firstName = firstName;
                // this.lastName = lastName;
    
               // this.fullName = `${firstName} ${lastName}`;
               // 위와 같이 선언을 한다면, 설정된 이후에 다른 parameter를 주더라도
               // 변경 되지 않는다.
            }
        }
    
        const user = new User('kim', 'donghyeon');
        console.log(user.fullName);
        // user.firstName = 'park';
        // console.log(user.fullName);
    }

     

    댓글