본문 바로가기

카테고리 없음

Study_240625 ( TS 객체 리터럴 , 유틸리티 타입 , 간단한 카페 코드, 객체 지향 프로그램 )

object literal ( 객체 리터럴 ) : const , let 사용 가능 -> 키+값 페어로 구성된 객체 정의 방식

예시 코드 -> 어떤 타입의 값도 대입 가능 -> 코드 사용 전 값이 할당 되어야하므로 런타임 에러 방지 가능

const obj = {
  a: [1, 2, 3],
  b: "b",
  c: 4,
};

enum과 사용 구별

enum은 간단한 상수 값 그룹 관리 시 적합 / 상수이므로 각 멤버의 값이 변하면 안됨

객체 리터럴은 멤버의 값이나 데이터 타입 변경 가능 / 복잡한 구조와 다양한 데이터 타입 사용 시 사용

 

유틸리티 타입

Partial<T> : 타입 T(제네릭)의 모든 속성을 선택적으로 만듦 -> 기존 타입의 일부 속성만 제공하는 객체 생성

예시 코드

// interface는 TS에서 객체의 구조 정의 시 사용 -> 객체가 어떤 속성과 타입을 가져야하는지 명확하게 규정 가능
interface Person {
  name: string;
  age: number;
}

const updatePerson = (person: Person, fields: Partial<Person>): Person => {
  return { ...person, ...fields };
};

const person: Person = { name: "Spartan", age: 30 };
// name과 age 속성 중 택1 혹은 둘 다 인자로 구성 가능
const changedPerson = updatePerson(person, { age: 31 });

 

Required<T> : Partial<T> 타입과 반대로 타입T의 모든 속성을 필수적으로 만듦

-> T타입 객체에 정의된 모든 속성이 반드시 전부 제공 되는 객체를 생성해야함

예시 코드

interface Person {
  name: string;
  age: number;
  address?: string; // 속성 명 뒤에 붙는 ?는 선택적 속성 -> 있어도 되고 없어도 됨
}
// Required 사용 시 address도 필수로 입력 해야함
type RequiredPerson = Required<Person>;

 

Readonly<T> : 모든 속성을 읽기 전용(객체의 상수(const)화)으로 만듦

-> readonly 타입의 속성들로 구성된 객체가 아니어도 완전한 불변 객체로 취급 가능

예시 코드 -> 초기 Data... 객체는 불변X (host가 readonly가 아님) -> Readonly타입으로 불변 객체가 됨

interface DatabaseConfig {
  host: string;
  readonly port: number; // 인터페이스에서도 readonly 타입 사용 가능
}

const mutableConfig: DatabaseConfig = {
  host: "localhost",
  port: 3306,
};

const immutableConfig: Readonly<DatabaseConfig> = {
  host: "localhost",
  port: 3306,
};

mutableConfig.host = "somewhere";
immutableConfig.host = "somewhere"; // 오류!

 

Pick<T,K> : 타입 T에서 K(일부)속성들만 선택하여 새로운 타입을 만듦

-> 타입의 일부 속성만을 포함하는 새로운 타입을 생성

예시 코드 -> Sub...타입은 Person 인터페이스에서 name,age 속성만 선택하여 구성한 새로운 타입임

interface Person {
  name: string;
  age: number;
  address: string;
}

type SubsetPerson = Pick<Person, "name" | "age">;

const person: SubsetPerson = { name: "Spartan", age: 30 };

 

Omit<T,K> : 타입 T에서 K 속성들만 제외하여 새로운 타입을 만듦

-> Pick과 반대 -> 특정 타입을 제거한 새로운 타입을 생성

예시 코드 -> Sub...타입은 Person 타입( interface라 부르는 것이 적절하지만 interface가 타입의 일부이므로 타입이라고 부르는 것도 맞긴 함 ) 에서 address 속성만 제외한 새로운 타입임

interface Person {
  name: string;
  age: number;
  address: string;
}

type SubsetPerson = Omit<Person, "address">;

const person: SubsetPerson = { name: "Alice", age: 30 };

 

간단한 카페 코드

interface User {
  id: number;
  name: string;
  role: "admin" | "customer";
}

interface Drink {
  name: string;
  price: number;
}

interface Order {
  orderId: number;
  customerId: number;
  customerName: string;
  drinkName: string;
  // union 타입 -> 세가지 string 중 하나를 가질 수 있다는 뜻
  // string 으로 쓰지 않는건 다른 문자열을 막기 위해서임
  status: "placed" | "completed" | "picked-up";
}

// 관리 할 데이터 선언
let drinks: Drink[] = [];
let orders: Order[] = [];

const isAdmin = (user: User): boolean => {
  return user.role === "admin";
};

const isCustomer = (user: User): boolean => {
  return user.role === "customer";
};

const addDrink = (user: User, name: string, price: number): void => {
  if (!isAdmin(user)) {
    console.log("권한이 없습니다.");
    return;
  }
  const newDrink: Drink = { name, price };
  drinks.push(newDrink);
};

