본문 바로가기
혼공학습단 11기(完)

[혼공스] 09 - 2 '클래스의 고급 기능' 정리 (1)

by jaeheon0520 2024. 2. 15.

 

INTRO

클래스라는 문법은 객체를 더 안전하고 효율적으로 생성하기 위해 만들어진 문법이다. 즉 클래스 문법들은 '어떤 위험이 있어서', '어떤 비효율이 있어서'라는 이유를 기반으로 만들어졌다. 따라서 '어떤 위험'과 '어떤 비효율'이 있었는지 이해할 수 있어야 문법을 제대로 활용할 수 있다.

 

이번 절에서는 제시되는 코드 예제에 어떠한 문제가 있는지 파악한 뒤, 새로운 문법을 살펴보면서 문제를 해결하는 형태로 진행하겠습니다.

 

상속

다음 코드는 Rectangle이라는 사각형을 나타내는 클래스를 선언하고 사용하는 예이다. getPerimeter()라는 사각형의 둘레를 구하는 메소드와 getArea()라는 사각형의 넓이를 구하는 메소드를 추가했다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        class Rectangle {
            constructor (width, height) {
                this.width = width
                this.height = height
            }

            // 사각형의 둘레를 구하는 메소드
            getPerimeter () {
                return 2 * (this.width + this.height)
            }

            // 사각형의 넓이를 구하는 메소드
            getArea () {
                return this.width * this.height
            }
        }

        const rectangle = new Rectangle(10, 20)
        console.log(`사각형의 둘레: ${rectangle.getPerimeter()}`)
        console.log(`사각형의 넓이: ${rectangle.getArea()}`)
    </script>
</head>
<body>
   
</body>
</html>

 

Rectangle 클래스

 

도형을 더 추가하고 싶어서 Square라는 이름의 정사각형을 나타내는 클래스를 추가한다. 

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        class Rectangle {
            constructor (width, height) {
                this.width = width
                this.height = height
            }

            // 사각형의 둘레를 구하는 메소드
            getPerimeter () {
                return 2 * (this.width + this.height)
            }

            // 사각형의 넓이를 구하는 메소드
            getArea () {
                return this.width * this.height
            }
        }

        class Square {
            constructor (length) {
                this.length = length
            }

            // 정사각형의 둘레를 구하는 메소드
            getPerimeter () {
                return 4 * (this.length)
            }

            // 정사각형의 넓이를 구하는 메소드
            getArea () {
                return this.length * this.length
            }
        }

        const square = new Square(10)
        console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
        console.log(`정사각형의 넓이: ${square.getArea()}`)
    </script>
</head>
<body>
   
</body>
</html>

 

