클래스의 상속 (Inheritance)

Rex·2022년 2월 12일
0

인생 프로그래밍

목록 보기
27/33

본 글의 저작권과 원문은 https://blog.naver.com/sweetie_rex 에 있습니다.
현재의 글은 업데이트가 되지 않음을 유의해서 읽어주세요.
최신 업데이트 된 글을 읽으시려면 아래의 링크를 확인해주세요.

클래스의 상속 (Inheritance)

우리는 프로그래밍을 하면서 결코 이것을 잊어서는 안돼.
프로그래밍은 인생과 닮아있다는 사실을.

상속(Inheritance) 이란?

상속이라는 것은 말 그대로 부모님이 자식에게 물려주는 것과 같은 거야.
프로그래밍에서 말하는 상속은 class 에 대한 property 와 method 를 물려주는 것을 말하지.
예를들어 학생(Student) 라는 class 가 있다고 생각해보자. 모든 학생은 기본적으로 인간(Human)이지. 그렇다면 StudentHuman 의 특성을 그대로 상속한 class 라고 할 수 있지.
그리고 모든 인간(Human)은 동물(Animal)이기 때문에 Human class 는 Animal class 를 상속할 수 있을거야.
이것을 "is-a" 관계라고 해.
"Student is a Human"
"Human is a Animal" 이라는 관계가 성립된다는 의미야.

Student class 에는 점수(score) 라는 property 와 공부하기(study) 라는 method 가 있고,
Human class 에는 나이(age) property 와 인사하기(hello) 라는 method 가 있으며,
Animal class 에는 수명(life) property 와 호흡하기(breath) 라는 method 가 있다고 생각해보자.

이것을 코드로 구현해보면 다음과 같아.
같이 코딩해보자!

class Animal(object):  # 모든 클래스는 object 라는 최상위 클래스를 상속하게 됨
    life = 0

    def __init__(self, life):
        super().__init__()  # 상속받은 상위(super) 클래스의 생성자 호출
        self.life = life

    def breath(self):
        self.life += 1
        print(f"현재 life: {self.life}")


class Human(Animal):
    name = None
    age = None

    def __init__(self, name, age):
        super().__init__(100)
        self.name = name
        self.age = age

    def hello(self):
        print(f"저는 {self.name}에요. 나이는 {self.age}살이에요. 반가워요.")


class Student(Human):  # Human 의 특성(properties, methods)을 그대로 물려받은 Student class
    score = None

    def __init__(self, name, age, score):
        super().__init__(name, age)
        self.score = score

    def study(self):
        print(f"제 점수는 {self.score}점이며, 지금은 공부중이에요.")


student = Student('철수', 17, 90)  # Student class 인스턴스 생성
student.breath()  # Student 는 곧 Animal 이기 때문에 breath method 실행 가능
student.hello()  # Student 는 곧 Human 이기 때문에 hello method 실행 가능
student.study()  # Student 의 method 실행

human = Human('영희', 21)  # Human class 인스턴스 생성
human.breath()
human.hello()
human.study()  # 오류 발생! Human 은 Student 가 아니기 때문에 사용 불가!

위의 코드를 실행시키면 아래의 결과처럼 마지막 라인의 코드에서 오류가 발생할거야.

당연히 오류가 발생할 수 밖에 없어.
StudentHuman 으로부터 properties 와 methods 를 상속받았기 때문에 StudentHuman 의 모든 것을 사용할 수 있지만, Human 입장에서는 Student 와 아무런 관련이 없기 때문에 Student 의 method 인 study() 를 사용할 수 없기 때문이지.
이것이 바로 상속의 기능이야.

참고로, python 의 모든 class 는 object 를 기본적으로 상속하도록 되어있어. object 라는 녀석은 파이썬 세계의 최고 조상(root)이야. JavaScript 에서도, Java 에서도 Object 라는 녀석이 있는데, 모두 객체지향 세계에서 모든 class 의 최고 조상님이지. 이를 최상위 클래스 라고 해. 클래스 계층(hierarchy)의 최상위에 있는 녀석이지. object 가 싫다해도 상속을 거부할 수는 없어.
class Human(object): 라고 명시적으로 써도 되고,
class Human(): 처럼 object 를 생략해도 되고,
class Human: 처럼 괄호까지 생략해도 돼. 이 3가지는 모두 object 를 상속하는 똑같은 코드야.
보통은 object 외에 별도의 class 를 상속받는게 아니라면 괄호까지도 다 생략하는 편이야.

