본문 바로가기
typescript

클래스 / 상속 / 접근제한자 / 접근자

by csue 2021. 7. 27.

클래스 Class

typescript 클래스는 클래스 몸체에 클래스 프로퍼티를 사전 선언하여야 한다.

class Animal {
    // 클래스 프로퍼티를 사전 선언하여야 한다
    name: string;

    constructor(name: string) {
      // 클래스 프로퍼티에 값을 할당
      this.name = name;
    }

    status() {
      console.log(`${this.name} is cute.`);
    }
  }

  const animal = new Animal('Dog');
  animal.status(); // Dog is cute.

클래스 Animal 은 세 개의 멤버를 가지고 있다. name 프로퍼티, 생성자constructor, status 메소드이다. 클래스 안에서 클래스의 멤버를 참조할 때 this 를 앞에 붙인다. 이는 멤버에 접근하는 것을 의미한다.

const animal = new Animal('Dog'); 에서 new 는 Animal 클래스의 인스턴스를 생성한다. 이 코드는 이전에 정의한 생성자를 호출하여 Animal 형태의 새로운 객체를 만들고, 생성자를 실행하여 초기화한다.

상속 Interitance

typescript 기본 타입 정의에서 설명하였듯, 인터페이스는 extends 라는 키워드를 사용하여 이미 존재하는 클래스를 확장하고 상속할 수 있다. 이는 클래스도 마찬가지이다.

class Animal {
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

// Dog 클래스는 Animal 클래스를 상속 받았기 때문에 bark()와 move()를 모두 가진 Dog 인스턴스를 생성할 수 있다.
class Dog extends Animal {
    bark() {
        console.log('Woof! Woof!');
    }
}

const dog = new Dog();
dog.bark(); // Woof! Woof!
dog.move(10); // Animal moved 10m.

위의 코드에서 Dog 은 Animal 이라는 상위super 클래스에서 파생된 하위sub 클래스이다.

조금 더 심화된 코드를 보자.

// Animal 클래스는 name 이라는 프로퍼티와 생성자, move 라는 메소드를 가진다.
class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

// Snake 클래스는 Animal 클래스의 하위 클래스이다.
class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        super.move(distanceInMeters);
    }
}

// Horse 클래스는 Animal 클래스의 하위 클래스이다.
class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 45) {
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");
let jain: Animal = new Snake("Jain the Python")
let tom: Animal = new Horse("Tommy the Palomino");

sam.move(); // Sammy the Python moved 5m.
jain.move(10); // Jain the Python moved 10m.
tom.move(34); // Tommy the Palomino moved 34m.

위 코드를 살펴보면 파생된 클래스 Snake 와 Horse 의 생성자 함수는 상위 클래스 Animal 의 생성자를 실행하기 위해서 super() 를 호출해야 한다.

기초 클래스의 메소드를 하위 클래스에 오버라이드 하는 방법도 볼 수 있다. 기초 클래스에서는 move 메소드의 값을 0 으로 지정하였지만, Snake 와 Horse 클래스는 이를 오버라이드해서 각각 5와 45로 지정하였다. Snake의 클래스 인스턴스 sam 에 move 메소드를 붙인 sam.move(); 는 파라미터로 아무 값도 넘겨주지 않아 오버라이드 한 값 5를 출력하였지만, 마찬가지로 Snake 의 클래스 인스턴스 jain 에 move 메소드를 붙인 jain.move(10); 은 10이라는 파라미터로 오버로딩 되어 10을 출력한다.

아래의 코드로 보면 조금 더 이해하기 쉽다.

class A { 
    move(x:number = 0){
        console.log(x);
    }
 }

class B extends A { 
    move(x:number = 5){
        console.log(x);
    }
}

let a = new A();
let b = new B();

a.move(); // 0
b.move(); // 5 .. overriding
a.move(10); // 10 .. overloading
b.move(10); // 10 .. overloading

접근제한자 Access modifiers

접근제한자Access modifiers 는 클래스의 메소드나 프로퍼티의 가시성visibility 을 변경해준다.

다른 클래스 기반 객체 지향 언어가 사용하는 접근 제한자 public, protected, private 를 지원하며 의미 또한 기본적으로 동일하나, 접근 제한자를 명시하지 않았을 때 protected 로 지정되는 다른 클래스 기반 언어들과는 달리 typescript 는 암묵적으로 public 이 선언된다.

접근제한자의 종류

접근 가능성 public protected private
클래스 내부 O O O
자식 클래스 내부 O O X
클래스 인스턴스 O X X

아래의 코드를 ts 파일에 붙여넣으면 접근 제한자가 어떻게 기능하는지 빨간 줄을 통해 확인할 수 있다.

class Foo {
    public x: string;
    protected y: string;
    private z: string;

    constructor(x: string, y: string, z: string) {
      // public, protected, private 접근 제한자 모두 클래스 내부에서 참조 가능하다.
      this.x = x;
      this.y = y;
      this.z = z;
    }
  }

  const foo = new Foo('x', 'y', 'z');

  // public 접근 제한자는 클래스 인스턴스를 통해 클래스 외부에서 참조 가능하다.
  console.log(foo.x);

  // protected 접근 제한자는 클래스 인스턴스를 통해 클래스 외부에서 참조할 수 없다.
  console.log(foo.y);
  // error TS2445: Property 'y' is protected and only accessible within class 'Foo' and its subclasses.

