본문 바로가기

Study/TypeScript

Class

Class 

basic concepts 

js에서 class는 같은 구조의 객체를 빠르게 만들 수 있도록 도와준다(a blue print of objects). ts를 이용하면, class에 타입을 더해서 사용할 수 있다. 

 

class Department {
  // property 타입 정의 
  name: string;

  // 새로운 객체를 생성할 때 name 매개변수에 인자를 전달할 수 있다
  constructor(name: string) {
    this.name = name;
  }
  
  // methods
  describe() {
    // console.log('Department: ' + name); 
    console.log('Department: ' + this.name); 
  }
}

const hr = new Department('hr'); // hr의 타입은 Department이다
hr.describe(); // => Department: hr 

const frontEnd = {
    describe: hr.describe,
}
frontEnd.describe(); // 컴파일 에러 ❌ => Department: undefined

 

  1. console.log(name) 
    • 메서드(describe) 내에서 name 변수를 찾는다. 
    • 메서드 내부에 없다면, 클래스 외부(전역)에서 name 변수를 찾는다. 
    • window 객체의 프로퍼티 중 하나인 name을 찾는다. 
  2. console.log(this.name) 
    • 클래스 내부의 this는, 클래스에 의해 생성될 객체 자체를 가리킨다. 
    • 따라서 this.name은 객체의 name을 찾는다. 
  3. frontEnd.describe()
    • 함수를 호출할 때, 함수 내부의 this는 이 함수를 호출하고 있는 것을 가리킨다.
    • ex) frontEnd.describe()에서 describe의 this는 frontEnd을 가리킨다. 

 

class Department {
   // ...
    describe(this: Department) {
        console.log('department : ' + this.name)
    }
}

const backEnd = {
    age: 12, 
    describe: frontEnd.describe, 
}
backEnd.describe(); // 컴파일 에러⭕

/*
The 'this' context of type '{ age: number; describe: (this: Department) => void; }' 
is not assignable to method's 'this' of type 'Department'.
*/

 

  • describe(this: Department){ } 
    1. describe를 실행할 때, 이 함수 내에서 호출하고 있는 this.name의 this는 Department 클래스를 기반으로 한 instance 객체여야 한다는 것을 의미한다. 
    2. backEnd 객체 자체에 name 프로퍼티를 추가하거나, new Department를 이용해서 backEnd 객체를 생성하면 컴파일 에러를 해결할 수 있다. 

 

// 1. 
class Department {
    employees: string[] = [];

    addEmployee(name: string) {
    // validation check... 
        this.employees.push(name)
    }

    describeEmployess() {
        console.log(this.employees)
    }
}

const frontEnd = new Department();

frontEnd.addEmployee('Harry'); // 가능 
frontEnd.employees.push('Harry'); // 가능

// 2. 
class Department {
    private employees: string[] = [];
    //...
}

frontEnd.addEmployee('Harry'); // 가능 
frontEnd.employees.push('Harry'); // 불가능

// 3. 약식형 
class Department {
    private employees: string[] = [];
    
    constructor(private readonly id: string, public name: string) {
      // this.id = id 작업 생략 가능 
      // public 키워드 생략 불가능
    }
    // ...
}

 

  1. employee를 추가하는 방법이 두 가지가 있는데, 이는 문제가 될 수 있다.
    1. addEmployee에서 유효성 검사를 진행할 수 있다.
    2. 그런데 employees.push를 이용하면 유효성 검사 없이도 값을 추가할 수 있게 된다. 
    3. 뿐만 아니라 employee를 추가하는 방법이 통일되지 않아서, 팀 프로젝트가 중구난방 식이 될 수 있다. 
  2. private 키워드를 이용한다.  
    1. promerty나 method 앞에 priavte 키워드를 작성할 수 있다.
    2. private 키워드가 사용된 프로퍼티, 메서드는 class를 이용해 생성되는 객체 인스턴스 내부에서만 접근할 수 있다. => 즉, frontEnd 객체의 addEmployees 메서드를 통해서만 employees 속성에 접근할 수 있다. 
    3. private 키워드를 사용하지 않은 경우, 자동으로 public이 된다(기본값). => 외부에서도 접근할 수 있다.  
  3. readonly
    • 값이 초기화 된 이후에 변경되어서는 안되는 프로퍼티의 경우, readonly 키워드를 사용하면 된다.

more concepts 

inheritance

