IT_Programming/JavaScript

자바스크립트 완벽가이드 - 9.5 슈퍼 클래스와 서브 클래스

JJun ™ 2011. 4. 29. 13:33



9.5 슈퍼 클래스와 서브 클래스

 

-      자바 스크립트는 클래스 기반의 상속 대신 프로토 타입 상속을 지원한다.
하지만 자바스크립트를 사용하여도 생각할 수 있는 클래스 계층을

유사하게 만들 수 있다

 

-      프토토 타입 객체 역시 하나의 객체이다.
프로토 타입 객체는 Object() 생성자를 사용하여 만들어진다
.
이 말은 프로토 타입 객체도 Object.prototype의 프로퍼티들을 상속 받는다는 것을

의미한다. 프로토 타입 기반의 상속은 한 개의 프로토 타입에 제한되지 않는다.
대신 프로토 타입 객체의 체인을 사용한다
.

/*

 * Complex.js:

 * This file defines a Complex class to represent complex numbers.

 * Recall that a complex number is the sum of a real number and an

 * imaginary number and that the imaginary number i is the

 * square root of -1.

 */

 

/*

 * The first step in defining a class is defining the constructor

 * function of the class. This constructor should initialize any

 * instance properties of the object. These are the essential

 * "state variables" that make each instance of the class different.

 */

function Complex(real, imaginary) {

    this.x = real;       // The real part of the number

    this.y = imaginary;  // The imaginary part of the number

}

 

/*

 * The second step in defining a class is defining its instance

 * methods (and possibly other properties) in the prototype object

 * of the constructor. Any properties defined in this object will

 * be inherited by all instances of the class. Note that instance

 * methods operate implicitly on the this keyword. For many methods,

 * no other arguments are needed.

 */

 

// Return the magnitude of a complex number. This is defined

// as its distance from the origin (0,0) of the complex plane.

Complex.prototype.magnitude = function() {

    return Math.sqrt(this.x*this.x + this.y*this.y);

};

 

// Return a complex number that is the negative of this one.

Complex.prototype.negative = function() {

    return new Complex(-this.x, -this.y);

};

 

// Add a complex number to this one and return the sum in a new object.

Complex.prototype.add = function(that) {

    return new Complex(this.x + that.x, this.y + that.y);

}

 

// Multiply this complex number by another and return the product as a

// new Complex object.

Complex.prototype.multiply = function(that) {

    return new Complex(this.x * that.x - this.y * that.y,

                       this.x * that.y + this.y * that.x);

}

 

// Convert a Complex object to a string in a useful way.

// This is invoked when a Complex object is used as a string.

Complex.prototype.toString = function() {

    return "{" + this.x + "," + this.y + "}";

};

 

// Test whether this Complex object has the same value as another.

Complex.prototype.equals = function(that) {

    return this.x == that.x && this.y == that.y;

}

 

// Return the real portion of a complex number. This function

// is invoked when a Complex object is treated as a primitive value.

Complex.prototype.valueOf = function() { return this.x; }

 

/*

 * The third step in defining a class is to define class methods,

 * constants, and any needed class properties as properties of the

 * constructor function itself (instead of as properties of the

 * prototype object of the constructor). Note that class methods

 * do not use the this keyword: they operate only on their arguments.

 */

// Add two complex numbers and return the result.

// Contrast this with the instance method add()

Complex.sum = function (a, b) {

    return new Complex(a.x + b.x, a.y + b.y);

};

 

// Multiply two complex numbers and return the product.

// Contrast with the instance method multiply()

Complex.product = function(a, b) {

    return new Complex(a.x * b.x - a.y * b.y,

                       a.x * b.y + a.y * b.x);

};

 

// Here are some useful predefined complex numbers.

// They are defined as class properties, and their names are in uppercase

// to indicate that they are intended to be constants (although it is not

// possible to make JavaScript properties read-only).

Complex.ZERO = new Complex(0,0);

Complex.ONE = new Complex(1,0);

Complex.I = new Complex(0,1);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  위의 예제에서 Object.prototype 보다 Complex.prototype 객체를 먼저 검색하기 때문에
  Object.prototype에 있는 프로퍼티들 중에서 Complex.prototype에 있는 프로퍼티와
  이름이 같은 프로퍼티들은 감춰진다.

 

  또 다른 예를 보자. 아래의 예제는 위의 경우를 응용해서 Rectangle 클래스를
  똑같이 정의하고, 이를 확장하여 PositionedRectangle 클래스를 정의한 예제다.

 

 

// Here is a simple Rectangle class.

// It has a width and height and can compute its own area

function Rectangle(w, h) {

    this.width = w;

    this.height = h;

}

Rectangle.prototype.area = function() { return this.width * this.height; }

 

// Here is how we might subclass it

 

