본문 바로가기
Development

[21.01.29] YDKJSY - Mixing (Up) "Class" Objects

by igy95 2024. 1. 7.

OOP의 기본적인 속성

  • 클래스 지향적
  • 인스턴스화
  • 상속화
  • 다형성

Class Theory

OOP 혹은 클래스 지향 프로그래밍은 본질적으로 실생활의 문제를 정의하고 해결하기 위해 등장한 방법이다. 때문에 이 지론에서는 어떠한 데이터를 적절히 운용하기 위해 그에 밀접하게 관련된 동작들을 가지고 있어야 한다고 얘기한다. 해당 프로그래밍 기법에 따른 가장 이상적인 디자인 패턴은 데이터와 동작들을 함께 패키징하여 하나의 구조 안에 넣는 방법인데, 이 때 패키징하는 과정을 '캡슐화', 데이터들이 들어가는 구조를 '자료 구조'라고 정의할 수 있다.

JavaScript "Classes"

JS는 클래스 지향 언어에서 쓰이는 몇몇 문법을 사용하고 있긴 하지만, 실제적으로 클래스를 가지고 있는 것은 아니다. 클래스는 하나의 '설계 패턴'이라서, JS 내부의 기능만으로 정통적인 클래스의 근사치를 구현할 수 있기 때문이다. 그래서 JS는 클래스'처럼' 보일 수 있는 몇몇 문법을 지원함으로써 이에 대한 욕구를 충족하고 있다. 결국 클래스 디자인 패턴은 JS 내에서 필수로 행해야 하는 것이 아닌, 하나의 선택지라고 볼 수 있다.

Class Mechanics

클래스는 추상화를 위한 틀로 작용할 수 있기 때문에 실질적인 작업물에 대한 설계도, 청사진으로 이해할 수 있다. 그리고 이러한 틀에서 구체화된 것이 객체이고, 이러한 과정을 '인스턴스화'라고 칭하기 때문에 클래스로부터 생성된 객체를 인스턴스라고 부르기도 한다. 둘 사이의 관계성을 고려할 때 , 인스턴스는 클래스에서 정의된 모든 특성에 대한 복사본으로 볼 수 있다.

Constructor

인스턴스는 클래스의 특정한 메소드를 통해 생성되는데, 이 생성을 담당하는 메소드를 '생성자'라고 한다. 이 메소드는 인스턴스가 필요한 모든 정보(state)를 초기화하는 일을 담당한다. 클래스에 속해있는 생성자는 거의 일반적으로 클래스와 같은 이름으로 쓰이고 (현재는 constructor() 함수가 있음), new 키워드와 함께 호출되는데, 이 키워드를 통해 JS 엔진에게 우리가 새로운 인스턴스를 필요로 한다는 것을 알려줄 수 있기 때문이다.

Class Inheritance

Polymorphism

  • 관계성: 어떠한 메소드가 고수준의 상속 계급 내에서 다른 메소드를 참조할 수 있는 것
  • 다형성: 하나의 메소드가 상속 계급 내에서 다수의 정의를 가질 수 있는 것

다형성의 관점에서 발견할 수 있는 흥미로운 양상은 다음과 같다.

class Vehicle {
    engines = 1;

    ignition() {
        console.log( "Turning on my engine." );
    }

    drive() {
        this.ignition();
        console.log( "Steering and moving forward!" );
    }
}

class SpeedBoat extends Vehicle {
    engines = 2;

    ignition() {
        console.log( "Turning on my ", this.engines, " engines." );
    }

    pilot() {
        super.drive();
        console.log( "Speeding through the water with ease!" );
    }
}

let boat = new SpeedBoat();

Vehicle 의 특징을 상속받은 SpeedBoat 의 인스턴스를 만들었을 때, 그 인스턴스 boat에서 pilot() 을 호출하게 되면 부모 클래스의 drive() 를 연이어 호출하게 된다. 그렇다면 과연 drive() 내의 ignition() 을 호출했을 때 부모와 자식 클래스 중 어느 메소드를 참조하게 될까?

 

답은 SpeedBoat, 자식 클래스이다. 위에서 언급했다시피 상속 계급 내에서는 관계성을 기반으로 관계적 참조를 할 수 있게 되는데, 그것을 자식 클래스에서 부모 클래스의 메소드를 호출하는 것과 관련이 있다. 하지만 단순한 메소드 호출은 그 이름만을 참조하는 것이기 때문에 엔진은 인스턴스가 어느 클래스에서 생성되었는지에 따라 판가름하게 된다. 이는 곧 boat에서는 SpeedBoat 내부의 ignition() 메소드 유무에 상관없이 drive() 에서 호출한 해당 메소드가 Vehicle 에는 접근하지 못한다는 것을 의미한다. (this의 관점으로 생각해도 그렇게 될 수밖에 없다.)

 

이러한 부분에서 적지 않은 혼란을 초래하게 되는데, 이는 대개 자식 클래스가 부모 클래스로부터 상속을 받기 때문에 두 클래스 간에 어떤 링크가 존재하고 자식 클래스가 부모 클래스의 메소드에 '직접' 접근할 수 있다고 생각하는 데에서 발생한다. 하지만 상속을 받은 자식 클래스는 부모 클래스에 속해 있는 메소드의 copy 를 가지고 있을 뿐, override가 발생한다 하여도 기존의 메소드와 새로운 메소드를 모두 유지하고 있기에 메소드의 호출은 해당 인스턴스의 클래스 내부에서만 운용된다. 간추리자면, 상속 = 복사의 개념으로 생각해볼 수 있다.

Multiple Inheritance

'사실 상속의 개념에 빗대어 각 클래스의 관계를 부모-자식 으로 정의한다면, 본질적으로 자식은 두 부모의 특성을 물려받아야하는 것이 아닐까?' 라는 생각에서 착안된 매커니즘이다. 다수의 클래스에서 상속을 받을 수 있다면 어떻게 될까? 긍정적인 측면으로 보았을 때, 조금 더 복합적인 기능들을 클래스로 묶어 필요한 부분을 상속받을 수 있으니 효율성을 기대해볼 수 있다.

 

하지만 JS에서는 이런 매커니즘을 제공하지 않고 부정적인 입장에 속해있다. 그 이유는 상속받으려는 클래스들 내부에서 동일한 이름의 메소드가 발견 되었을 때의 그 처리가 애매하기 때문이다. 이러한 경우가 발생했을 때, 매번 일일이 어느 메소드를 참조하라는 부가 명령이 필요하다면, 쓸데없이 코드가 길어질 우려가 있고 가독성도 떨어질 수 있다.

Review

  • 클래스는 하나의 '설계 패턴'이다.
  • 다른 언어들처럼 JS에서도 클래스 지향 언어처럼 보이는 문법을 지원하지만 실상 그 작동 방식은 확연히 다르다.
  • class = copy
  • JS에서 이루어지는 인스턴스화, 상속 모두 카피에 기반한다.
  • 다형성은 부모-자식 클래스간 관계적 링크를 통한 참조가 아니다. 단지 copy의 결과일 뿐이다.
  • 하지만 그렇다고 JS가 자동적으로 객체 사이에서 copy를 생성하는 것은 아니다.
  • mixin pattern 은 주로 JS class 내에서 이루어지는 copy를 모방하기 위해 사용되고는 하지만 가독성도 떨어지고 불안정하다. 특히 explicit mixin 은 기존의 class copy 처럼 객체 자체를 복사하는 것이 아니라 해당 객체에 대한 참조를 복사하는 것이기 때문에 이는 미래의 유지보수에 있어 위험성을 초래할 가능성이 있다.