혼자서 공부하는 자바스크립트 완독하기! (Chapter_09 클래스)

운동하는 개발자·2022년 11월 24일
0
post-thumbnail

09. 클래스

09-1. 클래스의 기본 기능

C 언어를 제외한 프로그래밍 언어는 객체 지향이라는 패러다임을 기반으로 만들어진 프로그래밍 언어입니다.
객체 지향 페러다임이란 객체를 우선적으로 생각해서 프로그램을 만든다는 방법론 입니다.

객체 지향 프로그래밍 언어들은 클래스라는 문법으로 객체를 효율적이고 안전하게 만들어 객체 지향 패러다임을 쉽게 프로그래밍에 적용할 수 있도록 도와준다.

추상화

프로그램에 필요한 요소만 사용해서 객체를 표현 하는 것을 추상화라고 한다.

같은 형태의 객체 만들기

학생 성적 관리 프로그램을 만들때 추상화를 적용해 보자

객체를 처리하는 함수 만들기(성적표 만들기)

  1. 객체를 이용하지 않는 경우.
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            const students = []
            students.push({이름: '구름', 국어: 88, 영어: 83, 수학: 100, 과학: 12})
            students.push({이름: '별이', 국어: 82, 영어: 83, 수학: 10, 과학: 65})
            students.push({이름: '겨울', 국어: 84, 영어: 82, 수학: 40, 과학: 61})
            students.push({이름: '바다', 국어: 81, 영어: 23, 수학: 54, 과학: 62})
            students.push({이름: '하늘', 국어: 83, 영어: 63, 수학: 66, 과학: 65})

            let outPut = '이름\t총점\t평균\n'
            for (const s of students){
                const sum = s.국어 + s.영어 + s.수학 + s.과학
                const average = sum/4
                outPut += `${(s.이름)}\t${sum}점\t${average}점\n`
            }
            console.log(outPut)
        </script>
    </head>
    <body>
        
    </body>
</html>
  1. 객체를 이용하는 경우
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            const students = []
            students.push({이름: '구름', 국어: 88, 영어: 83, 수학: 100, 과학: 12})
            students.push({이름: '별이', 국어: 82, 영어: 83, 수학: 10, 과학: 65})
            students.push({이름: '겨울', 국어: 84, 영어: 82, 수학: 40, 과학: 61})
            students.push({이름: '바다', 국어: 81, 영어: 23, 수학: 54, 과학: 62})
            students.push({이름: '하늘', 국어: 83, 영어: 63, 수학: 66, 과학: 65})

            // 객체를 처리하는 함수를 선언합니다.
            function getSumOf(student){
                return student.국어 + student.영어 + student.수학 + student.과학
            }

            function getAverageOf(student){
                return getSumOf(student)/4
            }

            // 출력
            let outPut = '이름\t총점\t평균\n'
            for (const s of students){
                outPut += `${(s.이름)}\t${getSumOf(s)}점\t${getAverageOf(s)}점\n`
            }
            console.log(outPut)
        </script>
    </head>
    <body>
        
    </body>
</html>

1번 보다 2번의 경우 전체적인 코드의 줄이 길어지는 것을 느낄수 있다.
하지만, 2번의 경우 function 함수(객체)로 만들어 코드를 수정하는데 유리한 것을 확인 할 수 있다.
이러한 것을 코드에 확장성이 좋다고 표현을 한다.

객체의 기능을 메소드로 추가하기

객체의 수가 늘어나면 함수 이름 충돌이 발생할 수 있다. 또한 매개변수에 어떤 종류의 객체를 넣을지 몰라 함수를 사용하는데 혼동이 있을 수 있다. 그래서 개발자들은 함수를 메소드로써 객체 내부에 넣어 활용하는 방법을 사용하기 시작하였다.

아래의 코드를 확인해보자.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            const students = []
            students.push({이름: '구름', 국어: 88, 영어: 83, 수학: 100, 과학: 12})
            students.push({이름: '별이', 국어: 82, 영어: 83, 수학: 10, 과학: 65})
            students.push({이름: '겨울', 국어: 84, 영어: 82, 수학: 40, 과학: 61})
            students.push({이름: '바다', 국어: 81, 영어: 23, 수학: 54, 과학: 62})
            students.push({이름: '하늘', 국어: 83, 영어: 63, 수학: 66, 과학: 65})

            // 객체를 처리하는 함수를 선언합니다.
            for(const student of students){
                student.getSum = function(){
                return this.국어 + this.영어 + this.수학 + this.과학
            }

                student.getAverage = function(){
                    return this.getSum() / 4
                }
            }
            

            // 출력
            let outPut = '이름\t총점\t평균\n'
            for (const s of students){
                outPut += `${(s.이름)}\t${s.getSum()}점\t${s.getAverage()}점\n`
            }
            console.log(outPut)
        </script>
    </head>
    <body>
        
    </body>