function PositionedRectangle(x, y, w, h) {

    // First, invoke the superclass constructor on the new object

    // so that it can initialize the width and height.

    // We use the call method so that we invoke the constructor as a

    // method of the object to be initialized.

    // This is called constructor chaining.

    Rectangle.call(this, w, h);

 

    // Now store the position of the upper-left corner of the rectangle

    this.x = x;

    this.y = y;

}

 

// If we use the default prototype object that is created when we

// define the PositionedRectangle() constructor, we'd get a subclass of Object.

// To subclass, rectangle, we must explicitly create our prototype object.

PositionedRectangle.prototype = new Rectangle();

 

// We create this prototype object for inheritance purposes, but we

// don't actually want to inherit the width and height properties that

// each Rectangle object has, so delete them from the prototype.

delete PositionedRectangle.prototype.width;

delete PositionedRectangle.prototype.height;

 

// Since the prototype object was created with the Rectangle() constructor,

// it has a constructor property that refers to that constructor.  But

// we want PositionedRectangle objects to have a different constructor

// property, so we've got to reassign this default constructor property.

PositionedRectangle.prototype.constructor = PositionedRectangle;

 

// Now that we've configured the prototype object for our subclass,

// we can add instance methods to it.

PositionedRectangle.prototype.contains = function(x,y) {

    return (x > this.x && x < this.x + this.width &&

            y > this.y && y < this.y + this.height);

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

          - 서브 클래스를 만드는 순서

 

    서브 클래스의 생성자에서 슈퍼 클래스의 생성자를 호출한다.
이 때 슈퍼 클래스의 생성자가 새로 만든 객체의 메서드인 것처럼

호출이 되는데 주의하라.

 

    서브 클래스 생성자의 프로토 타입 객체를 설정하기 위한 몇 가지 기술이 필요하다. 반드시 프로토 타입 객체를 명시적으로 슈퍼 클래스의 인스턴스로 만들어야 하고, 그 후에 프로토 타입 객체의 constructor 프로퍼티를
명시적으로 설정해주어야 한다.
경우에 따라서 프로토 타입의 객체가 자신의 프로토 타입에서

상속받은 프로퍼티가 중요한 경우도 있다. 이 때 프로토 타입 객체에서
슈퍼 클래스의 생성자가 만든 프로퍼티들을 삭제할 수 있다.

 

-       PositionedRectangle 클래스 예제를 이용에서 다음과 같은 코드를
사용할 수 있다.

var r = new PositionedRectangle(2,2,2,2);
print(r.contains(3, 3)); 
//
인스턴스 메서드 호출

print(r.area());  //
상속된 인스턴스 메서드 호출

//
클래스 인스턴스 필드를 사용한다.
print(r.x+”,”+r.y+”,”+r.width+”,”+r.height);

//
우리가 만든 객체는 세 개의 클래스 인스턴스가 된다.
print( r instanceof PosionedRectangle
      && r instanceof Rectangle
      && r instanceof Object
);



 

 

 

 

 


 

 

 

9.5.1 생성자 체이닝

: 서브 클래스 생성자 함수가 상위 클래스의 생성자 함수를 명시적으로 호출하는 방식.
이는 하위 클래스의 프로토 타입 객체에 “superclass”라는 프로퍼티를 추가하는 방법을 통해

생성자 체이닝을 위한 문법을 간소화 할 수 있다.

 

 

// 상위 클래스 생성자에 대한 참조를 저장한다.

PositionedRectangle.prototype.superclass = Rectangle;

 

여기서 정의된 프로퍼티를 사용하여 생성자 체이닝을 더욱 간단히 만들 수 있다.

 

function PositionedRectangle (x, y, w, h)) {

   this.superclass(w, h);

   this.x = x;

   this.y = y;

}

 

이는 상위 클래스 생성자를 호출하는데 더 이상 call() 이나 apply()를 사용할 필요가 없다는 것이다.

 

 

 


 

 

9.5.2 재정의 된 메서드 호출하기

 

 

Rectangle.prototype.toString() = function() {

    return “[” + this.width + “, ” + this.height + “]”;

}

 

Rectangle.prototype.toString() = function() {

    return “[” + this.width + “, ” + this.height + “]”
+ Rectangle.prototype.toString.apply(this); 
//
상위 클래스의 체이닝

}

 

상위 클래스에 구현된 toString() 메서드는 하위 클래스에 있는 프로토 타입 객체의 프로퍼티다.

이 메서드를 직접 호출할 수 없다는데 주목하라! 어떤 객체에서 이 메서드를 호출해야 하는지를 지정하기 위해 apply() 메서드와 함께 호출하라.

 

만약 PositionedRectanglr.prototype superclass 프로퍼티를 추가했다면 다음과 같이 이 코드를 상위 클래스에 독립적인 방식으로 재지정 할 수 있다.

 

Rectangle.prototype.toString() = function() {

 return “[” + this.width + “, ” + this.height + “]”
+ this.superclass.prototype.toString.apply(this); 
//
상위 클래스의 체이닝

 

}