typescript chapter7. 클래스와 인터페이스

타입스크립트의 객체지향 프로그래밍 지원

객체지향 프로그래밍 요소 자바스크립트(ES6) 타입스크립트
클래스 class class
인터페이스 x interface
인터페이스구현 x implements
상속 extends extends
생성자 constructor(){} constructor(){}
접근 제한자 x private, public, protected
final 제한자 x readonly(TS 2.0부터 지원)
static 키워드 static static
super 키워드 super super

자바스크립트는 객체지향 프로그래밍을 하기에 지원이 다소 부족하지만, 타입스크립트는 객체지향 프로그래밍에 부족함이 없다.

클래스

클래스 선언과 객체 생성

타입스크립트에서 class를 선언할 때는 클래스명 앞에 class를 붙여 선언한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Rectangle {
x: number;
y: number;

constructor(x: number, y: number) {
this.x = x;
this.y = y;
}

getArea(): number {
return this.x * this.y;
}
}

이렇게 선언한 Rectangle 클래스는 클래스 타입이 된다. Rectangle 클래스는 다음의 인터페이스 타입과 정확히 일치하게 된다.

1
2
3
4
5
interface Rectangle {
x: number;
y: number;
getArea(): number;
}

클래스 내부에는 생성자는 contstructor을 정의한다. 생성자는 객체 생성 시 클래스에 필요한 설정을 매개변수로 전달받아 멤버변수를 초기화한다.
생성자를 생략하면 기본 생성자를 호출한다. 생성자는 클래스 선언 떄 생략 가능하다.

객체 생성

클래스는 멤버 변수와 메서드 등으로 구성된 ‘틀’이며 클래스를 실제로 사용하려면 객체를 새로 생성해줘야한다.

1
let rectangle = new Rectangle(1, 4);

new 키워드를 사용해 Rectangle 객체를 생성해 객체 참조변수에 할당한다. 이를 인스턴스화라고 한다.
rectangle은 객체 참조변수(인스턴스) 이고, new 키워드를 통해 Rectangle객체를 생성함(인스턴스화)

Rectangle 클래스 선언과 객체 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Rectangle {
x: number;
y: number;

constructor(x: number, y: number) {
this.x = x;
this.y = y;
}

getArea(): number {
return this.x * this.y;
}
}

let rectangle = new Rectangle(1, 4);

let result: number = rectangle.getArea();
console.log(result); // 4

상속관계, 포함관계

클래스 간의 관계는 상속 관계와 포함 관계가 있다.

  • 상속 관계

    부모 클래스를 기반 클래스 또는 슈퍼 클래스라고 하며 이를 상속 받는 자식 클래스를 파생 클래스 또는 서브 클래스라고 부른다.
    자식 클래스는 부모 클래스에 공개된 메서드나 변수를 상속받는다.( IS-A관계)
    상속을 위해 extends 키워드를 지원한다.
    단일 상속만 지원하므로 자식 클래스는 하나의 부모 클래스만 상속받을 수 있다. 상속시 자식 클래스 생성자에서 super() 메서드를 호출해 부모 클래스의 생성자를 호출해주어야한다.

1
2
3
4
5
class <자식 클래스> extends <부모 클래스>{
constructor(){
supper();
};
}
  • 포함 관계

    클래스가 다른 클래스를 포함하는 (HAS-A) 관계이다.
    합성(composition)관계
    집합(aggregation)관계

  • 합성 관계는 전체가 부분을 포함하며 강한 관계이다.
1
2
3
4
5
6
7
8
9
10
11
class Engine {}

class Car {
private engine;
constructor() {
this.engine = new Engine();
}
}

let car = new Car();
car = null;

Car 클래스에 선언된 engine 객체는 Car 클래스가 new로 생성될때 함께 생성되고, car가 null이되면 함께 제거된다.(생명주기를 함께한다)

  • 집합 관계는 전체가 부분을 포함하며 약한 관계이다.
1
2
3
4
5
6
7
8
9
10
11
class Engine {}

class Car {
private engine: Engine;
constructor(engine: Engine) {
this.engine = engine;
}
}

let engine = new Engine();
let car = new Car(engine);

Car 클래스의 car 객체가생성될때 외부에서 생성된 engine 객체를 전달하고 있다.따라서 car가 null이 되더라도,
engine 객체는 Car클래스 외부에서 선언되어 null이 되지 않아 생명주기를 함꼐 하지 않는다.