`Square 클래스 추가하기

 

코드를 보면 Rectangle 클래스와 Square 클래스는 큰 차이가 없다. 둘 다 사각형이다 보니 둘레를 구하는 메소드와 넓이를 구하는 메소드가 비슷하다.

 

클래스를 분리하는 것이 클래스를 활용하는 쪽에서는 편리하지만, 분리하면 클래스 선언 부분이 복잡해지는 문제가 발생한다. 이런 문제를 해결하기 위해 나온 것이 상속이다. 상속(inheritance)은 클래스의 선언 코드를 중복해서 작성하지 않도록 함으로써 코드의 생산 효율을 올리는 문법이다.

 

기본적인 형태는 다음과 같다.

 

class 클래스 이름 extends 부모클래스 이름 {

}

 

상속은 '상속'이라는 이름처럼 어떤 클래스가 가지고 있는 유산(속성과 메소드)을 다른 클래스에게 물려주는 형태로 사용한다. 이때 유산을 주는 클래스를 부모 클래스(parent class), 유산을 받는 클래스를 자식 클래스(child class)라고 부른다. 코드를 실행하면서 살펴보자.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        // 사각형 클래스
        class Rectangle {
            constructor (width, height) {
                this.width = width
                this.height = height
            }

            // 사각형의 둘레를 구하는 메소드
            getPerimeter () {
                return 2 * (this.width + this.height)
            }

            // 사각형의 넓이를 구하는 메소드
            getArea () {
                return this.width * this.height
            }
        }

        // 정사각형 클래스
        class Square extends Rectangle { // Square 클래스가 자식 클래스이다
            constructor (length) {
                super(length, length) // 부모의 생성자 함수를 호출하는 코드
            }

        }

        // 클래스 사용하기
        const square = new Square(10, 20)
        console.log(`정사각형의 둘레: ${square.getPerimeter()}`) // 상속받은 메서드를 사용
        console.log(`정사각형의 넓이: ${square.getArea()}`)
    </script>
</head>
<body>
   
</body>
</html>

 

상속을 이용한 구현

 

Square 클래스에서 getPerimeter() 메소드와 getArea() 메소드를 선언하지 않았다. 하지만 부모 클래스인 Rectangle 클래스에서 유산(속성과 메소드)을 상속받았으므로 사용할 수 있다.

 

getPerimeter() 메소드와 getArea() 메소드 내부에서 width 속성과 height 속성을 사용하고 있는데, Square 클래스를 보면 이러한 속성을 선언하는 코드조차 없어서 이상할 수 있다. 이와 관련해서 주목해야 하는 코드는 super(length, length)이다. super() 함수는 부모의 생성자를 나타내는 함수이다. super(length, length)를 호출하면 Rectangle 클래스의 constructor(width, height)가 호출되어 width 속성과 height 속성이 들어간다.

 

프로그래밍은 분업화가 매우 잘 되어 있는 분야이다. 그래서 프로그램을 개발할 때 사용하는 거대한 규모의 클래스, 함수, 도구 등의 집합을 의미하는 프레임워크(framework)엔진(engine)이라는 것을 만드는 개발자와 이를 활용해서 다수를 대상으로 하는 서비스, 애플리케이션, 게임을 개발하는 개발자가 다른 경우가 많다. 전자를 프레임워크 개발자 또는 엔진 개발자 등으로 부르며, 후자를 애플리케이션 개발자 등으로 부른다.

 

이때 애플리케이션 개발자들이 프레임워크와 엔진을 활용하는 가장 기본적인 방법이 상속이다. 그래서 상속을 어느정도 알아야 프레임워크와 엔진을 다룰 수 있다.

 

private 속성과 메소드

지금부터 살펴보는 클래스의 다른 내용들은 프레임워크 개발자들이 숙지하고 있어야 하는 내용이다. 자동차를 운전하는 사람이 자동차의 기본적인 구조와 설계를 안다면 "어떻게 운전해야 연비 효율이 좋은가", "겨울에는 이런 부분에 문제가 생길 수 있으니까 미리 대비하자" 등의 발상을 할 수 있는 것처럼 애플리케이션 개발자들도 지금부터 살펴보는 내용을 잘 알아두면 프레임워크를 조금 더 깊게 이해하는 데 도움이 된다.

 

개발의 규모가 커지면서 프레임워크 개발자와 애플리케이션 개발자가 나뉘자, 코드들이 위험해지기 시작했다. 프레임워크 개발자가 Square 클래스를 만들고, 이를 애플리케이션 개발자가 활용한다고 가정하고 다음 코드를 살펴보자.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        // 정사각형 클래스
        class Square {
            constructor (length) {
                this.length = length
            }

            getPerimeter () { return 4 * this.length}
            getArea () { return this.length * this.length}
        }

        // 클래스 사용하기
        const square = new Square(-10) // 길이에 음수를 넣어서 사용함
        console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
        console.log(`정사각형의 넓이: ${square.getArea()}`)
    </script>
</head>
<body>
   
</body>
</html>

 

사용자가 음수 길이를 입력한 경우

 

현재 코드를 보면 Square 객체를 생성할 때 생성자의 매개변수로 음수를 전달하고 있다. 그런데 '길이'라는 것은 음수가 나올 수 없는 값이다. 프레임워크 개발자들은 Square 클래스를 만들 때 "설마 누가 길이를 음수로 넣겠어"라고 생각했을 것이다. 하지만 활용하는 사람은 이러한 사실을 몰랐을 수도 있다.

 

이러한 문제를 막는 방법으로 다음 코드와 같이 조건문을 활용해서 0 이하의 경우 예외를 발생시켜 클래스의 사용자에게 그렇게는 할 수 없다고 인지시켜주는 방법이 있다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        // 정사각형 클래스
        class Square {
            constructor (length) {
                if (length <= 0) {
                    throw '길이는 0보다 커야 합니다.'
                }
                this.length = length
            }

            getPerimeter () { return 4 * this.length}
            getArea () { return this.length * this.length}
        }

        // 클래스 사용하기
        const square = new Square(-10) // 길이에 음수를 넣어서 사용함
        console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
        console.log(`정사각형의 넓이: ${square.getArea()}`)
    </script>
</head>
<body>
   
</body>
</html>

 

길이에 음수가 들어가지 않게 수정하기

 

하지만 이러한 코드만으로는 다음과 같이 생성자로 객체를 생성한 이후에 사용자가 length 속성을 변경하는 것을 막을 수 없다.

 

사용자의 잘못된 사용 예

 

// 클래스 사용하기
const square = new Square(10)
square.length = -10
console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
console.log(`정사각형의 넓이: ${square.getArea()}`)

 

이처럼 클래스의 사용자가 클래스 속성(또는 메소드)을 의도하지 않은 방향으로 사용하는 것을 막아 클래스의 안정성을 확보하기 위해 나온 문법이 private 속성메소드이다. 문법은 다음과 같다.

 

class 클래스 이름 {
    #속성 이름
    #메소드 이름 () {
    
    }
}

 

속성과 메소드 이름 앞에 #을 붙이기만 하면 된다. 이처럼 #이 붙어있는 속성과 메소드는 모두 private 속성과 메소드가 된다. 주의할 것이 있다면 private 속성은 사용하기 전에 미리 외부에 어떤 속성을 private 속성으로 사용하겠다고 선언해줘야 한다는 것이다.

 

코드를 통해 살펴보자. 다음 코드는 이전의 length 속성을 #length 속성으로 변경했다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        // 사각형 클래스
        class Square {
            #length // 이 위치에 해당 속성을 private 속성으로 사용하겠다고 미리 선언한다.

            constructor (length) {
                if (length <= 0) {
                    throw '길이는 0보다 커야 합니다.'
                }
                this.#length = length
            }

            getPerimeter () { return 4 * this.#length}
            getArea () { return this.#length * this.#length}
        }

        // 클래스 사용하기
        const square = new Square(10)
        console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
        console.log(`정사각형의 넓이: ${square.getArea()}`)
    </script>
</head>
<body>
   
</body>
</html>

 

private 활용

 

이렇게 private 속성으로 변경하면 클래스 외부에서는 해당 속성에 접근할 수 없다. 예를 들어 square 객체의 length 속성을 변경해보자. 변경해도 클래스 내부에서 사용하고 있는 속성은 #length 속성이지 length 속성이 아니므로 결과에는 어떠한 영향도 주지 않는다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        // 사각형 클래스
        class Square {
            #length

            constructor (length) {
                if (length <= 0) {
                    throw '길이는 0보다 커야 합니다.'
                }
                this.#length = length
            }

            getPerimeter () { return 4 * this.#length }
            getArea () { return this.#length * this.#length}
        }

        // 클래스 사용하기
        const square = new Square(10)
        square.length = -10
        console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
        console.log(`정사각형의 넓이: ${square.getArea()}`)
    </script>
</head>
<body>
   
</body>
</html>

 

 

클래스 내부의 length 속성을 사용하여 length를 변경하여도 오류가 발생하지 않음을 확인할 수 있다. 

 

#length 속성을 사용하면 다음과 같은 오류가 발생한다. 

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <script>
        // 사각형 클래스
        class Square {
            #length // 이 위치에 해당 속성을 private 으로 사용하겠다고 미리 선언한다

            constructor (length) {
                if (length <= 0) {
                    throw '길이는 0보다 커야 합니다.'
                }
                this.#length = length
            }

            getPerimeter () { return 4 * this.#length }
            getArea () { return this.#length * this.#length}
        }

        // 클래스 사용하기
        const square = new Square(10)
        square.#length = -10 // 클래스 내부의 #length 속성을 사용하여 변경한다
        console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
        console.log(`정사각형의 넓이: ${square.getArea()}`)
    </script>
</head>
<body>
   
</body>
</html>

 

클래스 내부의 #length 속성을 이용할 때 발생하는 오류

 

이렇게 만든 private 속성은 클래스 외부에서는 접근할 수 없으므로 클래스 사용자가 클래스를 잘못 사용하는 문제를 줄일 수 있다.

 

포스팅이 조금 길어졌는데 이 뒷부분에 나오는 내용도 상당히 중요하므로 잠깐 쉬었다 가도록 하자!

 

오늘 하루도 쌓였다.