const removeDrink = (user: User, drinkName: string): void => {
  if (!isAdmin(user)) {
    console.log("권한이 없습니다.");
    return;
  }
  drinks = drinks.filter((drink) => drink.name !== drinkName);
};

const getDrinks = (user: User): Drink[] => {
  if (!user) return [];

  return drinks;
};

const findDrink = (drinkName: string): Drink | undefined => {
  return drinks.find((drink) => drink.name === drinkName);
};

// 주문Id를 반환
const placeOrder = (user: User, drinkName: string): number => {
  if (!isCustomer(user)) {
    console.log("권한이 없습니다.");
    return -1;
  }

  const drink = findDrink(drinkName);
  if (!drink) {
    console.log("해당 음료가 없습니다.");
    return -1;
  }

  const newOrder: Order = {
    orderId: orders.length + 1,
    customerId: user.id,
    customerName: user.name,
    drinkName,
    status: "placed",
  };
  orders.push(newOrder);

  return newOrder.orderId;
};

const completeOrder = (user: User, orderId: number): void => {
  if (!isAdmin(user)) {
    console.log("권한이 없습니다.");
    return;
  }

  const order = orders.find((order) => order.orderId === orderId);
  if (order) {
    order.status = "completed";
    console.log(
      `[고객 메시지] ${order.customerName}님 주문하신 ${order.drinkName}나왔습니다`
    );
  }
};

const pickUpOrder = (user: User, orderId: number): void => {
  if (!isCustomer(user)) {
    console.log("권한이 없습니다.");
    return;
  }

  const order = orders.find(
    (order) => order.orderId === orderId && order.customerId === user.id
  );
  if (order && order.status === "completed") {
    order.status = "picked-up";
    console.log(
      `[어드민 메시지] 고객 ID[${order.customerId}]님이 주문 ID[${orderId}]을 수령했습니다.`
    );
  }
};

function main() {
  const admin: User = {
    id: 1,
    name: "바리스타",
    role: "admin",
  };

  // 유저 생성
  const member1: User = {
    id: 2,
    name: "르탄이",
    role: "customer",
  };

  const member2: User = {
    id: 3,
    name: "꿈꾸는개발자",
    role: "customer",
  };

  // 음료 등록
  addDrink(admin, "아메리카노", 4000);
  addDrink(admin, "카페라떼", 4500);
  addDrink(admin, "에스프레소", 3000);

  // 음료 삭제
  removeDrink(admin, "에스프레소");

  console.log(
    `안녕하세요~ ${
      member1.name
    } 고객님! 별다방에 오신 것을 환영합니다. 저희는 ${JSON.stringify(
      getDrinks(member1)
    )}를 판매하고 있습니다.`
  );
  // 음료 주문
  const orderId1 = placeOrder(member1, "아메리카노");
  if (orderId1 > 0) {
    setTimeout(() => {
      // 음료 제작 완료
      completeOrder(admin, orderId1);
      // 음료 수령
      pickUpOrder(member1, orderId1);
    }, 1000);
  }

  console.log(
    `안녕하세요~ ${
      member2.name
    } 고객님! 별다방에 오신 것을 환영합니다. 저희는 ${JSON.stringify(
      getDrinks(member2)
    )}를 판매하고 있습니다.`
  );
  // 음료 주문
  const orderId2 = placeOrder(member2, "카페라떼");
  if (orderId2 > 0) {
    setTimeout(() => {
      // 음료 제작 완료
      completeOrder(admin, orderId2);
      // 음료 수령
      pickUpOrder(member2, orderId2);
    }, 3000);
  }
}

main();

 

 

클래스

클래스는 객체 지향 프로그래밍 핵심 요소 중 하나이며 클래스는 객체를 만들기 위한 틀

☑️ 클래스의 구성 요소

  • 클래스에서는 같은 종류의 객체들이 공통으로 가지는 속성(attribute)과 메서드(method)를 정의
    • 속성객체의 성질을 결정하는 것
      • 예를 들어, 붕어빵은 팥이란 속성이 있는 팥 붕어빵 과 슈크림 이란 속성이 있는 슈크림 붕어빵이 있음
    • 메서드객체의 성질을 변화시키거나 객체에서 제공하는 기능들을 사용하는 창구.
      • 붕어빵 주인은 붕어빵을 팥 붕어빵에서 슈크림 붕어빵으로 전환 가능.
      • 붕어빵을 사는 고객들은 팥 붕어빵, 슈크림 붕어빵의 가격을 알 수 있음.

☑️ 객체란?

객체는 클래스를 기반으로 생성되며 클래스의 인스턴스(instance)라고도 함

  1. 클래스 정의하기
  • ☑️ 클래스 및 객체 정의 방법
    • TypeScript에서 클래스를 정의하려면 class 키워드를 사용하면 됨
    • 클래스의 속성과 메서드를 정의하고, new 키워드를 사용하여 객체를 생성 가능.
    • 예시 코드
      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()
    • 생성자(constructor)
      • 생성자는 클래스의 인스턴스를 생성하고 초기화하는데 사용되는 특별한 메서드
      • 생성자는 클래스 내에서 constructor라는 이름으로 정의됨
      • 생성자는 인스턴스를 생성할 때 자동으로 호출됨.
      • 생성자는 클래스 내에 오직 하나만 존재 가능.
      • 보통, 생성자로 객체 속성을 초기화 하는것 뿐 아니라 객체가 생성이 될 떄 꼭 되어야 하는 초기화 로직을 집어넣기도 함
        • 예를 들어, DBConnector라는 클래스가 있다면 이 클래스 타입의 객체가 생성이 될 때 생성자에서 DB 연결을 미리 해주면 편할 것임