상속관계와 포함 관계를 고려해 구현하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Flashlight 클래스 - 포함
class Flashlight {
constructor(public lightIntensity) {}
}

// Bicycle 클래스 - 부모
class Bicycle {
constructor(public numberOfWheel: number) {}

getNumberOfWheel(): number {
return this.numberOfWheel;
}
}

// Bicycle 클래스를 상속함(IS-A 관계)
class MountainBike extends Bicycle {
flashlight: Flashlight;

constructor(public numberOfWheel: number, public hasBackSaddle: boolean) {
super(numberOfWheel); // 부모의 메서드numberOfWheel를 사용하기위해

// 자전거가 후레쉬 라이트를 포함함(HAS-A 관계)
this.flashlight = new Flashlight(90);
}

getHasBackSaddle() {
return this.hasBackSaddle;
}

getNumberOfWheel() {
return this.numberOfWheel;
}
}

let hasBackSaddle = true;
let numberOfWheel = 2;
let mountainBike = new MountainBike(numberOfWheel, hasBackSaddle);
console.log("자전거의 안장 유무 : " + mountainBike.getHasBackSaddle()); // true
console.log("자전거의 바퀴 개수 : " + mountainBike.getNumberOfWheel()); // 2

접근 제한자 사용법

타입스크립트에서는 접근 제한자 (private, public , protected)를 제공한다.

접근 제한자 특징 상속 여부 외부객체를 통한 접근
public public으로 설정된 멤버(멤버 변수, 메서드)등은 자식 클래스에서 접근 할 수 있다. o o
protected protected로 설정된 맴버는 자식 클래스에서 접근 가능 o x
private private로 설정된 멤버는 현재 클래스에서만 접근할수 있고, 자식 클래스에서 접근 불가능 x x
  • public 제한자와 private 제한자

    public 은 클래스 내부와 외부에서 모두 접근 가능하게 한다. 객체 내부나 외부에서 접근할 수 있고 부모 클래스로부터 상속도 가능하다.
    private 제한자는 클래스 내부에서는 접근 할 수 있지만 외부에서는 접근 못하게 하는 접근 제한자이다.

생성자 매개변수에 접근 제한자 추가

생성자 매개변수에 접근 제한자를 추가하면 매개변수 속성이 되어 멤버 변수가 되는 효과가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Cube {
public width: number;

constructor(pwidth: number) {
this.width = pwidth;
}
}

/****같은 역할을 하도록 코드를 간결하게 변경****/
class Cube {
constructor(public width: number) {
}
}

생성자 매개변수에 접근 제한자를 추가해 멤버 변수처럼 사용하는 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Cube {
// #1 생성자 매개변수 선언
constructor(
public width: number,
private length: number,
protected height: number
) {}

// #2 직육면체 부피 구하기
getVolume() {
return this.width * this.length * this.height;
}
}

let [cWidth, cLength, cHeight] = [1, 2, 3]; // 가로, 세로, 높이
let cube = new Cube(cWidth, cLength, cHeight);
console.log("1번 세로 : ", cube.width, "cm"); // length, height는 접근 불가
console.log("2번 부피 : ", cube.getVolume(), "ml");

오직 public으로 선언된 생성자 매개변수 width만이 외부객체에서 접근을 허용함(private, protected는 비허용)

  • protected 제한자의 사용법

    protected는 객체를 통한 외부 접근은 비허용하지만 상속관계에서 부모클래스에 protected로 선언된 메서드나 멤버 변수의 접근은 허용한다.

부모 클래스의 맴버를 이용