</html>

이렇게 코드를 작성핳면 함수 이름 충돌도 발생하지 않고, 함수를 잘못 사용하는 경우도 줄일 수 있다.

위의 코드의 경우에는 개체의 키와 값을 하나하나 모두 입력하여 생성을 하였다. 다음으로는 함수를 사용해서 객체를 찍어내는 방법을 해보자.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            function creatStudent(이름, 국어, 영어, 수학, 과학) {
                return{
                    이름: 이름,
                    국어: 국어,
                    영어: 영어,
                    수학: 수학,
                    과학: 과학,
                    
                    // 메소드 선택
                    getSum() {
                        return this.국어 + this.영어 + this.수학 + this.과학
                    },
                    getAverage(){
                        return this.getSum() / 4
                    },
                    toString(){
                        return `${this.이름}\t${this.getSum()}점\t${this.getAverage()}점\n`
                    }
                }
                
            }
            // 객체선언
            const students = []
            students.push(creatStudent('구름', 88, 83, 100, 12))
            students.push(creatStudent('하늘', 88, 83, 100, 12))
            students.push(creatStudent('별', 88, 83, 100, 12))
            students.push(creatStudent('바다', 88, 83, 100, 12))
            students.push(creatStudent('태양', 88, 83, 100, 12))

            // 출력
            let outPut = '이름\t총점\t평균\n'
            for (const s of students){
                outPut += s.toString()
            }
            console.log(outPut)
        </script>
    </head>
    <body>
        
    </body>
</html>

이와 같이 코딩을 하는 경우 여러가지 이득을 취할수 있다.
1. 오탈자의 위험이 줄어듭니다.
2. 코드를 입력하는 양이 크게 줄어듭니다.
3. 속성과 메소드를 한 함수 내부에서 관리할 수있으므로 객체를 더 손쉽게 유지보수 할 수 잇습니다.

클래스 선언하기

클래스와 프로토타입

클래스 : 객체를 만들 때 수많은 지원을 하는 대신 많은 제한을 거는 문법
프로토타입 : 제한을 많이 하지 않지만, 대신 지원도 별도 하지 않는 문법

클래스를 기반으로 만든 객체는 전문 용어로 인스턴스라고 부릅니다.

new 클래스이름()

클래스를 붕어빵의 전체적인 틀이라고 한다면, 인스턴스는 붕어빵을 하나 만들기 위한 틀이라고 보면 쉽다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            class Student{

            }
            // 학생을 선언한다.
            const student = new Student()

            // 학생 리스트를 선언한다.
            const students = [
                new students(),
                new students(),
                new students(),
                new students(),
                new students(),
                new students()
            ]
        </script>
    </head>
    <body>
        
    </body>
</html>

클래스 이륾은 첫 글자를 대문자로 지정하는 것이 개발자들의 약속이다.

09-2. 클래스의 고급 기능

상속

클래스를 분리하는 것이 클래스 활용하는 쪽에서는 편리하겠지만, 분리하면 클래스 선언 부분이 복잡해지는 문제가 발생합니다. 이러한 문제를 해결하기 위해 상속이라느 개념을 사용합니다.

  • 상속의 기본적인 형태
class 클래스이름 extends 부모클래스 이름 {

}

