As i wish

[You don't know JS] - 클래스와 객체의 혼합 본문

JavaScript

[You don't know JS] - 클래스와 객체의 혼합

어면태 2019. 5. 21. 00:43

이번장에선 이론적인 설명이 많았습니다.

기존 객체지향 기반의 언어가 아닌 JS를 객체지향 언어처럼 쓰기 위한 노력들이 담겨 있었죠.

간단히 정리를 해보겠습니다.

 

1. 클래스 이론

클래스, 인스턴스에 대한 이론들은 사실 충분히 다른 포스팅에서 볼 수 있어요.

그렇기 때문에 크게 언급은 하지 않고 넘어가겠습니다.

간단히 말하면 제일 이해하기 쉬운게 붕어빵이죠. 붕어빵 틀을 클래스, 틀을 통해 나오는 붕어빵들을 인스턴스라고 생각하면 됩니다.

객체, 클래스, 인스턴스란...?

 

[Java 용어정리] Object(객체), Class(클래스), Instance(인스턴스) 란?

[Java 용어정리] Object, Class, Instance 란? object 객체, class 클래스, instance 인스턴스 에 대해...

blog.naver.com

1.1 클래스 디자인 패턴

사실 클래스는 디자인패턴의 종류 중 하나라고 명시되어있네요. 요즘 뜨는 함수형 프로그래밍을 경험해 보면 클래스는 코드를 짜는 디자인 패턴 중 하나라는 걸 알 수 있죠. 물론 JAVA 같은 언어들은 필수지만, C/C++는 기본적인 절차 지향과 객체 지향을 둘 다 제공하고 있죠.

1.2 자바스크립트 클래스

그럼 자바스크립트는 어떨까요? 사실 결론부터 말하면 클래스가 없고 클래스와 비슷하게 클래스 처럼 생긴 구문을 제공한다고 보는 게 맞아요. 그래서 인스턴스라기보다는 객체 복사가 조금 더 맞는 표현이지 않을까 싶습니다.

 

2. 클래스 체계

2.1 건축

클래스와 인스턴스를 건축 현장에 빗대어 생각하였는데요, 앞서 설명한것 처럼, 클래스는 틀, 인스턴스는 그 틀을 통해 나온 결과물이라고 보면 됩니다. 즉 클래스는 아키텍처 청사진, 인스턴스는 건물이라고 생각하면 됩니다.

2.2 생성자

인스턴스는 보통 클래스명과 같은 이름의 생성자라는 특별한 메서드로 생성됩니다. 생성자의 임무는 인스턴스에 필요한 정보를 초기화 하는 일이죠.

class coolGuy {
    specialTrick = nothing

    CoolGuy(trick) {
        specialTrick = trick
    }

    showOff() {
        output('This is my trick ', specialTrick);
    }
}

Joe = new CoolGuy('Magic')
Joe.showOff() // This is my trick Magic

위처럼 CoolGuy 클래스엔 생성자가 있어서 new CoolGuy를 하면 실제로 이 생성자가 호출됩니다. 생성자는 클래스에 속한 메서드로, 클래스명과 같게 명명하는 것이 일반적입니다.

 

3. 클래스 상속

상속 역시 객체 지향에 있는 개념인데, 부모 클래스로 부터 상속받은 자식 클래스는 부모 클래스에 메서드를 오버라이드 또는 그대로 사용할 수 있게 됩니다.

class Vehicle {
    engines = 1

    ignition() {
        output('Start engine');
    }

    drive() {
        ignition()
        output('Go drive');
    }
}

class Car inherits Vehicle {
    wheels = 4

    drive() {
        inherited:drive()
        output(wheels, 'are riding');
    }
}

class SpeedBoat inherits Vehicle {
    engines = 2

    ignition() {
        output(engines, 'are starting');
    }

    pilot() {
        inherited:drive()
        output('Go to the water')
    }
}

위 코드 처럼 Car과 SpeedBoat는 Vehicle 이란 객체를 상속받아서 사용합니다.

3.1 다형성

Car 는 Vehicle로부터 상속받은 drive() 메서드를 같은 명칭의 자체 메서드로 오버라이드 합니다. 그러나 이 메서드 안에서 inherited:drive() 호출은 Vehicle로부터 상속받아 오버라이드 하기 전의 원본을 참조하며, SpeedBoat의 pilot() 메서드 역시 상속받은 원본 drive()를 참조합니다. 이런 기법을 다형성이라고 하죠. 한 메서드가 상위 수준의 상속 체계에서 다른 메서드를 참조할 수 있게 해주는 아이디어이기도 합니다.

객체 지향에서 다형성이란, 서로 다른 객체가 동일한 메세지에 대하여 다르게 응답할 수 있는 기능이라고 정의되어 있네요.

상속과 다형성

 

[C++] 상속과 다형성 (Inheritance & Polymorphism)

우선 여기서는 상속과 다형성에 대한 간단한 의미만 정리 하고 넘어갈 것입니다. 다음에 상속과 다형성에 대해서 더 자세한 포스팅을 할 것입니다. 상속 (Inheritance) : 상속은 기존의 클래스를 토대로 해서 새로..

pacs.tistory.com

대부분 언어는 inherited 대신 super를 사용하여 부모 메서드에 접근을 하고 있죠. 다형성의 흥미로운 단면은 ignition() 메서드에 구체적으로 나타나 있는데요. pilot() 안에 다형적 레퍼런스가 Vehicle에서 상속된 drive()를 참조하고 있지만 drive()는 그냥 메서드 이름만 보고 ignition() 메서드를 참조합니다. 그래서 ignition()은 Vehicle이 아닌 SpeedBoat의 ignition()을 실행하게 되죠. 즉, 인스턴스가 어느 클래스를 참조하느냐에 따라 ignition() 메서드 정의는 다형적으로 되는 거죠.

3.2 다중 상속

사실 다중 상속은 상당히 복잡한데, 자바스크립트에서는 아예 기능을 제공하고 있지 않죠.

다중 상속이란.

 

다중 상속 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 클래스 상속 다이어그램의 다이아몬드. 다중상속(Multiple inheritance)이란 객체 지향 프로그래밍의 특징 중 하나이며, 어떤 클래스가 하나 이상의 상위 클래스로부터 여러 가지 행동이나 특징을 상속받을 수 있는 것을 말한다. 다중 상속을 지원하는 언어는 다음과 같다: C++, (CLOS을 거쳐) Common Lisp , (The EuLisp Object System TELOS을 거쳐) EuLisp, Curl,

ko.wikipedia.org

 

4. 믹스인

자바스크립트는 객체를 상속받거나 인스턴스화 해도 자동으로 복사 작업이 일어나지는 않습니다. 즉, 자바스크립트엔 인스턴스로 만들 '클래스'란 개념 자체가 없고 오직 객체만 존재하죠. 그리고 객체는 다른 객체에 복사되는 게 아니라 서로 연결만 됩니다. 

믹스인은 다른 언어와 달리 자바스크립트에선 누락된 클래스 복사 기능을 흉내 낸 거죠.

4.1 명시적 믹스인

function mixin(sourceObj, targetObj) {
    for (var key in sourceObj) {
        // 타깃에 없는 프로퍼티만 복사한다.
        if (!(key in targetObj)) {
            targetObj[key] = sourceObj[key];
        }
    }

    return targetObj
}

var Vehicle = {
    engines: 1,
    ignition: function() {
        console.log('Start engine');
    },
    drive: function() {
        this.ignition();
        console.log('Go');
    }
}

var Car = mixin(Vehicle, {
    wheels: 4,
    drive: function() {
        Vehicle.drive.call(this);
        console.log(this.wheels + 'are riding');
    }
})

Car는 Vehicle에서 복사한 프로퍼티와 함수 사본이 있습니다. 엄밀히 말하면 함수가 실제로 복사된 것은 아니고 원본 함수를 가리키는 레퍼런스만 복사된 거죠. 

Vehicle.drive.call(this)와 같은 코드를 책에서는 명시적 의사 다형성이라고 부릅니다. 앞의 예제에서 inhertied:drive()와 같은 부분이죠. 여기서 Vehicle.drive()로 함수를 호출하면 this는 Car가 아닌 Vehicle 객체에 바인딩되기 때문에 불가피하게. call(this)를 사용하여 Car객체의 콘텍스트로 실행하도록 하였죠.

자바스크립트는 다형적 레퍼런스가 필요한 함수마다 위와 같이 명시적 의사 다형성 방식의 취약한 연결을 명시적으로 일일이 만들어 줄 수밖에 없습니다. 따라서 유지 비용이 훨씬 들고 다중 상속 작동 방식을 모방할 수 있어 복잡도와 취약성은 한층 가중됩니다. 따라서 더 복잡하고, 읽기 어려운 코드가 되죠.

또한 명시적 믹스인은 쓸만하긴 하지만 실제로 어떤 객체에서 다른 객체로 프로퍼티를 복사하느니 차라리 그냥 무식하게 똑같은 프로퍼티를 각각 두 번씩 정의하는 게 더 낫다고 말하고 있죠. 자바스크립트에서 복사는 실제 복사가 아니라 레퍼런스만 복사하는 것이니까요.

4.2 암시적 믹스인

var Something = {
    cool: function() {
        this.greeting = 'Hello',
        this.count = thie.count ? this.count + 1 : 1
    }
}

Something.cool();
Something.greeting; // Hello
Something.count // 1

var Another = {
    cool: function() {
        // Something을 암시적으로 Another로 믹스인한다.
        Something.cool.call(this);
    }
}

Another.cool();
Another.greeting // Hello
Another.count // 1

가장 일반적인 생성자 호출 또는 메서드 호출 시 Something.cool.call(this)를 하면 Something.coll() 함수를 본질적으로 빌려와서 Another 콘텍스트로 호출한다.(this 바인딩 참조) 결국, something.cool()의 할당은 Something이 아닌 Another이다. 따라서 Something의 작동을 Another와 섞은 셈이 됩니다.

this 재 바인딩을 십분 활용한 이런 유형의 테크닉은 Something.cool.call(this) 같은 호출이 상대적 레퍼런스가 되지 않아 불안정하므로 사용할 때 신중이 해야 합니다. 또한 코드가 깔끔하지 않고 관리하기 쉽지 않아 쓰지 않는 편이 좋죠.

 

결론

- 클래스는 디자인 패턴의 일종이고, 자바스크립트에서 클래스의 의미는 다른 언어들과는 다릅니다.

- 클래스는 복사를 의미하고 전통적인 클래스는 인스턴스와 하면 복사가 일어납니다. 상속 역시 부모 -> 자식 방향으로 복사가 일어납니다.

- 자바스크립트는 객체 간 사본을 자동으로 생성하지 않습니다. 그래서 믹스인 패턴은 클래스의 복사 기능을 모방하기 위해 쓰이지만 대부분 쓰지 않습니다.

- 일반적으로 자바스크립트에서 클래스를 모방하는 건 당장 닥친 문제를 해결할 순 있어도 앞으로 터질 시한폭탄을 심어 놓는 것과 다름없다.

- 코드를 객체지향 적으로 짜려고 노력하다 보니 자바스크립트도 이에 맞게 변화한 건 맞지만 굳이 복잡하게 사용할 필요는 없을 것 같습니다.

Comments