※ Javascript에서의 객체지향과 크게 다르지 않습니다. TypeScript만의 객체지향을 말하는것이 아니니 주의해 주세요.
1. 클래스(Class)
TypeScript에서 클래스는 아래와 같이 생성합니다.
class Car {
color: string = 'red';
speed: number = 0;
Drive(acc): void {
this.speed += acc;
console.log('speed : ', this.speed);
};
Stop(): void {
this.speed = 0;
console.log('stop');
};
};
const car = new Car();
console.log(car.color);
Car라는 클래스는 color와 speed라는 멤버변수와 Drive(), Stop()라는 함수를 가지고 있습니다. 클래스 안에서 변수는 const나 let같은걸 붙이지 않고 함수 또한 function같은 수식어를 붙이지 않습니다. 또한 클래스안에서 멤버변수에 접근하는 경우는 this.을 사용해 접근해야 합니다.
클래스에서 인스턴스를 생성하려면 new 키워드를 통해 생성하며 각 멤버나 함수는 생성된 인스턴스를 통해 접근해야 합니다. 이때 생성자는 constructor() 함수를 사용합니다.
class Car {
constructor(color: string) {
this.color = color;
};
color: string = 'red';
speed: number = 0;
Drive(acc): void {
this.speed += acc;
console.log('speed : ', this.speed);
};
Stop(): void {
this.speed = 0;
console.log('stop');
};
};
const car = new Car('blue');
생성자는 클래스의 인스턴스를 생성할때 호출되며 생성자를 통해 필요한 초기값을 전달하고 사용할 수 있습니다. 생성자에서 하나 특이한 점은 생성자를 통해서 특정 변수를 정의해 둘 수 있다는 것입니다.
class Car {
constructor(public color: string) {
this.color = color;
};
};
const car = new Car('blue');
console.log(car.color);
본래 color라는 변수는 함수 내부에서 직접 선언되지 않았지만 생성자를 통해서 public color가 정의되었으므로 color는 클래스의 public 멤버변수가 되는 것입니다. 외부 공개 여부에 따라 private으로도 수식될 수 있습니다.
클래스에서 사용되는 color나 speed변수는 클래스의 인스턴스를 생성할때마다 만들어집니다. 각각의 인스턴스마다 해당 변수값을 설정하고 만들어 지는데 만약 동일한 값만이 사용되는 변수가 있다면 이 변수를 static으로 만들어 각 인스턴스마다 값을 공유하는 형태로 만들어 둘 수 있습니다.
class Car {
constructor(color: string) {
this.color = color;
};
color: string = 'red';
speed: number = 0;
static stopSpeed: number = 0;
Drive(acc): void {
this.speed += acc;
console.log('speed : ', this.speed);
};
Stop(): void {
this.speed = Car.stopSpeed;
console.log('stop');
};
};
const car = new Car('blue');
car.Stop();
console.log(car.speed);
변수는 특별히 지정하지 않으면 인스턴스마다 생성되는 변수가 되며 앞에 static을 붙이게 되면 클래스단위의 변수가 됩니다. static을 클래스 안에서 접근할때는 thsi가 아닌 클래스이름을 사용해 접근합니다.
이러한 static은 변수가 아닌 함수를 통해서도 구현할 수 있습니다.
class Car {
constructor(color: string) {
this.color = color;
};
color: string = 'red';
speed: number = 0;
static stopSpeed: number = 0;
static GetCar(color: string) : Car {
return new Car(color);
}
Drive(acc): void {
this.speed += acc;
console.log('speed : ', this.speed);
};
Stop(): void {
this.speed = Car.stopSpeed;
console.log('stop');
};
};
const car = Car.GetCar('blue');
단 static함수는 인스턴스가 아닌 클래스에서 호출되는 함수이므로 클래스내부의 자원들에는 접근하지 않고 완전히 독립적인 동작을 하는 함수에만 적용해야 합니다. 이러한 static함수나 변수는 인스턴스마다 할당되지 않으므로 메모리낭비를 막을 수 있지만 변수의 값이 바뀌면 이를 공유하고 있는 인스턴스 전체에 영향이 갈 수 있으므로 이 부분도 주의가 필요합니다.
2. 캡슐화 (Encapsulation)
클래스 내부에 속한 변수나 함수는 모두 별도로 지정하지 않으면 public이 기본입니다. public은 클래스외부에서 자유롭게 접근할 수 있는 상태를 말합니다.
const car = new Car('blue');
car.speed = -10;
console.log(car.speed);
그러나 경우에 따라 외부에서가 아닌 클래스 안에서만 사용되어야 하는 경우도 있는데 이런 변수나 함수는 private으로 수식해야 합니다.
private speed: number = 0;
...
...
const car = new Car('blue');
car.speed = -10; //private이므로 오류
private변수를 외부에서 접그시켜야 한다면 public보다는 별도의 함수를 사용해 접근할 수 있도록 하는편이 안전합니다.
AddSpeed(speed: number) {
if (speed < 0) {
throw new Error('속도는 0보다 작을 수 없음');
}
}
public와 private이외에 protected로도 수식할 수 있는데 protected는 상속받은 클래스에서만 사용할 수 있는 것을 말합니다. 이 부분은 아래 추상화에서 알아볼 것입니다.
3. get / set
위와 같이 변수를 private로 선언하고 AddSpeed처럼 별도의 함수를 만들어 private변수에 접근해 특정 값을 처리하고 있는데 이와 같은 동작에 대해서는 함수를 사용하는 대신 값을 가져오고 설정하는 용도로 특화된 get/set을 이용할 수도 있습니다.
get GetSpeed(): number {
return this.speed;
};
set SetSpeed(speed: number) {
this.speed = speed;
};
...
...
const car = new Car('blue');
car.SetSpeed = 10;
console.log(car.GetSpeed);
get은 특정값을 가져오도록 하는 것입니다. 함수처럼 선언되며 get을 호출할때는 괄호를 빼고 get이름만 지정해서 가져옵니다. 값을 설정할때는 set을 사용하며 속성처럼 호출해 필요한 값을 설정하면 됩니다.
4. 인터페이스 (interface)
만들고자 하는 클래스에서 특정 함수를 구현해야 한다면 인터페이스를 사용해야 합니다.
class Car implements ICar {
constructor(private color: string, private speed: number) {
this.color = color;
this.speed = speed;
};
UpSpeed(speed: number): number {
this.speed += speed;
return this.speed;
};
Driving() : void {
console.log('운전중..');
};
};
interface ICar {
UpSpeed(speed: number): number;
}
const car = new Car('blue', 100);
console.log(car.UpSpeed(10));
인터페이스는 interfaece키워드를 통해서 구현되며 통상 인터페이스명에 I를 붙여 인터페이스임을 표현하곤 합니다.
예제에서 ICar라는 인터페이스는 number형의 데이터를 받아 그 결과로 number형을 반환하는 UpSpeed함수를 만들어야 한다고 규정하고 있습니다. 그리고 Car클래스에서는 implements ICar를 통해서 UpSpeed함수의 실체를 구현하고 있습니다.
Car클래스는 현재 UpSpeed()와 Driving()라는 2개의 함수를 구현하고 있습니다. 따라서 이를 통해 생성된 인스턴스 객체는 UpSpeed()와 Driving() 2개의 함수를 호출할 수 있습니다. 하지만 Car클래스는 ICar의 함수를 구현하고 있으므로 인스턴스를 ICar형으로 받게되면 ICar에서 정의된 함수만 호출할 수 있도록 강제할 수 있습니다. 클래스에서 private으로 일일이 함수를 골래내지 않아도 인터페이스만을 사용해 필요한 함수만 노출하는 것입니다.
const car: ICar = new Car('blue', 100);
console.log(car.UpSpeed(10));
필요하다면 인터페이스는 다수의 인터페이스를 만들어 이를 클래스에서 구현하도록 할 수 있으며
class Car implements ICar, Icar2 {
같은 이름으로 인터페이스를 여러개 만드는 것도 가능합니다.
interface aaa {
aaa: number;
bbb: string;
};
interface aaa {
ccc: string;
};
다만 이와같이 하는 경우 aaa란 인터페이스를 구현할때 aaa, bbb, ccc모두를 구현해야 합니다.
5. 추상화 (Abstract)
인터페이스와 비슷한 개념으로 클래스를 상속받아 다른 클래스를 구현할때 부모클래스의 함수를 재정의하도록 강제하는 경우가 있습니다. 이런 클래스를 추상클래스라고 합니다.
abstract class Car implements ICar {
constructor(private color: string, private speed: number) {
this.color = color;
this.speed = speed;
};
UpSpeed(speed: number): number {
this.speed += speed;
return this.speed;
};
protected abstract Driving() : void;
};
추상클래스는 class앞에 abstract키워를 붙여 사용하는데 이 클래스는 자체로 인스턴스생성이 불가능합니다. 그리고 클래스 내부를 보면 Driving()이라는 함수가 있는데 인터페이스처럼 함수의 실체는 없고 그냥 이름과 매개변수, 반환형식만 정의되어 있습니다. 이제 Car를 상속받는 클래스는 Driving() 함수를 자체적으로 만들어야 합니다.
class Truck extends Car {
private weight = 0;
constructor(weight: number, color: string, speed: number,) {
super(color, weight - speed);
this.weight = weight;
};
Driving() : void {
console.log('짐을 싣고 운전중...');
};
};
Car를 상속받는 Truck클래스는 내부에서 Driving()함수를 구현하고 있습니다. 이 처럼 Truck안에서 Driving()을 구현하지 않으면 오류가 발생합니다.
6. 상속 (Inheritance)
클래스의 상속은 extends를 사용합니다. 상속을 하고 나면 private을 제외하고 대부분의 부모요소를 상속받게 됩니다.
class Truck extends Car {
}
const truck: Truck = new Truck('blue', 100);
console.log(truck.UpSpeed(10));
상속받은 자식 클래스에서는 부모의 함수를 다음과 같이 재정의 할 수 있습니다.
class Truck extends Car {
Driving() : void {
console.log('짐을 싣고 운전중...');
};
}
const truck: Truck = new Truck('blue', 100);
truck.Driving();
또는 super를 통해 부모클래스의 함수를 호출하는 것도 가능합니다.
class Truck extends Car {
Driving() : void {
super.Driving();
console.log('짐을 싣고 운전중...');
};
}
super는 생성자에서도 사용됩니다.
class Truck extends Car {
private weight = 0;
constructor(weight: number, color: string, speed: number) {
super(color, weight - speed);
this.weight = weight;
}
Driving() : void {
super.Driving();
console.log('짐을 싣고 운전중...');
};
}
const truck: Truck = new Truck(300, 'red', 100);
다만 상속은 2개이상의 클래스에서는 상속할 수 없습니다. 만약 각각의 클래스에 있는 기능이 필요하다면 하나의 클래스를 상속받아 특정 기능을 구현할때 다른 클래스의 객체를 생성자로 가져와 구현할 수도 있습니다. 클래스의 상속은 2개 이상에서는 할 수 없으므로 이를 해결하기 위한 것이기도 하며 의존성주입과 비슷한 방법이기도 합니다.
class Truck extends Car {
private weight = 0;
constructor(weight: number, color: string, speed: number, private boat: Boat) {
super(color, weight - speed);
this.weight = weight;
};
Driving() : void {
super.Driving();
boat.Moving(100);
console.log('짐을 싣고 운전중...');
};
};
const boat: Boat = new Boat();
const truck: Car = new Truck(300, 'red', 100, boat);
truck.Driving();
truck을 구현하는데 boat에 있는 Moving()기능이 필요한 상황이라 가정해 보면 Truck에서 Car와 Boat클래스를 동시에 상속받을 수 없으므로 Truk의 생성자에서 boat객체를 가져와 boat의 Moving()기능을 구현합니다.
다만 클래스끼리 강력하게 결합되는 문제가 있어 이를 피하고자 인터페이스를 활용하기도 합니다.
class Car implements ICar {
constructor(private color: string, private speed: number) {
this.color = color;
this.speed = speed;
};
UpSpeed(speed: number): number {
this.speed += speed;
return this.speed;
};
Driving() : void {
console.log('운전중..');
};
};
interface ICar {
UpSpeed(speed: number): number;
};
class Boat implements ICar {
private color: string = 'blue';
private knot: number = 0;
Moving(speed: number): number {
this.knot = speed;
console.log('배가 움직임');
return this.knot;
};
UpSpeed(speed: number): number {
this.knot += speed;
return this.knot;
};
};
Car와 Boat모두 ICar라는 인터페이스 사용하고 있습니다. 따라서 Car와 Boat는 ICar이라는 인터페이스 이기도 합니다.
class Truck extends Car {
private weight = 0;
constructor(weight: number, color: string, speed: number, private boat: ICar) {
super(color, weight - speed);
this.weight = weight;
};
Driving() : void {
super.Driving();
console.log(boat.UpSpeed(150));
console.log('짐을 싣고 운전중...');
};
};
const boat: Boat = new Boat();
const truck: Truck = new Truck(100, 'red', 150, boat);
truck.Driving();
Truck에서 ICar에 있는 UpSpeed()가 필요하다면 생성자에는 ICar의 객체를 받을 수 있도록 하고 이를 받아내면 ICar에 구현된 메서드를 호출할 수 있게 됩니다. 예제에서는 boat의 UpSpeed()를 사용했는데 Car의 UpSpeed()가 필요하다면 Boat대신 Car의 객체를 전달해 주기만 하면 됩니다.
7. 다형성 (Polymorphism)
위에서 처럼 인터페이스를 사용하거나 하나의 클래스에서 여러 자식클래스가 생성되었을때 자식 클래스는 구현해야할 함수를 가지고 있는 인터페이스가 되거나 부모클래스가 될 수 있습니다.
const truck: Car = new Truck(300, 'red', 100);
truck.Driving();
이처럼 하나의 어떤 형태에서 다양한 다른 형태가 만들어 질 수 있다고 하여 이를 '다형성'이라고 합니다.
'Web > TypeScript' 카테고리의 다른 글
[TypeScript] Visual Studio에서 TypeScript사용하기 (0) | 2022.05.06 |
---|---|
[TypeScript] tsconfig.json (0) | 2021.03.13 |
[TypeScript] 일반화 (Generic) (0) | 2021.03.11 |
[TypeScript] 데이터 타입 (0) | 2021.03.10 |
[TypeScript] 개요및 준비 (0) | 2021.03.10 |