☑️ 접근 제한자

  • 클래스에서는 속성메서드접근 제한자를 사용해 접근을 제한 가능.
  • TypeScript에서는 다음의 접근 제한자들을 제공
    • public
      • 클래스 외부에서도 접근이 가능한 접근 제한자
      • 접근 제한자가 선언이 안되어있다면 기본적으로 접근 제한자는 public
      • 보통은 클래스의 함수 중 민감하지 않은 객체 정보를 열람할 때나 누구나 해당 클래스의 특정 기능을 사용해야 할 때 많이 쓰임
    • private
      • 클래스 내부에서만 접근이 가능한 접근 제한자.
      • 보통은 클래스의 속성은 대부분 private으로 접근 제한자를 설정
        • 즉, 외부에서 직접적으로 객체의 속성을 변경할 수 없게 제한하는 것
      • 클래스의 속성을 보거나 편집하고 싶다면 별도의 getter/setter 메서드를 준비해놓는 것이 관례
    • protected
      • 클래스 내부와 해당 클래스를 상속받은 자식 클래스에서만 접근이 가능한 접근 제한자.
  • 사용 사례
    class Person {
      private name: string;
      private age: number;

      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }

      public sayHello() {
        console.log(
          `안녕하세요! 제 이름은 ${this.name}이고, 나이는 ${this.age}살입니다.`
        );
      }
    }

상속 ( inheritance )

  • 상속은 객체 지향 프로그래밍에서 클래스 간의 관계를 정의하는 중요한 개념
  • 상속을 통해 기존 클래스의 속성과 메서드를 물려받아 새로운 클래스를 정의 가능
  • 상속이 있어서 똑같은 코드를 계속 반복적으로 작성할 필요 없음
  • 상속을 구현하려면 extends 키워드를 사용하면 됨
  • 예시 코드 -> Animal이 부모 클래스 , Dog가 자식 클래스 / super 키워드는 자식 클래스가 부모 클래스를 참조하는데 사용 -> 자식 클래스에서 생성자를 정의 할 때 부모 클래스의 생성자를 호출해야 하는데 이 때 사용
  • 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(); // 출력: 동물 소리~
     
  • Dog 클래스는 부모의 makeSound 함수의 동작을 새롭게 정의 -> 오버라이딩 이라 부름

서브타입 , 슈퍼타입

upcasting과 downcasting은 슈퍼타입, 서브타입으로 변환할 수 있는 타입 변환 기술

☑️ upcasting 예시 코드 : 서브타입 -> 슈퍼타입 변환이 업캐스팅 / 타입 변환은 암시적으로 이루어져 별도 타입 변환 구문 필요X -> TS가 자동으로 변환해주므로 대입만 해도 됨

let dog: Dog = new Dog('또순이');
let animal: Animal = dog; // upcasting 발동!
animal.eat(); // 에러. 슈퍼타입(Animal)으로 변환이 되어 eat 메서드를 호출할 수 없어요!
  • upcasting이 필요한 이유는 서브타입 객체를 슈퍼타입 객체로 다루면 유연하게 활용할 수 있기 때문
    • 예를 들어, Dog, Cat, Lion 그리고 기타 등등 다양한 동물을 인자로 받을 수 있는 함수를 만들고 싶다면?
      • 올바른 선택: 아! Animal 타입의 객체를 받으면 모두 다 받을 수 있음!
      • 잘못된 선택: 아! union으로 새로운 타입을 만들어서 해당 타입의 객체를 받게해야하나?

☑️ downcasting 예시 코드 : 슈퍼타입 -> 서브타입 변환이 다운캐스팅 / as 키워드로 명시적으로 타입 변환 해야 함 / 생각보다 잘 안 쓰임

let animal: Animal;
animal = new Dog('또순이');

let realDog: Dog = animal as Dog;
realDog.eat(); // 서브타입(Dog)로 변환이 되었기 때문에 eat 메서드를 호출할 수 있죠!

 

추상 클래스

☑️ 추상 클래스의 정의

  • 추상 클래스는 클래스와는 다르게 인스턴스화를 할 수 없는 클래스

☑️ 추상 클래스가 있는 이유

  • 추상 클래스의 목적은 상속을 통해 자식 클래스에서 메서드를 제각각 구현하도록 강제를 하는 용도
  • 물론, 추상 클래스도 최소한의 기본 메서드는 정의를 할 수 있음
  • 하지만, 골자는 핵심 기능의 구현은 전부 자식 클래스에게 위임을 하는 것

☑️ 추상 클래스 사용 방법

  • 추상 클래스 및 추상 함수는 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();

const rectangle = new Rectangle(4, 6);
rectangle.printArea();