티스토리 뷰

hacking/web

prototype사용자를 위한 dojo 입문 (5)

Ho Eyo He Hum! iolo 2007.11.24 18:08
제 5 장 OOP 지원

javascript는 prototype 기반 언어이다. 가장 널리 쓰이고 있는(제목에도 포함된) prototype 라이브러리의 이름이 여기에서 유래된 것이다.

문제는 이 prototype 기반 언어가 대부분의 개발자가 익숙한 클래스 기반 언어와 미묘하게(?) 다르다는 것인데... 그래서 대부분의 자바스크립트 라이브러리들이 이 차이점을 완화하기 위한 도구들을 제공하고 있다.

모질라 홈페이지의 문서에 prototype과 dojo의 예를 추가해보자. 설명은 없고 영양가없는 소스만 길게 늘어놓았는데... 중간 중간 색깔과 코멘트를 넣어두었다. 만들려는 녀석은 대충 이런 식이다:

사용자 삽입 이미지

* java

public class Employee {
    public String name;
    public String dept;
    public Employee(String name, String dept) {
        this.name = (name != null) ? name : ""
        this.dept = (dept != null) ? dept : "general";
    }
    pubic String toString() {
        return "name:" + name + ",dept:" + dept;
    }
}

public class Manager extends Employee {
    public Employee[] reports;
    public Manager(String name, String dept, Employee[] reports) {
        super(name, dept);
        this.reports = (reports != null) ? reports : new Employee[0];
    }
    pubic String toString() {
        return super.toString() + ",reports=" + reports;
    }
}

public class WorkerBee extends Employee {
    public String[] projects;
    public WorkerBee(String name, String dept, String[] projects) {
        this(name, dept);
        this.projects = (projects != null) ? projects : new String[0];
    }
    pubic String toString() {
        return super.toString() + ",projects=" + projects;
    }
}

public class SalesPerson extends WorkerBee {
    public double quota;
    public SalesPerson(String name, String[] projects, double quota) {
        this(name, "sales", projects);
        this.quota = 100.0;
    }
    pubic String toString() {
        return super.toString() + ",quota=" + quota;
    }
}

public class Engineer extends WorkerBee {
    public String machine;
    public Engineer(String name, String[] projects, String machine) {
        super(name, "engineering", projects);
        this.machine = machine;
    }
    pubic String toString() {
        return super.toString() + ",machine=" + machine;
    }
}


* vanilla javascript

function Employee(name, dept) {
   this.name = name || "";
   this.dept = dept || "general";
} ;
Employee.prototype.toString = function() { /* 주의: '=' 이다! */
     return "name=" + this.name + ",dept=" + this.dept;
};

function Manager(name, dept, reports) {
   Employee.call(this, name, dept);/* 주의: 부모 클래스의 생성자를 자동으로 호출하지 않는다! */
   this.reports = reports || [];
} ;
Manager.prototype = new Employee;
Manager.prototype.toString = function() {
   return Employee.prototype.toString.call(this) + ",reports=" + this.reports;
};

function WorkerBee(name, dept, projects) {
   Employee.call(this, name, dept);
   this.projects = projects || [];
};
WorkerBee.prototype = new Employee;
WorkerBee.prototype.toString: function() {
   return Employee.prototype.toString.call(this) + ",projects=" + this.projects;
};

function SalesPerson(name, projects, quota) {
   WorkerBee.call(this, name, "sales", projects);
   this.quota = quota || 100;
};
SalesPerson.prototype = new WorkerBee;
SalesPerson.prototype.toString: function() {
     return WorkerBee.prototype.toString.call(this) + ",quota=" + quota;
};

function Engineer(name, projects, machine) {
   WorkerBee.call(this, name, "engineering", projects);
   this.machine = machine || "";
};
Engineer.prototype = new WorkerBee;
Engineer.prototype.toString: function() {
        return WorkerBee.prototype.toString.call(this) + ",machine=" + machine;
};


* javascript with prototype library

var Employee = Class.create(); /* 주의: 클래스도 객체다! 고로 var! */
Employee.prototype = {
   initialize : function(name, dept) { /* 주의: 생성자의 이름은 "initialize" 이다. ':' 이다! */
       this.name = name || "";
       this.dept = dept || "general";
   }, /* 주의: 여기 쉼표!! */  
   toString : function() { /* 주의: ':' 이다! */
        return "name=" + this.name + ",dept=" + this.dept;
   }  /* 주의: 여기에는 쉼표가 없다!! */
});

var Manager = Class.create();
Manager.prototype = Object.extend(new Employee(), {
   initialize : function(name, dept, reports) {
      this.reports = reports || [];
   }
});

var WorkerBee = Class.create();
WorkderBee.prototype = Object.extend(new Employee(), {
   initialize : function(name, dept, projects) {
      this.projects = projects || [];  
   }
});

var SalesPerson = Class.create();
SalesPerson.protype = Object.extend(new WorkerBee(), {
   initialize : function(name, dept, projects, quota) {
      this.dept = 'sales';  
      this.quota = quota || 100;
   }
});

var Engineer = Class.create();
Engineer.prototype = Object.extend(new WorkerBee(), {
   initialize : function(name, dept, projects, machine) {
      this.dept = 'engineering';  
      this.machine = machine || '';
   }
});


