본문 바로가기

웹/TypeScript

[Typescript] 디자인 패턴을 적용해보다

디자인 패턴 활용 했던 것

디자인 패턴을 활용해서 분기처리를 해본 경험을 써봅니다.

간단히 설명하면 데이터를 받아오고 가공해서 xlsx 시트에다가 넣는 작업을 하는 함수를 만들었을 때 생겼어요.

문제는 데이터에 있었어요. 처음에는 고정된 타입의 데이터가 온다고 생각했는데 또 다른 타입 형식으로 데이터를 받고 처리해야 하는 상황이였어요.

 

기존에 사용하는 타입 형식 과 또 다른 타입 형식2가지를 if문으로 분기처리하면 간단히 처리되었을 거에요. 하지만 저는 여기서 불안감을 느꼈어요. “데이터 형식을 여러게 만들어 달라하면 어떻하지?” 라는 생각이 들더라구요. 함수로 들어오는 데이터 형식이 많아 지면 많아질 수록 if 문이 많아질태고 나중에는 읽기 더 어려워질 거에요.

 

그래서 gpt, claude 한태 더 좋은 방법이 있냐고 물어봤어요. 디자인 패턴을 알려주더라고요. 그래서 팩토리 패턴, 전략 패턴을 사용해봤어요

 

🟥 기존 코드 문제

interface DataDetails {
  title: string;
  csvData: string;
  query: Record<string, any>;
}

const createSheet = (workbook: ExcelJS.Workbook, sheetName: string, data: DataDetails) => {
  const sheet = workbook.addWorksheet(sheetName);

  sheet.addRow(['Component: ', data.title]);
  sheet.addRow([]);
  sheet.addRow(['Query: ']);

  Object.entries(data.query).forEach(([key, value]) => {
    sheet.addRow(['', key, value]);
  });

  sheet.addRow([]);
  sheet.addRow(['Data: ']);

  const rows = data.csvData.split('\n').map((row) => row.split(','));
  rows.forEach((row) => sheet.addRow(row));
};

DataDetails 타입이 들어온다고 되어있어요.

그런데 타입안에 값이 추가가 되는거에요. 아래처럼 jsonData 라는 것이 들어왔다고 가정해볼게요.

interface DataDetails {
  title: string;
  csvData?: string;
  jsonData?: object[];
  query: Record<string, any>;
}

const createSheet = (workbook: ExcelJS.Workbook, sheetName: string, data: DataDetails) => {
  const sheet = workbook.addWorksheet(sheetName);

  if (data.csvData) {
    const rows = data.csvData.split('\n').map((row) => row.split(','));
    rows.forEach((row) => sheet.addRow(row));
  } else if (data.jsonData) {
    const keys = Object.keys(data.jsonData[0]);
    sheet.addRow(['', ...keys]);

    data.jsonData.forEach((item) => {
      sheet.addRow(['', ...Object.values(item)]);
    });
  }
};

jsonDatacsvData의 처리를 다르게 해달라고 요구했어요. 다행이 csvData만 있을 때는 csvData만 있고 jsonData가 있을 때는 jsonData만 들어온다고 해요. (지금 생각해보니 flag 를 만들어서 flag 대로 if문 처리를 하는게 어땠을까 싶네요😂)

data에 구분되는 것이 더 많아진다면? 하는 생각에 디자인 패턴을 적용해봤어요. 제가 적용한 디자인 패턴은 팩토리 패턴전략 패턴이에요.

 

 

🟦 디자인 패턴 적용(팩토리 패턴)

addDataSheet 함수(실제로 실행되는 함수)

const addDataSheet = (workbook: ExcelJS.Workbook, sheetName: string, data: DashboardComponentDetail) => {
  const sheet = workbook.addWorksheet(sheetName);

  /**
   * 분기처리 방식
   * oracleData가 있다
   * csvData가 있다
   * 2개 중 1개만 data로 도착함
   */
  const processor = DataProcessorFactory.getProcessor(data);

  processor.processQuery(sheet, data);
  sheet.addRow([]);
  sheet.addRow(['Data: ']);

  processor.processData(sheet, data);
};

이렇게 Factoryprocessor를 만들고 processor안에서 데이터 구분이 일어나게 해요.

 

 

