본문 바로가기

Development/Javascript

펌) 코어자바스크립트(1) - OOP

본문링크 : http://youngman.kr/?p=457


개요

아직도 많은 개발자들은 자바스크립트를 단순히 클라이언트 상의 액션을 실행하기 위한 함수 기반의 스크립트 언어라고 생각하고 있다. 하지만 자바스크립트는 객체 지향 언어이다. 비록 자바나 C#처럼 완벽하지는 않지만 기본적인 클래스 개념과 상속, 은닉 등을 제공한다. 이번 장에서는 자바스크립트의 OOP 요소들을 하나하나 예제로 풀어가며 알아보도록 할 것이다.

클래스 정의

function MyClass() {
}

var MyClass = function() {
}
// -- 위의 두 구문은 동일하게 MyClass 클래스를 선언한다.
// -- 필자는 가독성 측면에서 두 번째 방식을 선호한다

(1) 접근 제어자

자바스크립트는 클래스 내부에서 정의할 수 있는 두 가지의 접근 제어자를 제공한다.

var Student = function(id, name) {
     var id = id;
     this.name = name;
}

var my = new Student("Seogi1004", "Moon-Hak-I");
alert(my.id + ", " + my.name);

위의 소스 코드의 출력 메시지는 ’undefined, Moon-Hak-I’이다. 클래스 내부에 선언된 변수의 지정자가 ‘var’일 경우에는 해당 클래스 내부에서만 접근이 가능하며 자바의 ‘private’와 같다라고 생각하면 된다. 그리고 클래스 외부에서도 사용하기 위해서는 ’ this 키워드’를 사용하면 된다.

위에서 보여진 변수 접근 제어자는 메소드에도 동일하게 적용된다. 

var People = function(name, age) {
     var name = name;
     var age = age;

     var isAdult = function() {
          if(age > 19) return true;
          else return false;
     }

     this.viewMyInfo = function() {
          if(isAdult()) {
               return name + "님은 성인이 맞습니다.";
          } else {
               return name + "님은 성인이 아닙니다.";
          }
     }
}

var people = new People("Moon-Hak-I", 26);
alert(people.isAdult());          // Uncaught TypeError: Object [object Object] has no method 'isAdult'
alert(people.viewMyInfo());

위의 소스 코드에서 22라인 ’people.isAdult()’ 부분을 주의깊게 볼 필요성이 있다. var 키워드를 사용하여 정의된 메소드는 외부에서 호출되지 못한다. 즉, People 클래스에서 외부에서 직접적으로 사용할 수 있는 메소드는 ’viewMyInfo’뿐이다.

OOP에서 이런 개념을 캡슐화라고 하는데 클래스 내부의 연산 과정을 숨김으로써 보다 독릭접인 코드 구성을 꾀할 수 있고, 예기치 못한 접근을 차단하여 무분별한 함수 사용을 미연에 방지할 수 있다. 

아래 소스 코드는 일반적으로 사용되는 함수 정의 방법이며 기능은 위의 예제와 동일하다. 

unction isAdult(age) {
     if(age > 19) return true;
     else return false;
}

function viewMyInfo(name, age) {
     if(isAdult(age)) {
          return name + "님은 성인이 맞습니다.";
     } else {
          return name + "님은 성인이 아닙니다.";
     }
}

alert(isAdult(26));
alert(viewMyInfo("Moon-Hak-I", 26)); 


첫번째 예제와 달리 ’isAdult 함수’가 정상적으로 호출 된다. 실제로 대다수의 웹 개발자들이 위와 같은 형태로 함수 정의를 하는데 이러한 방식은 많은 문제점을 발생시킨다. 요약하자면 아래와 같다.


1. 해당 페이지에 포함되는 자바스크립트가 많을 경우 동일한 함수가 재정의 될 가능성이 있다. 스크립트 언어 특성상 마지막에 정의된 함수를 사용하게 된다. (기능이 전혀 다른 함수일 경우, 매우 심각한 문제)

2. 하나의 기능을 수행하고자 다수의 비즈니스 로직 처리 함수를 사용한다면 가독성에 문제가 생길 수 밖에 없다. 위의 예제와 같은 함수 정의 방식은 지극히 수평적이고 함수와 함수 간의 관계를 증명할 방법이 없다. (기껏해야 주석 또는 네이밍 규칙)

 

(2) 정적 클래스 

자바스크립트에서 정적 클래스는 별도로 인스턴스를 생성하지 않으며 선언과 동시에 정의가 된다. 정적 클래스는 일반적으로 유틸리티 형태의 기능 구현을 할 때 주로 사용된다.

아래 소스 코드는 일반 클래스와 정적 클래스의 메소드를 사용하는 예제이다.

var MyClass = function() {
     this.show = function() {
          return "MyClass";
     }
}
var MyStaticClass = new function() {
     this.show = function() {
          return "MyStaticClass";
     }
}

var cls = new MyClass();
alert(cls.show());

// -- 별도의 인스턴스 생성을 하지 않는다.
alert(MyStaticClass.show());


심화

자바스크립트에서 변수(or 프로퍼티)와 함수(or 메소드)를 접근하는 방법은 크게 외부에서 가능한지 아닌지로 나눌 수 있다. 일반적으로 ‘public’이라고 불리우는 메소드는 클래스 안의 ‘private’으로 정의된 요소들을 접근할 수 있지만 자바스크립트에서는 불가능하다. 외부에서도 접근이 가능하고 클래스 안에 ‘private’으로 정의된 요소들을 접근할 수 있는 메소드를 자바스크립트에서는 ‘public’이 아닌 ‘privileged’라고 한다. 

