41. 타입스크립트 개념 정리 3

yeah·2023년 7월 26일
0

Today I Learned

목록 보기
28/70
post-thumbnail

Mission: 타입 스크립트 개념 정리

클래스

클래스와 객체

  • 클래스는 객체 지향 프로그래밍(OOP)의 핵심 구성 요소 중 하나이며, 객체를 만들기 위한 틀(template)이다.
  • 클래스는 객체들이 공통으로 가지는 속성(attribute)과 메서드(method)를 정의한다.
  • 속성은 객체의 성질을 결정하며, 예를 들어, 팥 붕어빵과 슈크림 붕어빵은 각각 팥과 슈크림이란 속성을 가질 수 있다.
  • 메서드는 객체의 성질을 변화시키거나 객체에서 제공하는 기능들을 사용하는 창구이다.

    클래스 정의하기
  • TypeScript에서 클래스를 정의하기 위해 class 키워드를 사용한다.
  • 클래스의 속성과 메서드를 정의하고, new 키워드를 사용하여 객체를 생성할 수 있다.

    클래스 접근 제한자
  • 클래스에서 속성과 메서드에 접근 제한자를 사용해 접근을 제한할 수 있다.

TypeScript에서 제공하는 접근 제한자

public, private, protected

  • public: 클래스 외부에서도 접근 가능한 접근 제한자로 기본 설정이다.
  • private: 클래스 내부에서만 접근 가능한 접근 제한자로, 외부에서 직접적으로 객체의 속성을 변경할 수 없게 한다.
  • protected: 클래스 내부와 해당 클래스를 상속받은 자식 클래스에서만 접근 가능한 접근 제한자이다.
// 클래스 예시 코드
class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  sayHello() {
    console.log(`안녕하세요! 제 이름은 ${this.name}이고, 나이는 ${this.age}살입니다.`);
  }
}
const person = new Person('Spartan', 30);
person.sayHello();

상속

상속이란?

  • 상속은 객체 지향 프로그래밍에서 클래스 간의 관계를 정의하는 개념으로, 기존 클래스의 속성과 메서드를 물려받아 새로운 클래스를 정의하는 것이다.
  • extends 키워드를 사용하여 상속을 구현한다.

    서브타입, 슈퍼타입
  • 서브타입과 슈퍼타입은 타입 간의 관계를 나타낸다.
  • 서브타입은 다른 타입의 서브클래스로 어디든 해당 타입의 객체를 안전하게 사용할 수 있다.
  • 슈퍼타입은 다른 타입의 슈퍼클래스로 어디든 해당 타입을 안전하게 사용할 수 있다.
  • any는 모든 것의 슈퍼타입이며, Animal은 Dog, Cat의 슈퍼타입이고, Dog, Cat은 Animal의 서브타입이다.

    upcasting, downcasting
  • upcasting은 서브타입을 슈퍼타입으로 변환하는 것을 의미한다.
  • 서브타입 객체를 슈퍼타입으로 다루면 유연하게 활용할 수 있다.
  • downcasting은 슈퍼타입을 서브타입으로 변환하는 것을 의미하며, 명시적인 타입 변환(as 키워드)이 필요하다.
// 상속 예시 코드
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(); // 출력: 동물 소리~

추상 클래스

추상 클래스란?

  • 추상 클래스는 클래스와는 다르게 인스턴스화할 수 없는 클래스이다.
  • 추상 클래스의 주요 목적은 상속을 통해 자식 클래스에서 메서드를 구현하도록 강제하는 것이다.
  • 추상 함수를 가질 수 있으며, 최소한의 기본 메서드를 정의할 수도 있다.

    추상 클래스 사용 방법
  • 추상 클래스 및 추상 함수는 abstract 키워드를 사용하여 정의한다.
  • 추상 클래스는 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(); // 출력: 도형 넓이: 78.53981633974483
const rectangle = new Rectangle(4, 6);
rectangle.printArea(); // 출력: 도형 넓이: 24

인터페이스

인터페이스란?

  • 인터페이스는 TypeScript에서 객체의 타입을 정의하는데 사용된다.
  • 인터페이스는 객체가 가져야 하는 속성과 메서드를 정의한다.
  • 인터페이스를 구현한 객체는 인터페이스를 반드시 준수해야하며, 이를 통해 코드의 안정성과 유지 보수성을 향상시킬 수 있다.

    추상 클래스와 인터페이스 차이
  1. 구현부 제공 여부
    추상 클래스는 클래스의 기본 구현을 제공한다.
    인터페이스는 객체의 구조만을 정의하고 기본 구현을 제공하지 않는다.

  2. 상속 메커니즘
    추상 클래스는 단일 상속만 지원한다.
    인터페이스는 다중 상속을 지원한다.
    즉, 하나의 클래스는 여러 인터페이스를 구현할 수 있다.

  3. 구현 메커니즘
    추상 클래스를 상속받은 자식 클래스는 반드시 추상 함수를 구현해야 한다.
    인터페이스를 구현하는 클래스는 인터페이스에 정의된 모든 메서드를 전부 구현해야 한다.

    언제 쓰면 좋을까?
  • 추상 클래스는 기본 구현을 제공하고 상속을 통해 확장하는데 초점을 맞추고 싶을 때 사용한다.
  • 객체가 완벽하게 특정 구조를 준수하도록 강제하고 싶을 때는 인터페이스를 사용한다.

