Backend/TypeScript

추상클래스와 제네릭 (실습)

kimdozzi 2024. 12. 29. 14:49

요구사항에 맞게 코드를 작성하는 과제입니다.

 


1. 클래스 구조

- PublicOfficer: 추상 클래스로, 공무원의 기본 속성과 메서드를 정의
- 하위 클래스: Police, Firefighter, Doctor가 상속

 


2. 핵심 static 클래스들

- CentralArchives: 모든 공무원 정보를 중앙 저장소에서 관리
- TrainingCenter: 공무원 객체 생성을 담당하는 팩토리 클래스 (제네릭 활용)
- Extractor: 특정 타입의 공무원 목록을 필터링하는 유틸리티 클래스

 


3. 주요 기능

- 공무원 등록/제거 (CentralArchives)
- 타입별 공무원 목록 조회 (Extractor)
- 근속연수(짝수/홀수)별 필터링 기능
- 제네릭을 활용한 타입 안전성 보장


// 1. PublicOfficer 를 상속 받는 클래스를 추가하세요. (Police, Firefighter 외 1개)
// - 속성을 추가하여도 무방합니다.
// 2. CentralArchives 클래스를 static 으로 구현하세요.
// - PublicOfficer[] 를 가지고 있는 중앙 저장소입니다.
// - 해당 필드는 외부에서는 읽기만 가능해야 합니다.
// - register, remove 메소드 구현
// - PublicOfficer[] 에 추가, 삭제합니다.
// - 모든 생성되는 Officer 는 CentralArchives 에 등록되어야 합니다.
// 3. TrainingCenter 클래스를 static 으로 구현하세요.
// - PublicOfficer 를 상속받은 클래스를 생성하는 팩토리 클래스입니다.
// - Police 뿐만 아니라 다른 Class 에 대한 train 함수도 구현해야 합니다.
// 4. Extractor 클래스를 static 으로 구현하세요.
// - CentralArchives 에서 필요한 직업의 정보를 추출하는 Utility 클래스입니다.
// - getPoliceList 메소드 구현합니다. (다른 class getList 메소드도 구현해야 합니다.)

// **추가 요구 사항**
// 5. 짝수/홀수 근속연수에 따라 Officer들을 반환해주세요.
// 6. TrainingCenter와 Extractor의 중복되는 메소드를 제네릭을 활용해서 리팩토링하세요.

abstract class PublicOfficer {
  public name: string;
  public yearsOfService: number;

  constructor(name: string, yearsOfService: number) {
    // 초기화
    this.name = name;
    this.yearsOfService = yearsOfService;
  }

  public abstract introduce(): void;

  public toString(): string {
    return `Name: ${this.name}, 
                Years of service: ${this.yearsOfService}`;
  }
}

class Police extends PublicOfficer {
  public introduce(): void {
    // 필요한 구현
    console.log(super.toString());
  }
}

class Firefighter extends PublicOfficer {
  public introduce(): void {
    // 필요한 구현
    console.log(super.toString());
  }
}

class Doctor extends PublicOfficer {
  public introduce(): void {
    // 필요한 구현
    console.log(super.toString());
  }
}

class CentralArchives {
  private static officers: PublicOfficer[] = [];
  private constructor() {}

  public static get allOfficers(): ReadonlyArray<PublicOfficer> {
    // officers 반환
    return this.officers as ReadonlyArray<PublicOfficer>;
  }

  public static register(officer: PublicOfficer): void {
    // officer 추가
    this.officers.push(officer);
  }

  public static remove(officer: PublicOfficer): void {
    // officer 제거
    const index = this.officers.indexOf(officer);
    if (index > -1) {
      this.officers.splice(index, 1);
    }
  }
}

class TrainingCenter {
  private constructor() {}

  public static train<T extends PublicOfficer>(
    constructor: new (name: string, yearsOfService: number) => T,
    name: string,
    yearsOfService: number
  ) {
    const officer = new constructor(name, yearsOfService);
    CentralArchives.register(officer);
    return officer;
  }
}

type Constructor<T> = new (...args: any[]) => T;

class Extractor {
  private constructor() {}

  public static getPoliceList(): Police[] {
    return this.filterByType(Police);
  }

  public static getFirefighterList(): Firefighter[] {
    return this.filterByType(Firefighter);
  }

  public static getDoctorList(): Doctor[] {
    return this.filterByType(Doctor);
  }

  private static filterByType<T extends PublicOfficer>(
    type: Constructor<T>
  ): T[] {
    // 어떤 인자를 받든 T 타입의 인스턴스를 만들 수 있는 생성자
    return CentralArchives.allOfficers.filter(
      (officer): officer is T => officer instanceof type
    ); // 이 조건문이 true를 반환하면 officer는 T 타입이다
  }

  // 짝수/홀수 근속연수 조회
  public static filterByYearsOfService<T extends PublicOfficer>(
    type: Constructor<T>,
    isEven: boolean
  ): T[] {
    return this.filterByType(type).filter((officer) =>
      isEven
        ? this.isEvenYearsOfService(officer)
        : !this.isEvenYearsOfService(officer)
    );
  }

  public static isEvenYearsOfService(officer: PublicOfficer): boolean {
    return officer.yearsOfService % 2 === 0;
  }

  // police
  public static getPoliceEvenYearsOfService(): Police[] {
    return this.filterByYearsOfService(Police, true);
  }

  public static getPoliceOddYearsOfService(): Police[] {
    return this.filterByYearsOfService(Police, false);
  }

  // firefighter
  public static getFirefighterEvenYearsOfService(): Firefighter[] {
    return this.filterByYearsOfService(Firefighter, true);
  }

  public static getFirefighterOddYearsOfService(): Firefighter[] {
    return this.filterByYearsOfService(Firefighter, false);
  }

  // doctor
  public static getDoctorEvenYearsOfService(): Doctor[] {
    return this.filterByYearsOfService(Doctor, true);
  }

  public static getDoctorOddYearsOfService(): Doctor[] {
    return this.filterByYearsOfService(Doctor, false);
  }
}