디자인 패턴 활용 했던 것
디자인 패턴을 활용해서 분기처리를 해본 경험을 써봅니다.
간단히 설명하면 데이터를 받아오고 가공해서 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)]);
});
}
};
jsonData
와 csvData
의 처리를 다르게 해달라고 요구했어요. 다행이 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);
};
이렇게 Factory
로 processor
를 만들고 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에서 분기처리하면 해주면 되요. 실제 사용되는 코드의 수정은 최소화가 되요.
결론
- 디자인 패턴을 사용해서 얻은 것이 많아요.
- 팩토리 패턴을 사용해 객체 생성 로직을 캡슐화하고, 전략 패턴을 통해 행위를 캡슐화했어요.
- 확장성이 제일 커요. 다른 확장 데이터가 와도 손쉽게 변경할 수 있어요.
- 유지보수성이 좋아졌어요. 코드를 읽기 쉽게 클래스로 분리를 해놨어요.
느낀점
책으로만 보고 사용했던 디자인 패턴을 실제로 적용해보니 확장성과 유지보수성이 좋아진것 같아요. 괜히 불안감에 오버 엔지니어링 한게 아닌가 생각도 들었어요.
하지만 체계화된 코드를 보니 이래서 디자인 패턴 하는구나 싶어요. 좋은 구조와 효율성을 찾기위해서 디자인 패턴을 공부하는 이유를 알았으니 좋았습니다.
'웹 > TypeScript' 카테고리의 다른 글
[Typescript] typescript이 컴파일 되는 기준과 d.ts파일 (0) | 2024.11.25 |
---|---|
typescript에서 …(전개 연산자)을 string에 쓴다면 (0) | 2023.04.26 |
[Typescript] react, typescript에서 변수를 다른 파일에서 사용할 때 만난 TS1184: Modifiers cannot appear here 오류 (0) | 2023.03.23 |
Typescript에서 string key로 객체 접근하기 (0) | 2023.02.01 |
✅ [Typescript] ts-node와 node는 뭐가다를까요? (0) | 2022.12.08 |