상속은 어떤 클래스가 가지고 있는 속성과 메소드를 다른 클래스에게 공유 하는 형태로 사용합니다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            class Ractangle {
                constructor (width, height){
                    this.width = width
                    this.height = height
                }

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

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

            // 정사각형 클래스
            class Sqare extends Ractangle {
                constructor (length){
                    super(length, length)
                }
            }

            // 클래스 사용하기
            const sqare = new Sqare(10, 20)
            console.log(`둘레 ${sqare.getPerimeter()}`)
            console.log(`넓이 ${sqare.getArea()}`)
        </script>
    </head>
    <body>
        
    </body>
</html>

Sqare 클래스에는 getPerimeter()getArea()를 선언하지 않았습니다. 하지만, 상속기능을 이용하여 Rectangle 클래스에서 선언한 것을 가지고 와서 사용하였습니다.

여기서 주목해야될 것은 상속 시 속성을 선언하는 방법입니다.

super(legnth, length)

super() 함수는 부모의 생성자를 나타내는 함수입니다. 따라서 이경우 whidtheight가 됩니다.

private 속성과 메소드

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            class Ractangle {
                constructor (width, height){
                    this.width = width
                    this.height = height
                }

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

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

            // 정사각형 클래스
            class Sqare extends Ractangle {
                constructor (length){
                    super(length, length)
                }
            }

            // 클래스 사용하기
            const sqare = new Sqare(-10)
            console.log(`둘레 ${sqare.getPerimeter()}`)
            console.log(`넓이 ${sqare.getArea()}`)
        </script>
    </head>
    <body>
        
    </body>
</html>

위의 코드에서는 -10 '음수' 이므로 길이가 음수가 나올 수 없으므로 코드상 오류는 일어나지 않지만, 원하는 값이 제대로 나오지는 않습니다.

이러한 경우 예외처리를 해야되는데 다음과 같은 방법으로 예외처리를 한다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            class Ractangle {
                constructor (width, height){
                    this.width = width
                    this.height = height
                }

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

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

            // 정사각형 클래스
            class Sqare extends Ractangle {
                constructor (length){
                    if(length <= 0){
                        throw '길이는 0보다 커야됩니다.'
                    }
                    this.length = length
                }
            }

            // 클래스 사용하기
            const sqare = new Sqare(-10)
            console.log(`둘레 ${sqare.getPerimeter()}`)
            console.log(`넓이 ${sqare.getArea()}`)
        </script>
    </head>
    <body>
        
    </body>
</html>

이렇게 if 문을 이용하여 예외처리를 할 수 잇지만,
정말 다양한 사용자가 있어 아래와 같이 사용하는 경우가 있습니다.

// 클래스 사용하기
const sqare = new Sqare(-10)
sqare = -10
console.log(`둘레 ${sqare.getPerimeter()}`)
console.log(`넓이 ${sqare.getArea()}`)

이와 같이 사용하면 막을 수가 위의 예외처리로는 막을 수 없습니다.
이러한 경우가 존재하기 때문에 previate를 사용합니다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            class Ractangle {
                constructor (width, height){
                    this.width = width
                    this.height = height
                }

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

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

            // 정사각형 클래스
            class Sqare extends Ractangle {
                #length // 이 위치에 해당 속성을 prviate 속성으로 사용하겠다고 미리 선언합니다.
            
                constructor (length){
                    if(length <= 0){
                        throw '길이는 0보다 커야됩니다.'
                    }
                    this.length = length
                }
            }

            // 클래스 사용하기
            const sqare = new Sqare(-10)
            sqare#length = -10
            console.log(`둘레 ${sqare.getPerimeter()}`)
            console.log(`넓이 ${sqare.getArea()}`)
        </script>
    </head>
    <body>
        
    </body>
</html>

get/set

prviate 속성을 사용하면 외부에서는 #length 속성에 아예 접근을 할 수 없는 문제가 발생한다.
이러한 문제를 해결하기 위해서 get/set을 메소드를 만들었다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            // 정사각형 만들기
            class Sqare {
                #length

                constructor (length){
                    this.setLength (length)
                }

                setLength (value){
                    if(value <= 0){
                        throw '길이는 0보다 큰 숫자여야 됩니다.'
                    }
                    this.#length = value
                }

                getLength (value){
                    return this.#length
                }

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

            // 클래스 사용하기
            const sqare = new Sqare(10)
            console.log(`둘레 ${sqare.getPerimeter()}`)
            console.log(`넓이 ${sqare.getArea()}`)

            sqare.setLength(-10)
        </script>
    </head>
    <body>
        
    </body>
</html>
  • get() : 속성 값을 확인할 때 사용하는 메소드를 게터라고 한다
  • set() : 속성 값을 지정할 때 사용하는 것을 세터라고 한다

처음 게터와 세터를 배우면 모든 private 속성에 게터와 세터르르 붙이려고 하는 경우가 있다.
하지만, 필요한 경우만 사용하면 된다.

아래의 코드는 get키워드와 set 키워드 조합을 한 코드이다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script>
            // 정사각형 만들기
            class Square {
                #length

                constructor (length){
                    this.setLength = length
                }

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

                get area() {
                    return this.#length * this.#length
                }

                set length (length){
                    if (length <= 0) {
                        throw '0 이상 숫자를 입력하세요'
                    } 
                    this.#length = length
                }
            }

            // 클래스 사용하기
            const SquareA = new Square(10)
            console.log(`한변길이 ${SquareA.length}`)
            console.log(`둘레 ${SquareA.getPerimeter}`)

            // const sqareB = new Sqare(-10)
        </script>
    </head>
    <body>
        
    </body>
</html>
profile
강인한 체력이 최고의 무기다.

0개의 댓글