엘리스_이론

23.10.13 (interface, generic)

하루_st 2023. 10. 13. 14:59

인터페이스에 대한 설명 중 올바른 것 > 직접 인스턴스 생성 불가능, 변수 함수나 클래스 타입 체크 위해 사용

제네릭에 대한 설명 중 틀린 것 > 컴파일 시에는 타입 오류 발생 하지 않음

 

 

인터페이스

   일반적으로 변수, 함수, 클래스에 타입 체크를 위해 사용됨

   직접 인스턴스 생성 불가, 모든 메소드가 추상 메소드

   추상 클래스의 추상 메소드와 달리 abstract키워드 사용 불가

   es6지원 안함, 타입스크립트 인터페이스 지원

사용하는 이유

   타입의 이름을 짓고 코드 안의 계약 정의함

   프로젝트 외부에서 사용하는 코드의 계약 정의 하는 강한 방법

   >객체의 스펙(속성과 속성 타입), 함수 파라미터, 함수 스펙(파라미터, 반환타입), 배열과 객체 접근 방식, 클래스

 

function sayName(obj : {name:string}) {  console.log(obj.ame); }

let person = {name : "june" };

sayName(person);

>>

interface Person { name : string }

function sayName (obj: Person) { console.log(obj.name); }

ler person = {name: "june"); }

sayName(person);

>>인터페이스를 추가해 함수 매개변수 프로퍼티 정의 가능

정의한 프로퍼티 값을 누락하거나 정의하지 않은 값을 인수로 전달 시 컴파일 에러 발생

 

인터페이스 사용 예제

// 매개변수로 객체를 받는 예제 코드입니다.
function printName(namedObj: { name: string }) {
  return namedObj.name;
}

// 어떤 결과가 나오는지 확인해보세요.
let myObj = { size: 10, name: "Saehee Choi" };
console.log(printName(myObj));

// string 타입의 프로퍼티인 name을 갖는 Interface를 정의하세요.
interface namedValue {
  name: string;
}

// printName 함수와 동일하게 작동하는 printName2라는 함수를 생성하고, 정의된 Interface를 parameter 타입으로 지정하세요.
function printName2(namedObj: namedValue) {
  return namedObj.name;
}

// 주석을 해제하여 어떤 결과가 나오는지 확인해보세요.
let myObj2 = { size: 10, name: "Elice Kim" };
console.log(printName2(myObj2));

// 채점을 위한 코드입니다. 수정하지 마세요.
export { printName2 };

 

프로퍼티

컴파일러는 프로퍼티의 두 가지 요소를 검사함 > 필수요소 프로퍼티의 유무, 프로퍼티 타입

예약어로 프로퍼티 컨트롤 가능 > ?(optional properties), readonly

 

optional

프로퍼티 선언 시 이름 끝에 ?를 붙여 표시함

인터펭스에 속하지 않는 프로퍼티의 사용 방지, 사용 가능한 프로퍼티 기술 시 사용

객체 안 몇개의 프로퍼티만 채워 함수에 전달하는 "option bags"같은 패턴에 유용함

ex) interface MM{ color?:string   width?: number }

 

readonly

객체가 처음 생성될 때만 값 설정 가능, 이후 수정 불가능

프로퍼티 앞 readonly붙여 사용

ex) interface Point { readonly x : number   readonly y : number }

 

옵셔널, 읽기 전용 예제

// 지시사항에 주어진 타입의 프로퍼티들을 갖도록 Interface를 정의하세요.
interface User{
    readonly name:string;
    readonly id: string | number;
    gender? : string;
    accountNumber: number;
}


// 정의한 인터페이스를 할당한 객체를 생성하여 테스트해보세요.
 let user: User = {
   name: "Calvin",
   id: 23,
   accountNumber: 20,
 };

// 채점을 위한 코드입니다. 수정하지 마세요.
export { User };

 

readonly vs const

공통점 - 생성 후 배열 미변경 보장 > 수정 불가

차이점 - 변수는 const사용, 프로퍼티는 readonly사용

 

인터페이스 타입

타입에서 인터페이스는 함수, 클래스에서 사용 가능

함수 - js객체가 가질 수 있는 넓은 범위의 형태 기술, 프로퍼티 객체 기술 외 인터페이스는 함수 타입 설명

클래스 - 특정 통신 프로토콜을 충족하도록 명시적으로 강제함, 다른 언어에서 일반적으로 사용하는 방법과 동일

 