super 키워드와 this 키워드를 이용해서 자식 클래스에서 부모 클래스에서 선언된 메서드나 변수를 이용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class PC {
constructor(hddCapacity) {
this.hddCapacity = hddCapacity;
this.ram = "0G";
}
set ramCapacity(value) {
this.ram = value;
} // set 프로퍼티
get ramCapacity() {
return this.ram;
} // get 프로퍼티
getHddCapacity() {
return this.hddCapacity;
}
}
class Desktop extends PC {
constructor(hddCapacity) {
// 부모 클래스의 생성자를 호출함
super(hddCapacity);
this.hddCapacity = hddCapacity;
}
getInfo() {
console.log("1번 HDD 용량 : " + super.getHddCapacity(), super.hddCapacity);
console.log("2번 HDD 용량 : " + this.getHddCapacity(), this.hddCapacity);
this.hddCapacity = "2000G";
console.log("3번 HDD 용량 : " + super.getHddCapacity(), super.hddCapacity);
console.log("4번 HDD 용량 : " + this.getHddCapacity(), this.hddCapacity);
super.ramCapacity = "16G"; // 부모 클래스의 set 프로퍼티로 값을 설정함
console.log("5번 RAM 용량 : " + this.ramCapacity, super.ramCapacity);
this.ramCapacity = "8G"; // 상속 받은 set 프로퍼티로 값을 설정함
console.log("6번 RAM 용량 : " + this.ramCapacity, super.ramCapacity);
}
}
let myDesktop = new Desktop("1000G");
myDesktop.getInfo();
/*
1번 HDD 용량 : 1000G undefined
2번 HDD 용량 : 1000G 1000G
3번 HDD 용량 : 2000G undefined
4번 HDD 용량 : 2000G 2000G
5번 RAM 용량 : 16G 16G
6번 RAM 용량 : 8G 8G
*/

기본 접근 제한자

접근 제한자 선언을 생략할 때 적용되며 , 대체로 public 이다.
constructor에서 접근 제한자가 생략되면 생성자 내부에서만 사용 가능해진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class Account {
balance: number;

// 적금액 얻기(get 프로퍼티 이용)
get getBalance() {
return this.balance;
}

// 적금하기(set 프로퍼티 이용)
set setBalance(amount: number) {
this.balance += amount;
}

// 적금하기(메서드 이용)
deposite(depositeAmount: number) {
this.setBalance = depositeAmount;
}

// 기본 적금(balance)액을 설정하기
constructor(
defaultBalance: number = 0,
protected bankName: string = "happy bank",
readonly interestRate: number = 0.1
) {
this.balance = defaultBalance;
}

// 생성자 매개변수 interestRate는 public으로 설정됐으므로 호출 가능
getInterestRate() {
return this.interestRate;
}

// 생성자 매개변수 defaultBalance는 private(기본 접근 제한자)이므로 호출 불가
getDefaultBalance() {
// return this.defaultBalance;
}
}

class MyAccount extends Account {
// 테스트
constructor() {
super();
this.deposite(1000); // 1000원 적금하기
this.setBalance = 1000; // 1000원 적금하기
console.log(
`2번) 적금 : ${this.balance}원, ${this.getBalance}원 / 이율 : ${
this.interestRate
}, ${this.getInterestRate()}% / 은행명 : ${this.bankName} `
);
}
}

let account = new Account();
console.log(
`1번) 적금 : ${account.balance}원, ${account.getBalance}원 / 이율 : ${
account.interestRate
}, ${account.getInterestRate()}% `
);

let myAccount = new MyAccount();

account.bankName; // Property 'bankName' is protected and only accessible within class 'Account' and its subclasses

접근 제한자를 생략하면 기본적으로 public이 된다.
위 예제에서 defaultBalance는 생성자 내부에서만 사용 가능한 private가 된다.
bankName은 protected로 자식 클래스에서 접근가능하지만 객체를 통한 외부접근은 불가능하다.(account.bankName 불가)
interestRate는 readOnly로 자식 클래스에서도 접근 가능하고 외부 접근도 가능하다.

추상 클래스

추상 클래스는 구현 메서드와 추상 클래스가 동시에 존재할 수 있다.
abstract 키워드를 클래스 선언 앞에 붙여서 선언한다.

1
2
3
4
5
6
abstract class 추상클래스{
abstract 추상메서드();
abstract 추상멤버변수 : string;
public 구현메서드():void{
}
}

인터페이스

인터페이스는 타입스크립트에서만 지원한다. 인터페이스는 컴파일 후에는 사라지는 타입이다.
인터페이스는 선언만 존재하며 멤버 변수와 멤버 메서드를 선언할 수 있지만 접근제한자 설정은 불가능하다.

1
2
3
interface Car{
speed : number;
}

자식 인터페이스sms extends 키워드를 사용해 부모 인터페이스를 상속해 확장 할 수 있다.

1
interface <자식 인터페이스명> extends Car{}

자식 인터페이스는 여러 부모 인터페이스를 다중 상속할수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Car {
speed: number;
}
interface SuperCar {
gear: number;
}