class Department {
  private employees: string[]; 
  constructor(public name: string, private id: string) {
  }
  
  describe() {
    console.log('Department: ' + this.name); 
  }
  
  addEmployee(name: string) {
    this.employess.push(name);
  }
}

 

// 1. 
class ItDepartment extends Department {
}

new ItDepartment('backend', '12345'); // class가 텅 비어있어도 에러가 발생하지 않는다

// 2. 
class ItDepartment extends Department {
  constructor(id: string, public reports: []string,) {
    super(id, 'IT'); 
    this.reports = reports; 
  }
  
  addEmployee(name: string) {
    if(name === 'Harry') return ;
    this.employess.push(name); // error! 
  }
}

const it = new ItDepartment('12345', ['something...']);
it.describe(); // Department: it

// 3. 
class Department {
  protected employees: string[]; // private => protected
  // ...
}

class ItDepartment extends Department {
  // override 
  addEmployee(name: string) {
    if(name === 'Harry') return ;
    this.employess.push(name); // no error! 
  }
}

// 4. abstract
abstract class Department {
  // ...
  abstract describe(this: Department) : void; 
  // abstract describe?(this: Department) : void; => optional method
}

class ItDepartment extends Department {
  // ...
  describe() {
    console.log(this.name); 
  }
}

 

  1. ItDepartment는 Department를 상속한다. 
  2. 이 때 ItDepartment는 Department의 모든 것(프로퍼티, 메서드, constructor)을 자동으로 가져온다.
    • => 클래스가 비어있어도 에러가 발생하지 않는다. 
  3.  두번째 예제의 경우처럼 constructor를 이용하면, Department에서 정의된 속성 이외에도, 객체를 생성할 때 인자로 전달받을 값을 추가할 수도 있다. 
    • super() | Department의 constuctor를 호출한다. 
    • super를 호출한 다음, ths 키워드를 이용해서 새로운 프로퍼티를 추가할 수 있다. 
  4. Department에 이미 정의되어 있는 메서드를 덮어쓰는 것도 가능하다.
  5.  ItDepartment(상속받는 클래스)의 메서드에서 Department(상속하는 클래스)의 private 필드에 접근하려고 하면, 에러가 발생한다. 
    • private 필드는 그 필드가 정의된 클래스 내부에서만 접근 가능해서, 상속받는 클래스에서는 접근이 불가능하기 때문이다. 
    • protected | private 대신 protected를 사용하면, (여전히 외부에서 그 값에 접근하는 것은 불가능하지만) 상속받는 클래스에서도 그 프로퍼티에 접근할 수 있다.  
  6. abstract | 인스턴스를 직접 만들지 않고 상속하기만 하는 클래스를 정의할 때, 또는 어떤 메서드가 모든 상속받는 클래스에 기본적으로 존재하되, 상속받을 때 이를 구체화 하고 싶다면 abstract 키워드를 사용할 수 있다.
    • 클래스 이름 앞에 abstract 키워드(modifier)를 사용해야 한다. 
    • 메서드의 구체적인 내용은 상속받는 클래스에서 정의하고, 상속하는 클래스에서는 메서드의 타입만을 정의한다. => 구체적인 내용이 정해지는 상속받는 클래스 내부에서 이 메서드를 호출한다.  
    • 상속받는 클래스에서 메서드를 정의하지 않으면 에러가 발생한다. 
    • 상속하는 클래스에서 abstract 메서드의 modifier로 private을 사용할 수 없다(private abstract method is not supported). 

getter & setter 

private 필드는, 클래스 내부의 메서드를 통해서만 그 값에 접근할 수 있다. 이 때 getter 메서드를 이용하면 private 필드의 값을 확인할 수 있다. 

 

class Department {
  private employees: string[]; 
  
  get lastEmployee() {
    if(this.employess[this.employees.length - 1]) {
      return this.employess[this.employees.length - 1]; 
    } throw new Error('no employees'); 
  }
  
  set lastEmployee(value: string) {
    if(!value) return; 
    this.addEmployee(value);
  }
  
  constructor(public name: string, private id: string) {
  }
  
  addEmployee(name: string) {
    this.employess.push(name);
  }
}