* javascript with dojo

dojo.declare("Employee", null, { /* 주의: 클래스 이름을 따옴표로 둘러싼다! */
    constructor : function(name, dept) {  /* 주의: 생성자 이름은 "constructor" 이다! */
       this.name = name || '';
       this.dept = dept || 'general';
   },
   toString : function() {
      return "name=" + this.name + ",dept=" + this.dept;
   }
});
dojo.declare("Manager", Employee, { /* 주의: 부모클래스 이름에는 따옴표가 없다! */
   constructor : function(name, dept, reports) {
      this.reports = reports || [];
   },
   toString : function() {
      return this.inherited("toString") + ",reports=" + reports; /* 주의: 함수 이름을 따옴표로 둘러싼다! */
   }
});

dojo.declare("WorkerBee", Employee, {
   constructor : function(name, dept, projects) {  
      this.projects = projects || [];  
   },
   toString : function() {
      return this.inherited("toString") + ",projects=" + projects;
   }
});

dojo.declare("SalesPerson", WorkerBee, {  
   constructor : function(name, dept, projects, quota) {  
      this.dept = dept || "sales";  
      this.quota = quota || 100;
   },
   toString : function() {
      return this.inherited("toString") + ",quota=" + quota;
   }
});
dojo.declare("Engineer", WorkerBee, {  
   constructor : function(name, dept, projects, machine) {  
      this.dept = dept || "engineering";  
      this.machine = machine || "";
   },
   toString : function() {
      return this.inherited("toString") + ",machine=" + machine;
   }
});

보면 알겠지만, prototype은 자바스크립트의 오리지널 문법을 조금 확장한 형태이고, dojo는 좀 더 일반적인 클래스의 모습을 흉내내려고 노력했다(최근 버전의 prototype은 dojo와 비슷한 문법을 지원한다).
자바스크립트는 함수 오버로딩을 지원하지 않으므로 약간의 삽질성 코드가 필요하다. 이게 생성자의 경우에 조금 곤란한 문제를 만들어 낸다. 즉, 자식 클래스의 생성자는 부모 클래스의 생성자와 인자가 동일하거나 추가만 가능하다(꼼수가 있긴한데... 다음 기회에 얘기하기로 하고... -.-;) .
dojo는 dojo.declare의 두번째 파라메터로 배열을 사용해서 다중 상속을 지원한다. 정확히는 한개의 부모 클래스와 여러개의 mixin이지만, 다중상속이라고 생각해도 무방하다. prototype의 새버전의 문법을 보면 더욱 클래스 기반 언어와 비슷해진 모습을 볼 수 있는데... 조만간 dojo에도 지원되겠지...

OOP와 직접 관련은 없지만 중요한 이야기를 하나 더 해야겠다.
자바스크립트의 this는 C++이나 자바 같은 일반적인 클래스 기반 언어의 this와 다르다!!! this가 현재 문맥에 따라 수시로 변한다!!! 그래서 이런 코드는 기대하지 않았던 결과를 낸다:

function Hello(name, button) {
   this.name = name;
   button.onclick = function(event) {
     alert("hello, " + this.name); /* 주의: 이 this는 함수 밖의 this와 다르다! 즉, Hello 클래스의 인스턴스가 아니고, this.name이라는 건 없다! */
  };
}


* vanilla javascript

...
var self = this; /* 주의: 이 기괴한 문장을 잘 기억해 두자. 자주 쓰게 될 것이다! */
button.onclick = function(event) {
  alert("hello," + self.name); /* 주의: 이 self는 위에서 지정한 self와 같다! */
};
...


* prototype

...
button.onclick = function(event) { ... }.bind(this);
...


* dojo

...
button.onclick = dojo.hitch(this, function(event) { ... });
...

사실, 여기에는 더 복잡한 설명이 필요하지만, (언제나 그랬듯이) 다음 기회로 기약하기로 하고...
일단 prototype 라이브러리에서는 bind()와 bindAsArgs() 함수를 dojo에서는 dojo.hitch()함수를 사용한다고 기억해두자.
위의 코드에서는 비교를 위해서 이벤트를 단순무식하게 연결했지만, 이 함수들은 지난 번에 얘기했던 prototype과 dojo의 이벤트 지원과 함께 많이 사용된다.

* prototype

$("button").each(
   function(buttonNode) {
      Event.observe(buttonNode, "click", this.buttonClicked.bind(this)); /* NOTE: 예제에선 계속 인자로 this만 넘기고 있지만 반드시 그래야 하는 것은 아니다! */
   }.bind(this)
);


* dojo

dojo.forEach("button", function(buttonNode) (
    dojo.connect(buttonNode, "onclick", dojo.hitch(this, "buttonClicked")); /* 주의: 함수 이름에 따옴표! */
}, this); /* 참고: dojo.forEach는 dojo.hitch의 기능을 내장하고 있다 */

dojo.hitch()는 여기에서 설명한 것보다 훨씬 다양한 용법을 갖고 있다. 자세한 내용은 온라인 문서를 참조하시라~

prototype 사용자를 위한 dojo 입문 시리즈(?)는 이 정도로 끝내고, 다음에 기회가 되면 dojo 고유의 기능들에 대해서 소개할 생각이다.

cya~

댓글
댓글쓰기 폼