// 다중 상속
interface MyCar extends Car, SuperCar {
setting: boolean;
}

let mycar = <MyCar>{};
mycar.speed = 100;
mycar.gear = 2;
mycar.setting = true;

만약 부모 인터페이스에서 같은 이름의 메서드를 사용하고 이를 자식이 상속받으면 상속된 자식 인터페이스에서 같은 이름의 메서드를 모두 정의해야한다.
인터페이스 정의를 마치면 implements 키워드를 이용해 인터페이스를 구현하는 클래스를 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Dog {
run(): void;
getStatus(): { runningSpeed: number };
}

interface Bird {
fly(): void;
getStatus(): { flySpeed: number };
}

interface DogBird extends Dog, Bird {
getStatus(): { runningSpeed: number; flySpeed: number };
}

인터페이스 역할과 컴파일 결과

인터페이스를 이용하면 객체의 구조를 고정 할 수 있다.
인터페이스는 타입 검사의 용도로 사용하고 컴파일 후에는 제거된다. typeof를 이용해 인터페이스 타입을 조사할 수 없다.

배열 요소 타입을 객체 리터럴 타입으로 사용

배열 요소가 객체 리터럴이면 배열 타입 선언 시, 배열 요소의 타입을 객체 리터럴로 지정할 수 있다.
배열 요소 타입이 선언되어있으므로 이에 맞춰서 json의 속성을 지정해줄 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
let person: { name: string; age: number }[];

person = [
{
name: "lee",
age: 20,
},
{
name: "kim",
age: 30,
},
];

클래스를 배열 타입으로 지정함

1
2
3
4
5
6
7
class Person {
constructor(public name: string, public age: number) {}
}

let person: Person[] = [new Person("lee", 20), new Person("kim", 30)];

console.log(JSON.stringify(person)); // [{"name":"lee","age":20},{"name":"kim","age":30}]

인터페이스를 배열 타입으로 지정함

1
2
3
4
5
6
7
8
9
10
11
interface Person {
name: string;
age: number;
}

let person: Person[] = [
{ name: "lee", age: 20 },
{ name: "kim", age: 30 },
];

console.log(JSON.stringify(person)); // [{"name":"lee","age":20},{"name":"kim","age":30}]

클래스와 인터페이스의 활용

오버라이딩(overriding)

오버라이딩은 부모 클래스에 정의된 메서드를 자식 클래스에 새로 구현하는 것을 말한다.
부모 클래스를 오버라이든 클래스(overridden class)라고 한다.
오버라이든 클래스 안에는 오버라이든 메서드가 존재한다. (부모)
오버라이딩 메서드가 재 정의 되려면 (자식)

  • 오버라이든 메서드의 매개변수 타입은 오버라이딩 메서드의 매개변수 타입과 같거나 상위 타입이여야한다.
  • 오버라이든 메서드의 매개변수 개수가 오버라이딩 메서드의 매개변수 개수와 같거나 많아야한다.(위 조건 충족 시)
오버라이딩 예제
1
2
3
4
5
6
7
8
9
10
11
// Bird : 오버라이든 클래스 (부모)
class Bird{
// 오버라이든 메서드
flight(sppeed : any = 0){} // 부모의 메서드의 타입이 같거나 상위 타입이어야함, 갯수가 같거나 많아야함
}

// Eagle : 오버라이딩 클래스 (자식)
class Eagle extends Bird{
// 오버라이딩 메서드(부모 메서드와 이름이 같음)
flight(sppeed2 : number = 0){} // 매개변수 이름은 달라도 된다. 단 타입은 같거나 하위 타입이여함
}

위 두개의 조건을 만족하지 않으면 오버라이딩 되지 않음

1
2
3
// 위 경우는 메서드의 이름이 같아도 오버라이딩 되지 않는다.
filght(speed : any = 0; distance : number = 0); // 오버라이든 메서드
filght(speed : number = 0; distance : string = ''); // 오버라이딩 메서드

오버로딩(overloading)

메서드 오버로딩은 메서드의 이름이 같지만 매개변수 타입과 개수를 다르게 정의하는 방법을 말한다.

  • 오버라이딩 메서드를 오버로딩하는법

    부모 클래스에 상위 타입을 가지는 오버라이든 메서드를 구현하고, 파생 클래스에서 오버라이딩 메서드를 선언해 구현함

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class SingleTypeChecker {
constructor() {}
typeCheck(value: string): void {
console.log(`${typeof value} : ${value}`);
}
}