const it = new Department('it', '12345');
it.lastEmployee; // get => it.lastEmployee() ❌
it.lastEmployee = 'harry'; //set => it.lastEmployee('harry') ❌

 

  1. getter 
    • 반드시 무언가를 return 해야한다. 
    • 단순히 값을 확인하는 것 이외의 로직을 추가할 수도 있다. 
    • getter 메서드는 함수처럼 실행하는 것이 아니라, 프로퍼티처럼 접근해야 한다. 
  2. setter 
    1. set 함수 내에서 원하는 로직을 작성할 수 있다. 
    2. setter 메서드 또한 함수처럼 실행하는 것이 아니라, 프로퍼티 처럼 접근해서 값을 할당해야 한다. 

static properties & methods

 Math.round() 는 Math 클래스 자체의 round 메서드를 호출하는 것이다. 즉 Math의 인스턴스를 생성하지 않고(ex.  const myMath = new Math(); myMath.round(); ), 클래스 자체에 접근한다. 이러한 속성/메서드를 static property/method 라고 부른다. 

 

class Department {
  // ... 
  static addEmployee(name: string) {
    return { name }
  }
  static fiscalYear = 2022; 
  
}

const employee1 = Department.addEmployee('Harry');
const fiscal =  Department.fiscalYear;

 

  1. static 키워드를 사용해서 프로퍼티/메서드를 정적으로 만들 수 있다. 
  2. this.fiscalYear❌ => Department.fiscalYear⭕
    • 클래스 내부에서 this는 new 키워드에 의해 생성될 인스턴스 객체를 가리킨다. 
    • fiscalYear와 같은 정적 속성은 인스턴스 객체에 존재하는 것이 아니라, 클래스 자체에 존재하는 것으로 this를 이용해 접근하는 것이 불가능하다. 
    • 클래스 내부에서 정적 속성/메서드에 접근하고자 할 때에는 그 클래스의 이름(Department)을 이용한다. 

singleton 

객체지향 프로그래밍에서 singleton이란, 특정 클래스의 인스턴스를 정확히 하나로 제한하는 것을 말한다. 예를 들어 하나의 회사에 여러 개의 It 부서를 만들 필요가 없다면, new ItDepartment 를 이용해서 만들 수 있는 인스턴스는 하나로 제한하는 것이 좋다. 즉 ItDepartment 클래스의 인스턴스는 정확히 하나로 제한하는 것이 좋다. 

 

class ItDepartment extends Department {
  private static instance: ItDepartment; 

  private constructor(id: string, public reports: string[],) {
    super(id, 'IT'); 
    this.reports = reports; 
  }
  
  static getInstance() {
    // 이 때의 this는 ItDepartment 클래스 자체를 가리킨다 
    if(this.instance) return this.instance; 
    // constructor(new ItDepartment)가 private이므로, 클래스 내부 메서드에서 호출 가능
    this.instance = new ItDepartment('12345', ['report1']);
    return this.instance; 
  }
  
  // ...
}

// const it = new ItDepartment('123', ['asdf']); 에러! 
const it = ItDepartment.getInstance(); // 에러가 발생하지 않는다
const it2 = ItDepartment.getInstance(); // it와 it2는 같다

 

  1. private constructor 
    • constructor 함수는 외부에서 호출할 수 없다. 즉, 클래스 외부에서 new ItDepeartment 생성자 함수를 호출하는 것이 불가능하다. => 대신 getInstance라는 메서드를 정의하고, 그 내부에서 instance를 생성한다. 
  2. static getInstance
    • 클래스의 인스턴스가 이미 존재하는지 확인하고, 없을 때에만 새로운 인스턴스를 반환한다.
    • static 메서드이므로 클래스 자체를 통해서만 호출할 수 있다. => ItDepartment.getInstance()
    • static 메서드에서 this는 ItDepartment 자체가 된다(ItDepartment를 통해서만 호출할 수 있기 때문).
  3. private static instance: ItDepartment
    • instance라는 프로퍼티를 만든다. => 여기에 ItDepartment 타입을 부여한다. 
    • static 프로퍼티이므로, 클래스 자체(ItDepartment)에서만 접근할 수  있다. => ItDepartment.instance
    • 동시에 private 프로퍼티이므로, 클래스 내부에서만(이 클래스의 메서드, 속성을 통해서만) 이 값에 접근(조작? 무언가를 하는 것?)할 수 있다. 

'Study > TypeScript' 카테고리의 다른 글

Generic  (0) 2022.07.24
Advanced Types  (0) 2022.07.23
Interface  (0) 2022.07.21
tsconfig | ts compiler  (0) 2022.07.17
Types  (0) 2022.07.17