facotires/data-processor.factory.ts

class DataProcessorFactory {
  static getProcessor(data: DataDetails): DataProcessor {
    if (data.csvData) {
      return new CsvDataProcessor();
    } else if (data.jsonData) {
      return new JsonDataProcessor();
    }

    throw new Error('Unsupported data type');
  }
}

이 파일에서 사용된 것이 팩토리 패턴(Factory Pattern) 이라고 해요. 객체 생성 로직을 캡슐화해요. 객체가 생성될 때 data안의 값을 보고 실제로 값을 처리하는processor 를 반환해요.

팩토리 패턴으로 여러가지 processor 를 만들기 위해서는 전략패턴(Strategy Pattern)을 사용해요.

 

 

🟦 디자인 패턴 적용(전략 패턴)

공통 인터페이스 정의를 해요.

 

dashboard.type.ts

interface DataProcessor {
  processQuery(sheet: ExcelJS.Worksheet, data: DataDetails): void;
  processData(sheet: ExcelJS.Worksheet, data: DataDetails): void;
}

DataProcessor 라는 인터페이스를 정의해, 각 데이터 형식이 공통적으로 가지고 있어야할 기능을 정의해둬요.

🟨 각각의 전략을 만들어요 행위를 캡슐화 하는 거에요.

 

csvData-processor.strategy.ts

class CsvDataProcessor implements DataProcessor {
  processQuery(sheet: ExcelJS.Worksheet, data: DataDetails): void {
    Object.entries(data.query).forEach(([key, value]) => {
      sheet.addRow(['', key, value]);
    });
  }

  processData(sheet: ExcelJS.Worksheet, data: DataDetails): void {
    if (!data.csvData) return;

    const rows = data.csvData.split('\n').map((row) => row.split(','));
    rows.forEach((row) => sheet.addRow(row));
  }
}

jsonData-processor.strategy.ts

class JsonDataProcessor implements DataProcessor {
  processQuery(sheet: ExcelJS.Worksheet, data: DataDetails): void {
    Object.entries(data.query).forEach(([key, value]) => {
      sheet.addRow(['', key, value]);
    });
  }

  processData(sheet: ExcelJS.Worksheet, data: DataDetails): void {
    if (!data.jsonData?.length) return;

    const keys = Object.keys(data.jsonData[0]);
    sheet.addRow(['', ...keys]);

    data.jsonData.forEach((item) => {
      sheet.addRow(['', ...Object.values(item)]);
    });
  }
}

이 2가지 파일들은 정의해둔 행위들을 구현해요.

  • 객체를 사용할 때 csvData인지 , jsonData 인지 모르는 상태로 행위만 가져다가 쓰는 장점이 있어요.
  • const processor = DataProcessorFactory.getProcessor(data); processor.processQuery(sheet, data); sheet.addRow([]); sheet.addRow(['Data: ']); processor.processData(sheet, data);

위 3가지 파일을 보고 새로운 방향 찾은 것 같았어요. 전략패턴은 객체의 행위를 캡슐화 해서 동적으로 선택할 수 있도록 만들어요.

만약 csvData 이 아니라 blobData, another 등등 라는 데이터가 와도 processor 만 추가하고 factory에서 분기처리하면 해주면 되요. 실제 사용되는 코드의 수정은 최소화가 되요.

 

 

결론

  • 디자인 패턴을 사용해서 얻은 것이 많아요.
    • 팩토리 패턴을 사용해 객체 생성 로직을 캡슐화하고, 전략 패턴을 통해 행위를 캡슐화했어요.
  • 확장성이 제일 커요. 다른 확장 데이터가 와도 손쉽게 변경할 수 있어요.
  • 유지보수성이 좋아졌어요. 코드를 읽기 쉽게 클래스로 분리를 해놨어요.

 

 

느낀점

책으로만 보고 사용했던 디자인 패턴을 실제로 적용해보니 확장성과 유지보수성이 좋아진것 같아요. 괜히 불안감에 오버 엔지니어링 한게 아닌가 생각도 들었어요.

하지만 체계화된 코드를 보니 이래서 디자인 패턴 하는구나 싶어요. 좋은 구조와 효율성을 찾기위해서 디자인 패턴을 공부하는 이유를 알았으니 좋았습니다.

728x90