내일배움캠프 스파르타 코딩클럽
1. 상속
- 상속은 객체 지향 프로그래밍에서 클래스 간의 관계를 정의하는 중요한 개념이다.
- 상속을 통해 기존 클래스의 속성과 메서드를 물려받아 새로운 클래스를 정의할 수 있다.
- 상속이 있어서 똑같은 코드를 계속 반복적으로 작성할 필요가 없게 된다.
- 위의 예제를 보면 Car 라는 클래스를 생성했으면 머스탱, 골프와 같은 차량들은 Car라는 클래스를 상속받고 자체적으로
필요한 속성 및 메서드를 추가하면 끝이다.
- 상속을 구현하려면 extends 키워드를 사용하면 된다.
- 사용 예시:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound() {
console.log('동물 소리~');
}
}
class Dog extends Animal {
age: number;
constructor(name: string) {
super(name);
this.age = 5;
}
makeSound() {
console.log('멍멍!'); // 부모의 makeSound 동작과 달라요!
}
eat() { // Dog 클래스만의 새로운 함수 정의
console.log('강아지가 사료를 먹습니다.');
}
}
class Cat extends Animal { // Animal과 다를게 하나도 없어요!
}
const dog = new Dog('누렁이');
dog.makeSound(); // 출력: 멍멍!
const cat = new Cat('야옹이');
cat.makeSound(); // 출력: 동물 소리~
- 여기서 Animal을 부모 클래스, Dog를 자식 클래스라고 한다.
- super 키워드는 자식 클래스가 부모 클래스를 참조하는데 사용하는 키워드이다.
1. 즉, 자식클래스에서 생성자를 정의할 때 부모 클래스의 생성자를 호출해야하는데 이때 쓴다.
2. 자식 클래스가 부모 클래스의 생성자나 메서드를 그대로 사용하고 싶다면 자식 클래스에선 다시 작성하지 않아도
된다. 참고: Cat 클래스
- Dog 클래스는 부모의 makeSound 함수의 동작을 새롭게 정의한다. 이것을 오버라이딩이라고 한다.
2. 서브타입, 슈퍼타입
- 상속이라는 개념을 알았으니 이제 서브타입과 슈퍼타입에 대해서도 알아보자.
- 우선, 서브타입의 정의는 다음과 같다.
( 두 개의 타입 A와 B가 있고 B가 A의 서브타입이면 A가 필요한 곳에는 어디든 B를 안전하게 사용할 수 있다. )
- 또한, 슈퍼타입의 정의는 다음과 같다.
( 두 개의 타입 A와 B가 있고 B가 A의 슈퍼타입이면 B가 필요한 곳에는 어디든 A를 안전하게 사용할 수 있다. )
- any는 모든 것의 슈퍼타입이다.
- Animal은 Dog, Cat의 슈퍼타입이고, Dog, Cat은 Animal의 서브타입이다.
3. upcasting, downcasting
upcasting과 downcasting은 슈퍼타입, 서브타입으로 변환할 수 있는 타입 변환 기술이다.
- upcasting
let dog: Dog = new Dog('또순이');
let animal: Animal = dog; // upcasting 발동!
animal.eat(); // 에러. 슈퍼타입(Animal)으로 변환이 되어 eat 메서드를 호출할 수 없어요!
1. 서브타입 => 슈퍼타입으로 변환을 하는 것을 upcasting이라고 한다.
2. 이 경우에는 타입 변환은 암시적으로 이루어져 별도의 타입 변환 구문이 필요가 없다.
- 즉, TypeScript 가 자동으로 해준다는 얘기이다.
- 위의 코드에서도 단지 슈퍼타입 변수에 대입만 했을 뿐이다.
3. upcasting이 필요한 이유는 서브타입 객체를 슈퍼타입 객체로 다루면 유연하게 활용할 수 있기 때문이다.
- 예를 들어, Dog, Cat, Lion 그리고 기타 등등 다양한 동물을 인자로 받을 수 있는 함수를 만들고 싶다면?
1. 올바른 선택: Animal 타입의 객체를 받으면 모두 다 받을 수 있겠다.
2. 잘못된 선택: union으로 새로운 타입을 만들어서 해당 타입의 객체를 받게해야겠다.
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound(): void {
console.log("Some generic animal sound");
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
makeSound(): void {
console.log("Woof!");
}
}
class Cat extends Animal {
constructor(name: string) {
super(name);
}
makeSound(): void {
console.log("Meow!");
}
}
function playWithAnimal(animal: Animal): void {
animal.makeSound();
}
const myDog: Dog = new Dog("Buddy");
const myCat: Cat = new Cat("Whiskers");
// upcasting
const animals: Animal[] = [myDog, myCat];
animals.forEach(playWithAnimal);
// Animal 타입으로 upcasting 되었기 때문에 Animal 타입 변수에 서브타입 객체를 할당할 수 있습니다.
const animal: Animal = myDog;
animal.makeSound(); // Woof!
- downcasting
let animal: Animal;
animal = new Dog('또순이');
let realDog: Dog = animal as Dog;
realDog.eat(); // 서브타입(Dog)로 변환이 되었기 때문에 eat 메서드를 호출할 수 있죠!
1. 슈퍼타입 => 서브타입으로 변환을 하는 것을 downcasting이라고 한다.
2. 이 경우에는 타입 단언 as 키워드로 명시적으로 타입 변환을 해줘야 한다.
3. downcasting 을 할 일은 생각보다 많지는 않습니다만 필요한 경우에 Dog와 같은 서브타입의 메서드를 사용해야 될 때
변신을 해야 될 수 있다.]
4. 타입 가드를 사용하여 타입을 안전하게 변환할 수 있다.
- 예제:
let dog: Dog = new Dog('또순이');
let animal: Animal = dog; // upcasting 발생
animal.makeSound(); // "Woof!" 호출됨
// 타입 가드를 사용하여 안전하게 타입을 확인하고 변환
if (animal instanceof Dog) {
animal.eat(); // "또순이 is eating." 호출됨
} else {
console.log("This animal cannot eat");
}
- instanceof 와 typeof 의 차이
1. typeof : 원시 타입과 함수의 타입을 검사한다.
2. instanceof : 객체가 특정 클래스의 인스턴스인지 검사한다.
4. 추상 클래스
추상 클래스는 자식 클래스들이 특정 방식으로 자라거나 동작하도록 유도하는데 사용된다.
이를 통해 부모 클래스에서 정의된 틀을 강제하고, 자식 클래스가 일정한 구조와 동작을 따르도록 한다.
- 추상 클래스의 정의
( 추상 클래스는 클래스와는 다르게 인스턴스화를 할 수 없는 클래스이다. )
- 추상클래스가 있는 이유
1. 추상 클래스의 목적은 상속을 통해 자식 클래스에서 메서드를 제각각 구현하도록 강제를 하는 용도이다.
2. 물론, 추상 클래스도 최소한의 기본 메서드는 정의를 할 수 있다.
3. 하지만, 골자는 핵심 기능의 구현은 전부 자식 클래스에게 위임을 하는 것이다.
- 추상클래스 사용 방법
1. 추상 클래스 및 추상 함수는 abstract 키워드를 사용하여 정의한다.
2. 추상 클래스는 1개 이상의 추상 함수(메서드)가 있는 것이 일반적이다.
- 예시:
abstract class Shape {
abstract getArea(): number; // 추상 함수 정의!!!
printArea() {
console.log(`도형 넓이: ${this.getArea()}`);
}
}
class Circle extends Shape {
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
getArea(): number { // 원의 넓이를 구하는 공식은 파이 X 반지름 X 반지름
return Math.PI * this.radius * this.radius;
}
}
class Rectangle extends Shape {
width: number;
height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
getArea(): number { // 사각형의 넓이를 구하는 공식은 가로 X 세로
return this.width * this.height;
}
}
const circle = new Circle(5);
circle.printArea();
const rectangle = new Rectangle(4, 6);
rectangle.printArea();
5. 인터페이스
- 인터페이스란?
1. 인터페이스는 TypeScript에서 객체의 타입을 정의하는데 사용된다.
2. 인터페이스는 객체가 가져야 하는 속성과 메서드를 정의한다.
3. 인터페이스를 구현한 객체는 인터페이스를 반드시 준수해야한다. 규약과 같아서 어길 수가 없다.
4. 이렇게 인터페이스를 사용하면 코드의 안정성을 높이고 유지 보수성을 향상시킬 수 있다.
- 추상 클래스와 인터페이스는 어떤 차이가 있을까?
1. 구현부 제공 여부
- 추상 클래스
( 클래스의 기본 구현을 제공한다. )
- 인터페이스
( 객체의 구조만을 정의하고 기본 구현을 제공하지 않는다. )
2. 상속 메커니즘
- 추상 클래스
( 단일 상속만 지원한다. )
- 인터페이스
1. 다중 상속을 지원한다.
2. 즉, 하나의 클래스는 여러 인터페이스를 구현할 수 있다.
3. 구현 메커니즘
- 추상 클래스
( 추상 클래스를 상속받은 자식 클래스는 반드시 추상 함수를 구현해야 한다. )
- 인터페이스
( 인터페이스를 구현하는 클래스는 인터페이스에 정의된 모든 메서드를 전부 구현해야 한다. )
4. 언제 쓰면 좋을까?
- 기본 구현을 제공하고 상속을 통해 확장하는데 초점을 맞추고 싶다면 => 추상 클래스
- 객체가 완벽하게 특정 구조를 준수하도록 강제하고 싶다면 => 인터페이스
결론
- 추상 클래스는 기본 구현을 제공하면서 자식 클래스가 이를 확장할 수 있도록 한다. 주로 코드의 재사용성을 높이고
공통된 기능을 여러 클래스에서 공유할 때 유용하다.
- 인터페이스는 객체가 특정 구조을 갖도록 강제한다. 이를 통해 코드의 일관성을 유지하고, 다양한 클래스가 동일한
인터페이스를 구현함으로써 유연성을 높일 수 있다.
추상 클래스 예제 ( 기본 구현을 제공하고자 할 때는 추상 클래스! )
abstract class Animal {
constructor(protected name: string) {}
// 추상 메서드
abstract makeSound(): void;
// 일반 메서드
move(): void {
console.log(`${this.name} is moving.`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
// 추상 메서드 구현
makeSound(): void {
console.log("Woof!");
}
}
const dog = new Dog("Buddy");
dog.makeSound(); // "Woof!" 출력
dog.move(); // "Buddy is moving." 출력
인터페이스 예제 ( 특정 구조를 강제하고 싶을 때는 인터페이스! )
interface Animal {
name: string;
makeSound(): void;
move(): void;
}
class Dog implements Animal {
constructor(public name: string) {}
makeSound(): void {
console.log("Woof!");
}
move(): void {
console.log(`${this.name} is moving.`);
}
}
class Cat implements Animal {
constructor(public name: string) {}
makeSound(): void {
console.log("Meow!");
}
move(): void {
console.log(`${this.name} is moving.`);
}
}
const dog: Animal = new Dog("Buddy");
dog.makeSound(); // "Woof!" 출력
dog.move(); // "Buddy is moving." 출력
const cat: Animal = new Cat("Whiskers");
cat.makeSound(); // "Meow!" 출력
cat.move(); // "Whiskers is moving." 출력
'TIL' 카테고리의 다른 글
TypeScript 강의 (12) ( 실습 : 도서관 프로그램 ) (0) | 2024.07.31 |
---|---|
TypeScript 강의 (11) ( S.O.L.I.D ) (0) | 2024.07.30 |
TypeScript 강의 (9) ( 클래스 ) (0) | 2024.07.30 |
TypeScript 강의 (8) ( 실습 : 별다방 ) (0) | 2024.07.29 |
TypeScript 강의 (7) ( 유틸리티 타입 ) (0) | 2024.07.29 |