그렇다면 상속을 사용하면 어떤 장점/단점이 있는지 알아보자.

상속의 장점

1. 코드 재사용

상속을 사용하면 중복되는 코드를 줄이고, 코드를 재사용할 수 있어. Human class 를 하나 잘 설계해놓으면 Human 에서 파생되는 수많은 클래스들, 예를들면 Student, Teacher, Friend, Baby 등의 class 를 만들때 아주 유용하게 사용할 수 있지!
그래서 소스코드가 간결해지고, 유지-보수가 쉬워져.

"is-a" 관계와 비슷한 개념으로 "has-a" 관계도 있어.
"Car has a Wheel" (자동차에는 바퀴가 있다)
"Bike has a Wheel" (자전거에는 바퀴가 있다)
이 경우에도 Car, Bike class 는 Wheel class 를 상속함으로서 코드량을 줄이거나 코드의 생산성을 높일 수도 있어.
이를 합성(Composition) 이라고도 해.
이를 두고 상속(Inheritance)이 더 좋냐, 합성(Composition)이 더 좋냐 왈가왈부 하기도 하는데, 프로그래밍은 인생과 같이 어떤게 늘 옳거나 어떤게 늘 틀린 것이 없어. 중요한 것은 기술에 대한 이해와 활용이야.

2. 타입 다형성(Polymorphism)

다형성이라는 말이 굉장히 생소할텐데, '다양한 형태를 가질 수 있는 성질' 이라고 생각하면 될 것 같아.
다형성은 OOP에서 중요한 개념인데 간단하게 말하면, 하나의 상위(super) Type 을 가지고 그의 하위(child) Type 들을 자유롭게 사용할 수 있는 성질을 말해.
다형성은 Python 과 JavaScript 에는 그다지 해당사항이 없고, Java 또는 TypeScript(JavaScript 에 Type 기능을 추가한 superset 언어)등의 언어에 적용되는 내용이라 이 책에서 자세히 다루지는 않을거야.
지금은 몰라도 상관없는 내용이니까 자세히 설명하지 않고 넘어가도록 할게. ;-)

여튼 이렇게 장점들이 있지만 아쉽게도, 세상의 모든 것이 그렇듯 장점이 존재한다면 단점도 있어.

상속의 단점

코드의 결합도가 증가한다.

앞으로 개발 공부를 계속 하다보면 언젠가 배우겠지만, 코딩을 할때 중요한 개념이 2가지가 있어.
'결합도(Coupling)''응집도(Cohension)' 라는 개념인데, 결론부터 말하자면 "결합도가 낮으면서, 응집도가 높은 코드" 가 좋은 코드야.

결합도(Coupling) 는 코드(모듈)들간에 얼마나 상호 의존적인지, 얼마나 연관성이 높은지에 대한 정도를 말하는 것이고,
응집도(Cohension) 는 코드(모듈)가 얼마나 특정 기능을 위해서만 집중되어 작성되었는지에 대한 정도를 말하는거야.

결합도가 높은 코드 라는 것은, 코드들끼리 너무 얽히고 섥혀있어서, 뭐 하나를 수정하면 다른곳에서 에러가 발생하고, 그곳의 에러를 수정하면 또 다른곳에서 새로운 에러가 발생하는.. 그런 무시무시한 상황이 전개될 수 있다는 리스크가 있고,
결합도가 낮은 코드 는 코드들간에 상당히 독립적이어서 뭔가를 수정했을때 다른곳에 영향을 미치지 않아서 유지보수가 상대적으로 쉬운 구조를 말해.

응집도가 낮은 코드 는 하나의 기능(function) 을 구현하는데, 핵심기능이 아닌 부수적인것까지 함께 작성되어 있는 형태를 말하고,
응집도가 높은 코드 는 코드의 구성이 하나의 기능에만 매우 충실한 형태를 말해.

