6.4 객체지향 프로그래밍 응용 예제

vencott·2021년 6월 2일

6.4.1 클래스의 기능을 가진 subClass 함수

기존 클래스와 같은 기능을 하는 자바스크립트 함수를 만들어보자

함수의 이름은 subClass로 하며 다음 세 가지를 활용하여 구현한다

  1. 함수의 프로토타입 체인
  2. extend 함수
  3. 인스턴스를 생성할 때 생성자 호출(여기서는 생성자를 _init 함수로 정한다)

6.4.1.1 subClass 함수 구조

subClass는 상속받을 클래스에 넣을 변수 및 메서드가 담긴 객체를 인자로 받아 부모 함수를 상속받는 자식 클래스를 만든다

여기서 부모 함수는 subClass() 함수를 호출할 때 this 객체를 의미한다

var SuperClass = subClass(obj);
var SubClass = SuperClass.subClass(obj);

이처럼 SuperClass를 상속받는 SubClass를 만들고자 할 때, SuperClass.subClass()의 형식으로 호출하게 구현한다

참고로 최상위 클래스인 SuperClass는 자바스크립트의 Function을 상속받게 한다

function subClass(obj) {
  // 1. 자식 클래스(함수 객체) 생성
  // 2. 생성자 호출
  // 3. 프로토타입 체인을 활용한 상속 구현
  // 4. obj를 통해 들어온 변수 및 메서드를 자식 클래스에 추가
  // 5. 자식 함수 객체 반환
}

6.4.1.2 자식 클래스 생성 및 상속

function subClass(obj) {
  // ...
  
  var parent = this;
  var F = function() {
    
  };
  
  var child = function() {
    
  };
  
  F.prototype = parent.prototype;
  child.prototype = new F();
  child.prototype.constructor = child;
  child.parent = parent.prototype;
  child.parent_constructor = parent;
  
  // ...
  
  return child;
}

자식 클래스는 child 라는 이름의 함수 객체를 생성함으로써 만들어졌다

부모 클래스를 가리키는 parent는 this를 그대로 참조한다

프로토타입 체인 구성은 6.2.2 클래스 기반의 상속에서 설명된 방식을 그대로 사용하였다

하나 다른 점이 있다면 child.parent_prototype에 부모의 생성자를 참조시켰다는 점인데 이 부분은 6.4.1.4 생성자 호출에서 설명한다

이렇게 프로토타입 체인을 구성하여 부모를 상속받는 새로운 자식 클래스가 만들어지고 반환된다

6.4.1.3 자식 클래스 확장

이제 사용자가 인자로 넣은 객체를 자식 클래스에 넣어 자식 클래스를 확장시킨다

for (var i in obj) {
    if (obj.hasOwnProperty(i)) {        // hasOwnProperty() 함수는 인자로 넘기는 이름에 해당하는 프로퍼티가 객체 내에 있는지를 판단한다
        child.prototype[i] = obj[i];    // 다만, 프로토타입 쳉니을 타고 올라가지 않고 해당 객체 내에서만 찾는다
    } 
} 

6.2.1. 프로토타입을 이용한 상속에서 살펴본 extend() 함수의 역할을 하는 코드를 넣었다

여기서는 얕은 복사로 객체의 프로퍼티를 복사하는 방식을 택했다

6.4.1.4 생성자 호출

클래스의 인스턴스가 생성될 때, 클래스 내에 정의된 생성자가 호출돼야 한다

물론 부모클래스의 생성자 역시 호출되어야 한다

이를 자식 클래스 안에 구현하자

var child = function() {
  if (parent.hasOwnProperty("_init")) 
      parent._init.apply(this, arguments);
  
  if (child.prototype.hasOwnProperty("_init")) 
      child.prototype._init.apply(this, arguments);
}

위 코드는 문제가 없어 보이나 큰 문제가 있다

위 코드는 단순히 부모와 자식이 한 쌍을 이루었을 때만 제대로 동작한다

자식을 또 다른 함수가 다시 상속받았을 때는 어떻게 될 것인가?

var SuperClass = subClass();
var SubClass = SuperClass.subClass();
var Sub_SubClass = SubClass.subClass();

var instance = new Sub_SubClass();

이 코드에서 instance를 생성할 때 그 상위 클래스의 상위 클래스인 SuperClass의 생성자가 불리지 않는다

따라서 부모 클래스의 생성자를 호출하는 코드는 재귀적으로 구현할 필요가 있다

이미 child.parent_constructor 프로퍼티에 부모의 생성자 함수를 참조시켜 놨으므로 구현에 문제가 없다

var child = function() {
  var _parent = child.parent_constructor;
  
  if (_parent && _parent !== Function) // 현재 클래스의 부모 생성자가 있으면 그 함수를 호출한다. 다만 부모가 Function인 경우는 최상위 클래스에 도달했으므로 실행하지 않는다
      _parent.apply(this, arguments); // 부모 함수의 재귀적 호출
  
  if (child.prototype.hasOwnProperty("_init"))
      child.prototype._init.apply(this, arguments);
}

6.4.1.5 SubClass 보완

parent를 단순히 this로 지정하면 안된다