그럼, 지금부터 아래 예제를 통해 각각의 유형에 대해서 자세히 알아보도록 하자.

function Person(firstName, lastName) {
     // 객체 생성시 증가
     this.constructor.population++;

     // **********************************************************************
     // PRIVATE VARIABLES AND FUNCTIONS
     // 'PRIVELEGED METHODS'만 읽기/쓰기/수정 가능
     // **********************************************************************
     var alive = true;
     function getFullName() {
          return lastName + ", " + firstName;
     }

     // **********************************************************************
     // PRIVILEGED METHODS
     // 공개적으로 호출할 수 있으며, 'PRIVATE' 항목에 유일하게 접근 가능
     // 하지만 수정할 수 없고, 'PUBLIC'과 대체 가능
     // **********************************************************************
     this.toString = function(){
          return getFullName();
     };

     this.isAlive = function()  {
          return alive;
     };

     // **********************************************************************
     // PUBLIC PROPERTIES -- 누구나 읽기/쓰기 가능
     // **********************************************************************
     this.age = 1;
};

// *************************************************************************
// PUBLIC METHODS -- 누구나 읽기/쓰기 가능
// *************************************************************************
Person.prototype.addAge = function() {
     this.age++;
};

// *************************************************************************
// PROTOTYOPE PROERTIES -- 누구나 읽기/쓰기 가능 (but may be overridden)
// *************************************************************************
// Person.prototype.age = 0;

// *************************************************************************
// STATIC PROPERTIES -- 누구나 읽기/쓰기 가능
// *************************************************************************
Person.population = 0;

// *************************************************************************
// STATIC METHODS -- 누구나 읽기/쓰기 가능
// *************************************************************************
Person.showTotalPopulation =  function (msg) { 
     alert(msg +  " : "  + Person.population);
} 


var personList = [ new Person('Jae-Seok', 'Hong'), new Person('Ji-Sung', 'Jung') ];

$("#result")
    .append("'" + personList[0] + "'과 '" + personList[1] + "'이 태어났습니다.
") .append("현재 인구는 " + Person.population + "명 입니다.

"); for(var i = 0; i < 25; i++) { personList[0].addAge(); personList[1].addAge(); } $("#result") .append("'" + personList[0] + "'은 " + personList[0].age + "살이 되었습니다.
"); Person.showTotalPopulation("총 인구");


(1) 변수 (or 프로퍼티)

private 변수는 앞서 설명한 것처럼 클래스 안에서 ‘var’ 키워드를 사용하여 선언되며, 오직 ‘private/privileged 함수’에서만 접근할 수 있다.

public 프로퍼티는 클래스 안에서 ‘this.propertyName’ 형태로 선언되며 외부에서도 읽기/쓰기가 가능하다.

prototype 프로퍼티는 클래스 외부에서 정의되며 ‘Classname.prototype.propertyName = someValue’ 형태로 사용하지만 동일한 이름의 public 프로퍼티가 존재하지 않을 경우에만 정의할 수 있다. (예제의 46라인의 주석을 해제하여 실행을 하여도 결과 값은 동일함)

static 프로퍼티는 ‘Classname.propertyName’ 형태로 정의되며 인스턴스의 생성과는 별개의 영역에 존재한다. (예제의 3라인처럼 클래스 내부적으로 static 프로퍼티는 ‘this.constructor.prototyeName’ 형태로 접근할 수 있음)
 

(2) 함수 (or 메소드)

private 함수는 클래스 내부적으로 ‘function functionName() {…} or var functionName = function() {…}’와 같은 형태로 선언되며 생성자와 privileged 메소드를 통해서만 호출될 수 있다.

privileged 메소드는 ‘this.methodName = function() {…}’와 같은 형태로 선언되며 클래스 외부에서도 실행할 수 있다. 하지만 외부에서는 ‘prototype’을 이용한 재정의를 할 수가 없다.

public 메소드는 클래스 외부에서 ‘Classname.prototype.methodName = function() {…}’와 같이 자유롭게 정의할 수 있지만 클래스 내부에 정의된 ‘private’ 요소들은 접근할 수 없다. 또한 prototype 프로퍼티와 마찬가지로 클래스 내부에 동일한 이름의 privileged 메소드가 존재하지 않을 경우에만 정의할 수 있다. 

static 메소드는 ‘Classname.methodName’ or ‘Functionname.methodName’ 형태로 정의되며 인스턴스 생성과는 별개의 영역에 존재한다. 자주 사용되는 static 메소드는 대표적으로 call/apply 메소드가 있다. (static 프로퍼티와 마찬가지로 클래스 내부적으로 ‘this.constructor.methodName’ 형태로 접근할 수 있음)
 

(3) 참고 사항

변수와 프로퍼티(or 함수/메소드)는 비슷한 의미를 가지고 있지만 결정적인 차이가 있다. 먼저 변수와 함수는 클래스 내부에서만 존재하며 선언적 의미를 가지고 있다. 프로퍼티와 메소드는 정의되는 것이라 할 수 있는데, 여기서 말하는 정의란 클래스가 구체화되어 인스턴스가 생성되는 것을 말한다. 이때 프로퍼티와 메소드는 생성된 인스턴스를 통해 접근할 수 있다.