function type

함수의 인자 타입과 반환 값의 타입 정의 + 함수 타입 정의 시 사용

ex) interface searchFunc { (source:string, substring:string) : boolean }

let mysearch = searchFunc

mysearch = function (src, sub) { 

   let result = src.search(sub);   return result > -1; }

mysearch = function (src,sub) { let result = src.search(sub);  return "string"; }

 

class type

클래스가 특정 계약(contract)을 충족하도록 명시적으로 강제함

ex) interface animal { makeSound():void }

class dog implements animal { mmakeSound(): void { console.log("멍멍"); } }

 

interface확장

ex) interface animal { makeSound(): void }

interface dog extends animal { speed:number } //확장

class bulldog implements dog { makeSound(): void { console.log("멍멍");  } }

 

하이브리드 타입

js의 유연하고 동적인 타입 특성에 따라 인터페이스 또한 여러 타입을 조합 가능

함수 타입이면서 객체 타입 정의할 수 있는 인터페이스 구현 가능

ex) interface Counter {  (start:number) : string

interval:number reset():void }

function getCounter():counter { let counter = function(start:number0 {} as Counter

counter.interval = 123;  counter.reset = function() {}  return counter; }

let c = getCounter(); c(10); c.reset(); c.interval = 5.0;

 

디자인 패턴(strategy pettern)

객체가 할 수 있는 행위들을 전략(strategy)으로 만들어 두고

동적으로 행위의 수정이 필요한 경우 전략을 바꾸는 것만으로 수정 가능하도록 만든 패턴

 

전략패턴 예시

interface PaymentStrategy {
  pay(): void
}

// PaymentStrategy를 상속받는 두 개의 클래스를 구현해주세요.
// 각 클래스의 `pay()` 메소드를 호출했을 때 cash pay, card pay가 출력되어야 합니다.
class CashPaymentStrategy implements PaymentStrategy{
    pay():void {
        console.log("cash pay");
    }
}
class CardPaymentStrategy implements PaymentStrategy{
    pay():void {
        console.log("card pay");
    }
}

class VendingMachine {
  private paymentStrategy: PaymentStrategy

  setPaymentStrategy(paymentStrategy: PaymentStrategy) {
    this.paymentStrategy = paymentStrategy
  }

  pay() {
    this.paymentStrategy.pay()
  }
}

const vendingMachine = new VendingMachine()

vendingMachine.setPaymentStrategy(new CashPaymentStrategy())
vendingMachine.pay() // cash pay

vendingMachine.setPaymentStrategy(new CardPaymentStrategy())
vendingMachine.pay() // card pay

generic

정적 type언어는 클래스나 함수 정의 시 타입선언을 해야 하는데 제네릭은 코드 작성 시가 아닌 코드 수행시 타입 명시함

코드 작성 시 식별자를 써 아직 정해지지 않은 타입 표시 > T,U,V 등  필드 이름의 첫글자를 사용하기도 함

 

사용하는 이유

재사용성이 높은 함수와 클래스 생성 가능

   여러타입에서 동작 가능 > 한 번의 선언으로 다양한 타입에 재사용 가능 + 코드 가독성 향상)

오류 쉽게 포착

   any타입 사용 시 컴파일때 타입 체크 안함 > 관련 메소드 힌트 사용 불가 > 컴파일 시 컴파일러가 오류 찾지 못함

타입을 미리 체크해 오류 찾을 수 있음

 

함수만들기

function sort<T>(items: T[]): T[]{  return items.sort(); }

const nums: number[] = [1,2,3,4];

const chars:string[] = ["a","b","c","d"];

sort<number>(nums);

sort<string>(chars);

클래스 만들기

class queue<T> { 

protected data: Array<T> = [];

push(itme:T) { this.data.push(item); }

pop():T | undefined{ return this.dta.shift(); } }

const numberQueue = new Queue<number>();

numberQueue.push(0);

numberQueue.push("1"); //의도하지 않은 실수 사전 검출 가능

numberQueue.push(+"1");  //실수 사전 인지 및 수정 가능

class Queue<T> {
  private data: Array<T> = []
  // 제네릭을 활용하여 push()와 pop() 메소드를 구현해주세요.
  push(item:T){
      this.data.push(item);
  }
  pop():T|undefined {
      return this.data.shift();
  }
}

유니온 타입

