객체 지향 프로그래밍? ⇒ 코드에서 실제와 유사한 객체를 만듦
- 클래스
⇒ 객체의 청사진
⇒ 객체가 가지는 프로퍼티나 메서드 등의 구조를 정의
- 객체(인스턴스)
⇒ 애플리케이션의 로직을 분할해 객체로 각 로직을 관리할 수 있음
⇒ 데이터를 저장하고 메서드를 실행하는데 사용하는 자료구조
⇒ 하나의 클래스를 기반으로 동일한 구조를 가짐
⇒ 1클래스 당 n객체 생성 가능
- JS의 클래스 ⇒ 생성자 함수의 문법 설탕..
- 속성(like 변수), 생성자, 메서드(like 함수)
- 속성이나 메서드에 const, let을 써주지 않아도 됨
- 속성, 메서드에 타입을 각각 써주면 됨
- 속성의 초기화는 필수가 아님
- constructor : 생성자 함수, 객체가 생성될 때 사용
- 메서드는 콜론과 function 키워드를 생략 가능(JS 규칙)
class Department { name:string //this.name으로 접근 가능 //기존 object처럼 key, value의 쌍이 아니라 클래스의 필드임 constructor(n: string) { //생성자함수, JS와 TS 모두 지원 this.name = n } getName() { //메서드는 콜론과 function을 생략 가능(원래는 getName: function() {}) return this.name } } const accounting = new Department('Accounting')
- es6에는 클래스가 있긴 하지만 필드가 없다. 따라서 필드 없는 클래스로 컴파일 됨
const describe = accounting.describe describe() //호출 가능, 근데 undefined가 나옴(이유는 밑 설명)
- 해당 객체만 이 메서드를 호출할 수 있음
- 함수 호출시 this 매개변수는 안써줘도 된다.
- 메서드가 아닌 일반 함수(화살표 함수는 안됨)에서 이렇게 명시함으로써 함수 내에 this를 타입을 any가 아닌 특정하게 지정해줄 수 있다
class Department { ... getName(this: Department) { //Department 객체만 이 메서드를 호출할 수 있음 return this.name } } const accounting = new Department() accounting.getName() //O, accounting은 Department의 객체이므로 호출 가능 const accountingCopy1 = { getName: accounting.getName } accountingCopy1.getName() //X : getName에서 쓰는 this가 accountingCopy인데, accountingCopy는 name 속성이 없고, // Department의 객체만 getName를 호출할 수 있음 const accountingCopy2 = { name: 'jjong', getName: accounting.getName } //Department 객체임 accountingCopy.getName() //X, Department의 객체만 getName를 호출할 수 있음
type Department { name: '' } function getName(this: Department) { // Department 타입만 이 함수를 호출 가능 return this.name }
- private, public으로 접근 제어자를 지정할 수 있다
- public: (기본) 외부에서 접근 가능
- private: 외부에서 접근 불가능(자식도 불가)
- +) protected: (for 상속)외부에서 접근 불가능(자식은 가능)
- 이 접근제어자 개념은 es6에서 생긴 것으로, 그 이전 js에서는 xx
- 따라서 TS의 js 버전을 es6미만으로 한다면 해당 개념은 컴파일 되지 않고, ts에서 컴파일 오류가 난다고 해도 런타임 오류는 나지 않는다
class Department { private name:string .. }
class Department { private isChecked: boolean constructor(private id: number, public: name: string) { isChecked = true //생성자로 선언되는 속성이 아님 } } const accounting = new Department(1, 'accounting') //Department 객체 생성과 동시에 id와 name 필드가 같이 생성됨
- readonly로 속성을 읽기 전용, 즉 초기화 후에 변화하지 못하게 할 수 있다
- ts에만 있는 개념
class Department { private readonly name:string .. }
- 상속
- 클래스 이름 뒤에
extends 부모클래스
를 덧붙이면 해당 부모 클래스를 상속 한다는 의미 - 내부 코드가 없다면 걍 Department와 동일한 기능의 클래스가 됨
- 한 클래스만 상속 가능하다
- 자식 클래스의 생성자 함수를 따로 만들어 주려면, 생성자 내부에 부모 클래스의 생성자를 무조건 먼저 불러줘야 함
- 부모 생성자 부르기:
super()
⇒ 매개변수도 부모 생성자와 같아야 함 - 물론 자식 생성자에서도 기존 개념(접근 제어자로 새 변수 선언 등)이 적용된다
⇒ 기존에 있던 클래스의 필드-생성자-메서드를 다 받는 것, 여기에 추가도 가능하다
class Department { ... } class AccountingDepartment extends Department { constructor(id: string, private reports: string[]) { //reports 필드가 생성 super(id, 'Accounting') //부모 생성자 호출, 매개변수 같아야 함 } addReport(text:string) { //자식에서 새로운 메서드 정의 this.reports.push(text) } }
class Department { ... getName(name: string) { return this.name } } class AccountingDepartment extends Department { constructor(id: string, private reports: string[]) { //reports 필드가 생성 super(id, 'Accounting') //부모 생성자 호출, 매개변수 같아야 함 } getName(name:string) { //부모에 있던 메서드를 오버라이드 if (name === 'Ann') { return } return name } }
- 메서드의 오버라이드를 강제하는 것
- 기본 클래스에서 메서드를 구현x(중괄호 없이 선언), 상속받은 자식 클래스들에서 구현을 미룸
- 기본 클래스의 메서드는 매개변수와 반환값의 타입은 명시
- 자식 클래스들에서는 무조건 해당 메서드를 구현해야한다
- 추상화 하고 싶은 메서드 앞에
abstract
를 붙임. 클래스 내에 하나라도 abstract가 있으면 클래스 이름 앞에도abstract
를 붙여야 함 ⇒ 해당 클래스는 추상클래스가 됨 - 추상 클래스는 상속 받기 위해서만 존재하는 클래스이고, 인스턴스화 할 수 없다
abstract class Department { abstract describe():string; } class Account extends Department { describe(): string { //필수 return "" } }
- getter, setter
- 외부에서 필드 값을 얻게하는 get, 외부에서 필드 값을 정의하게 하는 set
get 게터이름() { return ~ }
set 세터이름(name: string) { ~ }
- 외부에서 호출:
객체.세터이름 = ‘name’
- 외부에서 부를 때 메서드가 아닌 속성처럼 접근
객체.게터이름
,객체.세터이름
- 그래서 외부에선 속성에 바로 접근하는 것 처럼 보임 ㅎ
class Department { ... get name() { if (!this.name) { throw new Error('No name found') } //로직 추가 가능 return this.name } set name(name: string) { if(!name) {throw new Error ..} this.name = name } } const accounting = new Department('accounting') const departmentName = accounting.name //getter 호출 accounting.name = 'marketing' //setter 호출
- 정적 메서드 & 속성
static
을 변수나 메서드 이름 앞에 붙여주면 정적 변수/메서드가 됨- 생성자를 포함한 정적 메서드가 아닌 곳에서는 this로 정적 메서드, 속성에 접근할 수 없다
- 정적 메서드가 아닌 곳에서의 this는 인스턴스를 가리키는 것이기 때문
- 대신 클래스 명으로는 접근 가능
- 정적 메서드에서 this는 클래스이다
: 인스턴스에선 접근 불가하고, 클래스에서 직접 접근가능하다
class Department { static name: string static createEmployee(employee: string) { return {employee: employee} } } Department.name //o Department.createEmployee //o
- 싱글톤 패턴
- 클래스당 하나의 인스턴스만 있는 것
- 생성자를 private으로 막아서 외부에서 인스턴스를 여러개 생성하지 못하도록 함
- 대신 클래스내에 인스턴스를 미리 선언함. 반환 값은 해당 클래스
- 그리고 인스턴스를 반환하는 메서드를 public으로 선언해 해당 메서드에서만 인스턴스를 가져오도록 함
- 해당 인스턴스에서는 지금 클래스에 선언된 인스턴스가 초기화(생성자로 정의됨)가 되었는지 확인
- 초기화가 안되어 있으면 생성자로 인스턴스를 생성해줌
- 되어 있으면 기존의 인스턴스를 생성함
- 인스턴스 반환 메서드는 static으로 선언해서, 외부에서 인스턴스로 접근하지 않고 클래스로 접근하게 한다(어차피 생성자로 인스턴스 생성불가라 인스턴스로 접근 못함)
- 클래스에 선언된 인스턴스를 여기서 반환할것이라, 해당 인스턴스도 static이어야 한다
class Department { private static instance: Department; //인스턴스는 static 변수 private constructor(..) { //생성자를 private으로 } static getInstance() { if (this.instance) { //==Department.instance instance이 정의 되었다면 return this.instance } return new Department(..) //정의되어 있지 않으면 생성자로 생성함 } const department = new Department(..) //X const department = getInstance() //O const department2 = getInstance() // department와 같은 인스턴스이다 }