객체 지향 설계 원칙 - S.O.L.I.D

S(SRP. 단일 책임 원칙)

  • 클래스는 하나의 책임만 가져야 한다는 원칙이다.
  • 하나의 클래스는 한 가지 역할에 집중하여 유지 보수성을 높인다.
  • 클래스의 기능을 분리하여 적절한 책임을 갖도록 한다.

    O(OCP. 개방 폐쇄 원칙)
  • 클래스는 확장에 대해서는 열려 있어야 하고 수정에 대해서는 닫혀 있어야 한다.
  • 기존 코드를 변경하지 않고도 기능을 확장할 수 있도록 설계한다.
  • 인터페이스나 상속을 통해 기능을 확장하는 방법을 사용한다.

    L(LSP. 리스코프 치환 원칙)
  • 서브타입은 기반이 되는 슈퍼타입을 대체할 수 있어야 한다는 원칙이다.
  • 자식 클래스는 부모 클래스와 호환되어야 하며, 부모 클래스의 기능을 수정하지 않고 확장한다.

    I(ISP. 인터페이스 분리 원칙)
  • 클래스는 자신이 사용하지 않는 인터페이스의 영향을 받지 않아야 한다.
  • 필요한 인터페이스만 정의하여 클래스의 의존성을 최소화하고 무의미한 메소드의 구현을 막는다.

    D(DIP. 의존성 역전 원칙)
  • 하위 수준 모듈(구현 클래스)보다 상위 수준 모듈(인터페이스)에 의존해야 한다는 원칙이다.
  • 상위 수준 모듈에 의존하여 유연하고 확장 가능한 설계를 구현한다.
// 예시 1: 인터페이스 정의
interface IUser {
  id: number;
  name: string;
  email: string;
  getInfo(): string;
}
// 예시 2: 인터페이스 구현
class User implements IUser {
  constructor(public id: number, public name: string, public email: string) {}
  getInfo(): string {
    return `User ID: ${this.id}, Name: ${this.name}, Email: ${this.email}`;
  }
}
const user1 = new User(1, "John Doe", "john@example.com");
console.log(user1.getInfo());
// 예시 3: S.O.L.I.D 원칙 적용 예시
// 단일 책임 원칙(SRP) 예시
class UserService {
  constructor(private db: Database) {}
  getUser(id: number): User {
    // 사용자 조회 로직
    return this.db.findUser(id);
  }
  saveUser(user: User): void {
    // 사용자 저장 로직
    this.db.saveUser(user);
  }
}
class EmailService {
  sendWelcomeEmail(user: User): void {
    // 이메일 전송 로직
    console.log(`Sending welcome email to ${user.email}`);
  }
}
// 개방 폐쇄 원칙(OCP) 예시
interface IStorage {
  save(data: any): void;
}
class LocalStorage implements IStorage {
  save(data: any): void {
    // 로컬 스토리지에 데이터 저장하는 로직
    console.log("Saving data to local storage:", data);
  }
}
class CloudStorage implements IStorage {
  save(data: any): void {
    // 클라우드 스토리지에 데이터 저장하는 로직
    console.log("Saving data to cloud storage:", data);
  }
}
// 리스코프 치환 원칙(LSP) 예시
abstract class Bird {
  abstract move(): void;
}
class FlyingBird extends Bird {
  move() {
    console.log("펄럭펄럭~");
  }
}
class NonFlyingBird extends Bird {
  move() {
    console.log("뚜벅뚜벅!");
  }
}
class Penguin extends NonFlyingBird {}
// 인터페이스 분리 원칙(ISP) 예시
interface IEmailService {
  sendWelcomeEmail(user: User): void;
}
class EmailService implements IEmailService {
  sendWelcomeEmail(user: User): void {
    console.log(`Sending welcome email to ${user.email}`);
  }
}
// 의존성 역전 원칙(DIP) 예시
class UserService {
  constructor(private storage: IStorage) {}
  saveUser(user: User): void {
    // 사용자 저장 로직
    this.storage.save(user);
  }
}
const localDb = new LocalStorage();
const cloudDb = new CloudStorage();
const user2 = new User(2, "Jane Smith", "jane@example.com");
const userService1 = new UserService(localDb);
const userService2 = new UserService(cloudDb);
userService1.saveUser(user2);
userService2.saveUser(user2);
profile
기록과 회고

0개의 댓글