|을 사용해 두 개 이상의 타입을 선언하는 방식

선언한 공통된 메소드만 사용 가능, 리턴값이 하나의 타입이 아닌 선언된 유니온 타입으로 지정됨

ex) Union

const printMessage = (message:string | number ) => { return message; }

const message1 = printMessage(1234);

const message2 = printMessage("hello world");

 

유니온 타입 예제

interface Doctor {
  name: string;
  age: number;
}
interface Engineer {
  name: string;
  skill: string;
}

// Docter 인터페이스와 Engineer 인터페이스를 합친 타입을 선언하세요.
type inter = Doctor & Engineer;

// 함수의 매개변수 타입을 수정하세요.
function introduce(person: inter) {
  console.log(person.name);
  console.log(person.age);
  console.log(person.skill);
}

// 채점을 위한 코드입니다. 수정하지 마세요.
export { introduce };

 

generic

const printMessage2 = <T>(message:T) => { return message; }

const message1 = printMessage2<String>("hello world");

message1.length;

 

함수에 generic사용 예제

// Generic을 적용해 함수를 수정하세요.
function getFirstElement<T>(elements: T[]): T {
  return elements[0];
}

// 수정한 함수에 맞게 출력해보세요.
console.log(getFirstElement<number>([1, 2, 3]));
console.log(getFirstElement<string>(["one", "two", "three"]).substring(0, 1));
// console.log(getFirstElement([1, 2, 3]).substring(0, 1)); // 런타임 단계에서 에러 발생

// 채점을 위한 코드입니다. 수정하지 마세요.
export { getFirstElement };

 

제약조건

원하지 않는 속성에 접근하는 것을 막기우해 사용

constraints - 특정 타입들로만 동작하는 제네릭 함수를 만들때 사용

key of - 두 객체를 비교할 때 사용

    타입 매개변수 속성 키 값으로만 제한된 타입 매개변수 선언 위해 사용 

 

constraints 

제네릭 t에 제약조건 설정 (문자열 | 숫자)   제약조건 벗어나는 타입 선언 시 에러 발생

ex) 

const printMessage = <T extends string | number> (message:T) : T => { return message; }

printMessage<string>("1");

printMessage<number>(1);

printMessage<boolean>(false); //error

 

key of

const getProperty = <T extends object, U extends keyof T> (obj: T, key:U) => { return obj[key] }

getProperty({a:1, b:2, c:3}, "a" );

getProperty({a:1,b:2,c:3}, "z"); //error

>>제네릭에서 T는 키 값이 a,b,c만 존재하기 때문에 U값에 z를 쓰면 오류가 발생함

 

 

디자인패턴(factoryo pattern with generics)

객체를 생성하는 인터페이스만 미리 정의하고 인스턴스를 만들 클래스의 결정은 서브 클래스가 내림

여러개의 서브 클래스를 가진 슈퍼 클래스 존재 시 입력에 따라 하나의 서브 클래스의 인스턴스 반환함

ex)interface Car{ drive(): void  park():void }

class Bus implements Car { drive(): void{}  park():void{} }

class Taxi implements Car { drive():void {} park(): void{}  }

 

class CarFactory { static getInstance(type:string):Car {

   switch(type) { 

      case "bus" : return new Bus();

      default : return new Taxi();  } } }

const bus = CarFactory.getInstance("bus");

const taxi = CarFactory.getInstance("taxi");

 

export class CarFactory { static getInstance<T extends Car> (type: { new (): T}):T {

return new type(); } } 

const bus = CarFactory.getInstance(Bus);

const taxi = CarFactory.getInstance(Taxi);

interface Car {
  drive(): void
  park(): void
}

// Bus 클래스와 Taxi 클래스를 생성하세요.
class Bus implements Car{
    drive(): void {}
    park(): void{
        console.log("버스 주차");
    }
}
class Taxi implements Car{
    drive():void {}
    park(): void {
        console.log("택시 주차");
    }
}

// Factory pattern을 적용하기 위한 서브 클래스입니다.
class CarFactory {
  static getInstance<T extends Car>(type: { new (): T }): T {
    return new type()
  }
}

// CarFactory 클래스의 getInstance메소드를 이용해서 Bus, Taxi 인스턴스를 생성해주세요.
const bus = CarFactory.getInstance(Bus);
const taxi = CarFactory.getInstance(Taxi);

bus.park();
taxi.park();