코드의 결합도가 증가한다는 것이 상속의 가장 큰 단점이야.
하지만 그럼에도 불구하고 상속을 적절하게 잘 사용했을 경우 장점이 매우 뛰어나고 훌륭한 코드를 만들어낼 수 있기 때문에, 단점은 최대한 극복해 내면서 장점을 극대화 시키는 것이 개발자의 의무라고 할 수 있지.

JavaScript 전체 코드

class Animal extends Object {  // 모든 클래스는 Object 라는 최상위 클래스를 상속하게 됨
    constructor(life) {
        super();  // 상속받은 상위(super) 클래스의 생성자 호출
        this.life = life;
    }

    breath() {
        this.life += 1;
        console.log(`현재 life: ${this.life}`);
    }
}

class Human extends Animal {
    constructor(name, age) {
        super(100);
        this.name = name;
        this.age = age;
    }

    hello() {
        console.log(`저는 ${this.name}에요. 나이는 ${this.age}살이에요. 반가워요.`);
    }
}

class Student extends Human {  // Human 의 특성(properties, methods)을 그대로 물려받은 Student class
    constructor(name, age, score) {
        super(name, age);
        this.score = score;
    }

    study() {
        console.log(`제 점수는 ${this.score}점이며, 지금은 공부중이에요.`);
    }
}


const student = new Student('철수', 17, 90);  // Student class 인스턴스 생성
student.breath();  // Student 는 곧 Animal 이기 때문에 breath method 실행 가능
student.hello();  // Student 는 곧 Human 이기 때문에 hello method 실행 가능
student.study();  // Student 의 method 실행

const human = new Human('영희', 21);  // Human class 인스턴스 생성
human.breath();
human.hello();
human.study();  // 오류 발생! Human 은 Student 가 아니기 때문에 사용 불가!

Java 전체 코드

이 코드에서 Java 가 Python, JavaScript 와는 다르게 실행되는 모습을 볼 수 있어.
Python 과 JavaScript 는 '스크립트 언어' 이고, Java 는 '컴파일 언어' 라는 차이가 있거든.

이 책의 초반부에 Java 를 소개하면서 장점에 기재했던 내용이 여기에서 나오는데,
human.study() 라는 문법적으로 오류가 있는 코드를 실행 할 때, 스크립트 언어는 일단 위에서부터 순차적으로 실행을 하고, 오류가 발생하면 프로그램이 종료되는데, 컴파일 언어는 처음부터 모든 문법적인 오류를 먼저 검사하고, 오류가 발견되면 아예 실행 자체를 하지 않아.

그래서 아래 코드에는 정상적인 실행을 위해, 오류를 발생시키는 human.study(); 라인을 주석처리 했어.

public class Main {
    public static void main(String[] args) {
        Student student = new Student("철수", 17, 90);  // Student class 인스턴스 생성
        student.breath();  // Student 는 곧 Animal 이기 때문에 breath method 실행 가능
        student.hello();  // Student 는 곧 Human 이기 때문에 hello method 실행 가능
        student.study();  // Student 의 method 실행

        Human human = new Human("영희", 21);  // Human class 인스턴스 생성
        human.breath();
        human.hello();
        // human.study();  // 오류 발생! Human 은 Student 가 아니기 때문에 사용 불가!
    }
}

class Animal extends Object {  // 모든 클래스는 Object 라는 최상위 클래스를 상속하게 됨
    public int life = 0;

    public Animal(int life) {
        super();  // 상속받은 상위(super) 클래스의 생성자 호출
        this.life = life;
    }

    public void breath() {
        this.life += 1;
        System.out.println("현재 life: " + this.life);
    }
}

class Human extends Animal {
    public String name = "";
    public int age = 0;

    public Human(String name, int age) {
        super(100);
        this.name = name;
        this.age = age;
    }

    public void hello() {
        System.out.println("저는 " + this.name + "에요. 나이는 " + this.age + "살이에요. 반가워요.");
    }
}

class Student extends Human {  // Human 의 특성(properties, methods)을 그대로 물려받은 Student class
    public float score = 0;

    public Student(String name, int age, float score) {
        super(name, age);
        this.score = score;
    }

    public void study() {
        System.out.println("제 점수는 " + this.score + "점이며, 지금은 공부중이에요.");
    }
}
profile
🔥 from Abstraction to Realization

0개의 댓글