  // private 접근 제한자는 클래스 인스턴스를 통해 클래스 외부에서 참조할 수 없다.
  console.log(foo.z);
  // error TS2341: Property 'z' is private and only accessible within class 'Foo'.

  // extends 를 이용해 클래스 Foo 를 상속받은 자식 클래스 Bar
  class Bar extends Foo {
    constructor(x: string, y: string, z: string) {
      super(x, y, z);

      // public 접근 제한자는 자식 클래스 내부에서 참조 가능하다.
      console.log(this.x);

      // protected 접근 제한자는 자식 클래스 내부에서 참조 가능하다.
      console.log(this.y);

      // private 접근 제한자는 자식 클래스 내부에서 참조할 수 없다.
      console.log(this.z);
      // error TS2341: Property 'z' is private and only accessible within class 'Foo'.
    }
  }

접근 제한자는 생성자 파라미터에도 선언할 수 있다. 이때 ,접근 제한자가 사용된 생성자 파라미터는 생성자 내부에서 별도의 초기화가 없어도 암묵적으로 초기화가 수행된다.

class Foo {
    /*
    접근 제한자가 선언된 생성자 파라미터 x는 클래스 프로퍼티로 선언되고 자동으로 초기화된다.
    public이 선언되었으므로 x는 클래스 외부에서도 참조가 가능하다.
    */
    constructor(public x: string) { }
  }

  const foo = new Foo('Hello');
  console.log(foo);   // Foo { x: 'Hello' }
  console.log(foo.x); // Hello

  class Bar {
    /*
    접근 제한자가 선언된 생성자 파라미터 x는 멤버 변수로 선언되고 자동으로 초기화된다.
    멤버변수란 클래스 내에서 선언되는 변수를 의미한다.
    private이 선언되었으므로 x는 클래스 내부에서만 참조 가능하다.
    */
    constructor(private x: string) { 
        console.log(this.x); // Hello
    }
  }

  const bar = new Bar('Hello');

  console.log(bar); // Bar { x: 'Hello' }

  // private이 선언된 bar.x는 클래스 내부에서만 참조 가능하다
  console.log(bar.x); // Property 'x' is private and only accessible within class 'Bar'.

클래스 상속 시, 클래스의 모든 멤버(public, protected, private)가 상속되지만 구현까지 상속하지는 않는다

readonly

typescript 는 readonly 접근 제한자를 추가로 지원한다. 클래스의 속성에 readonly 키워드를 사용하면 프로퍼티에 변경할 수 없는immutable 속성을 선언하여 값을 할당할 수 없고 오로지 접근 및 읽기만 가능하다.

class Foo {
    private readonly MAX_LEN: number = 5;
    private readonly MSG: string;

    constructor() {
      this.MSG = 'hello';
    }

    log() {
      // readonly가 선언된 프로퍼티는 재할당이 금지된다.
      this.MAX_LEN = 10; // Cannot assign to 'MAX_LEN' because it is a constant or a read-only property.
      this.MSG = 'Hi'; // Cannot assign to 'MSG' because it is a constant or a read-only property.

      console.log(`MAX_LEN: ${this.MAX_LEN}`); // MAX_LEN: 5
      console.log(`MSG: ${this.MSG}`); // MSG: hello
    }
  }

  new Foo().log()

readonly 와 const 의 차이는 어디에 사용할지에 있다. const 는 변수에 사용하고 readonly 에는 프로퍼티에 사용한다.

접근자

typescript 는 객체가 특정 속성에 접근하고 할당하는 것에 대해 제어할 수 있다. 접근을 제한하는 접근제한자가 있다면 접근을 할 수 있도록 하는 접근자가 존재할수도 있다는 뜻이다.

획득자getter 와 설정자setter 는 객체의 멤버에 대한 접근을 가로채는 방식을 의미한다. 이를 통해 각 객체의 멤버에 접근하는 방법을 세밀하게 제어할 수 있을 뿐만 아니라 private 설정 된 속성에 접근하여 값을 읽거나 쓰기 위한 getter / setter 함수를 사용할 수 있다.

getter 와 setter 는 말 그대로 어떤 객체 혹은 변수의 값을 가져오거나 설정해주는 역할을 하는 메소드를 의미한다. getter 메소드는 프로퍼티를 읽으려고 할 때 실행되고 setter 는 값을 할당하려고 할때 사용된다. get 으로 가져오고 set 으로 설정한다고 생각하면 된다.

class Person {
  private _name: string | null = null;
  private _age: number

  // 여기에서 Person 의 private 프로퍼티인 _name 을 가져온다
  get name(): string {
    return this._name;
  }

  // 위에서 가져온 _name 에 조건 값을 할당한다
  set name(value:string) {
    if ( value.length > 3 ) {
      this._name = value;
      }
  }

}


/* 인스턴스 생성 ------------------------------------------------ */

let person = new Person();

console.log(person.name); // null

person.name = 'sue';

console.log(person.name); // null .. string 이 되려면 3보다 커야 한다

person.name = 'choi sue';

console.log(person.name); // choi sue

참고로 ECMAScript 5 이상을 출력하도록 컴파일러를 설정해야한다. tsc -t es5 filename.ts 명령어를 실행하여 컴파일하면 된다.

'typescript' 카테고리의 다른 글

typescript  (0) 2021.07.26