class UnionTypeChecker extends SingleTypeChecker {
constructor() {
super();
}

typeCheck(value2: number): void;
typeCheck(value2: string): void; // 같은 타입을 포함해야 한다.
typeCheck(value2: any): void {
if (typeof value2 === "number") {
console.log("숫자 : ", value2);
} else if (typeof value2 === "string") {
console.log("문자열 : ", value2);
} else {
console.log("기타 : ", value2);
}
}
}

let unionTypeChecker = new UnionTypeChecker();
unionTypeChecker.typeCheck(123);
unionTypeChecker.typeCheck("happy");
// unionTypeChecker.typeCheck(true); // 에러

위 예제는 any 타입에 number와 string 만 받을 수 있도록 typeCheck 메서드를 정의함
any 타입이 모든 타입을 받을 수 있을 것 같지만 실제로는 number와 string 만 받을 수 있다.

  • 인터페이스를 클래스에서 구현하여 오버로딩

    인터페이스를 이용해 오버로딩 하라면 인터페이스에 오버로딩할 기본 메서드를 선언하고, 클래스에서 기본 메서드를 구현해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface IPoint {
getX(x: any): any; // 기본 메서드 선언
}
class Point implements IPoint {
getX(x?: number | string): any {
// ?매개변수에 ? 을 추가해 선택 매개변수가 되어 입력값이 없는 호출을 받을 수 있다. p.getX();
if (typeof x === "number") {
return x;
} else if (typeof x === "string") {
return x;
}
}
}

let p = new Point();
console.log(p.getX()); // undefined
console.log(p.getX("hello")); // hello
console.log(p.getX(123)); // 123

다형성

여러 타입을 받아들여 여러 형태를 가지는 것. 타입스크립트에서는 다음 세 가지가 대표적인 예이다.

  1. 클래스의 다형성
  2. 인터페이스의 다형성
  3. 매개변수의 다형성

클래스의 다형성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Planet {
public diameter: number; // 접근 가능

getIsTransduction(): boolean { // 접근 가능
return this.isTransduction;
}

stop(): void { // 오버라이든 메서드 2)
console.log("stop1");
this.isTransduction = false;
}
}

class Earth extends Planet {
public features: string[] = ["soil", "water", "oxyzen"]; // 접근 불가

stop(): void { // 오버라이딩 메서드
console.log("stop2");
this.isTransduction = false;
}
}

let earth: Planet = new Earth(); // 1)

1) 부모 클래스(Planet) 타입으로 지정된 객체 참조변수(earth)는 자식 클래스의 객체(new Earth)를 할당받더라도 실제 동작은 부모 클래스를 기준으로 실행된다.
따라서 earth는 부모 메서드는 접근 가능하지만 자식 클래스의 매개변수(features)에는 접근 할 수 없다.

2) 그런데, stop()은 부모 클래스의 stop() 메서드가 자식 클래스로 오버라이딩 되어있다. 이럴 경우 자식 클래스의 메서드가 우선으로 호출된다.
즉 오버라이든 < 오버라이딩
이처럼, 런타임 시에 호출될 메서드가 결정되는 특성을 런타임 다형성이라고 한다.

인터페이스의 다형성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface IPerson {
age: number;
getAge(): number;
}

class MyPerson implements IPerson {
age: number;

getAge(): number {
return 10;
}
hasClub() {
return true;
}
}

let man: IPerson = new MyPerson(); // 1)
console.log(man.getAge()); // 2)
console.log(man.hasClub()); //3) Property 'hasClub' does not exist on type 'IPerson'.

1) new MyPerson()는 원래 MyPerson 타입이지만 객체 참조변수(man)에 할당되면서 인터페이스(IPerson) 기준으로 접근이 이뤄진다.
2) 따라서, man은 매개변수 age와 getAge()메서드에는 접근 가능하지만,
3) 구현 클래스(MyPerson)에 새롭게 추가된 hasClub() 메서드는 접근 할 수 없다.

매개변수의 다형성

  • 유니언 타입 이용

    매개변수 타입을 유니언 타입을 이용하므로써 객체가 다형성의 성질을 띄도록 만들 수 있다.

  • 인터페이스 타입 이용

클래스에서 getter,setter

Comentarios

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×