최상위 클래스가 window가 아닌 Function을 상속받도록 처리해야한다

// parent = this;

if (this === window)
    var parent = Function;
else 
    var parent = this;

// var parent = (this === window) ? Function : this;

또한 subClass 안에서 생성되는 자식 클래스의 역할을 하는 함수는 다시 누군가의 부모가 될 수 있으므로 subClass()를 가지고 있어야 한다

child.subClass = arguments.callee;

arguments.callee는 현재 호출된 함수를 의미하는데, 현재 호출된 함수가 subClass이므로 child.SubClass는 SubClass()를 참조한다

subClass 함수의 전체 코드는 다음과 같다

function subClass(obj) {
    var parent = (this === window) ? Function : this;
    var F = function () {
    };

    var child = function () {
        var _parent = child.parent;

        if (_parent && _parent !== Function)
            _parent.apply(this, arguments);

        if (child.prototype._init)
            child.prototype._init.apply(this, arguments);
    };

    F.prototype = parent.prototype;
    child.prototype = new F();
    child.prototype.constructor = child;
    child.parent = parent;
    child.subClass = arguments.callee;

    for (var i in obj) {
        if (obj.hasOwnProperty(i))
            child.prototype[i] = obj[i];
    }

    return child;
}

6.4.1.6 subClass 활용

다음은 subClass 함수를 활용한 상속 예제이다

var person_obj = {
    _init: function () {
        console.log("person init");
    },
    getName: function () {
        return this._name;
    },
    setName: function (name) {
        this._name = name;
    }
};

var student_obj = {
    _init: function () {
        console.log("student init")
    },
    getName: function () {
        return "Student Name : " + this._name;
    }
};

var Person = subClass(person_obj);
var person = new Person();
person.setName("zzoon");
console.log(person.getName());

var Student = Person.subClass(student_obj);
var student = new Student();
student.setName("iamhjoo");
console.log(student.getName());

console.log(Person.toString());

다음 사항들을 주의깊게 살펴보자

  1. 생성자 함수가 호출되는가?
  2. 부모의 메서드가 자식 인스턴스에서 호출되는가?
  3. 자식 클래스가 확장 가능한가?
  4. 최상위 클래스인 Person은 Function을 상속받는가?

6.3.1.7 subClass 함수에 클로저 적용

subClass 함수에서 프로토타입 체이닝을 위해 만든 임시 함수 객체 F가 계속 생성되는 것을 알 수 있다

이를 보완하기 위해 클로저를 적용한다

var subClass = function() {
  var F = function() {
    
  };
  
  var subClass = function(obj) {
    // ....
  }
  
  return subClass;
}();

즉시 실행 함수로 새로운 컨텍스트를 만들어서 F() 함수 객체를 생성하였다

그리고 이 F() 함수 객체를 참조하는 안쪽의 subClass() 함수를 반환받는다

이렇게 하면 F() 함수 객체는 클로저에 엮여서 가비지 컬렉션의 대상이 되지 않고, subClass() 함수를 호출할 때마다 사용된다

6.4.2 subClass 함수와 모듈 패턴을 이용한 객체지향 프로그래밍

6.3 캡슐화에서 잠시 언급되었던 모듈 패턴은 객체지향 프로그래밍과 큰 관계가 없어보인다

하지만 캡슐화의 중요한 개념인 정보를 은닉하려면 모듈 패턴은 상당히 유용하다

모듈 패턴으로 캡슐화를 구현하여, 앞 절에서 소개한 subClass() 함수로 상속을 구현하는 방법을 설명한다

var person = function(arg) {
  var name = undefined;
  
  return {
    _init: function(arg) {
        name = arg? arg : "zzoon";
    },
    getName: function() {
      return name;
    },
    setName: function(value) {
      name = value;
    }
  };
};

Person = subClass(person());
var iamhjoo = new Person("iamhjoo");
console.log(iamhjoo.getName());

Student = Person.subClass();
var student = new Student("student");
console.log(student.getName());

person 함수 객체는 name의 정보를 캡슐화시킨 객체를 반환받는 역할을 한다

이렇게 반환받은 객체는 subClass() 함수의 인자로 들어가 클래스의 역할을 하는 Person 함수 객체를 완성시킬 수 있다

이제 Person 함수 객체를 활용하여 상속을 구현할 수 있다

객체지향 프로그래밍을 마치며

지금까지 자바스크립트를 이용하여 객체지향적인 프로그래밍을 할 수 있는 여러 가지 방법을 살펴보았다

사실 자바스크립트의 객체지향적인 구현을 기존의 전통적인 방식에 맞추는 건 잘못된 판단이다

객체지향 프로그래밍 자체가 재사용성 및 유지보수의 용이성을 높이려고 끊임없이 연구되어왔다

자바스크립트 역시 이러한 목적을 달성하려는 자바스크립트만의 특성이 있으므로 이를 잘 활용하여 기존 틀에 얽매이지 않는다면 보다 효율적인 프로그래밍을 할 수있다

출처: 송형주, 고현준 저, 『인사이드 자바스크립트』, 한빛미디어(2014)

profile
Backend Developer

0개의 댓글