안녕하세요. 이번에는 Design Pattern에서 Compound Pattern에 대해 알아보려고 합니다.
회사에서 꽤 규모가 있는 프로젝트를 진행하던 중, 제가 알던 방식으로는 현재 규모의 프로젝트를 감당하기는 약간 버거웠습니다.
이에 따라 이를 개선할 방법을 찾게 되었고, 그러다보니 여러 디자인패턴에 관심을 가지게 되었습니다. 그래서 이 중 하나인 Compound Pattern에 대해 소개를 해볼까 합니다.
하나의 완결된 UI 컴포넌트를 위해서 작게 세분화된 하위 컴포넌트(Sub Component)가 존재하고,
그 하위 컴포넌트(Sub Component)들을 조합하여 사용하는 디자인 패턴입니다.
export const A_sectionInfoDetailArea = Object.assign(SectionInfoDetailMain, {
Header: SectionInfoDetailTitle,
ExtraInfo,
table: TableMain,
tableCaption: TableCaption,
tableHeader: TableHeader,
tableBody: TableBody,
});
먼저 index.ts에 A_sectionInfoDetailArea를 정의하였습니다.
이는 메인 컴포넌트, 서브 컴포넌트가 어떤 것인지 명시적으로 정의하기 위하여 index.ts를 다음과 같이 만들었습니다.
해당 코드에서 SectionInfoDetailMain가 Main이 되고, 나머지가 Sub Component가 됩니다.
import cn from "classnames";
interface SectionInfoDetailMainProps {
children: React.ReactNode;
className?: string;
}
export default function SectionInfoDetailMain({
children,
className,
}: SectionInfoDetailMainProps) {
return <div className={cn(className)}>{children}</div>;
}
이는 Main Component입니다.
classnames와 children을 통해 재사용 및 유연성을 고려하여 코드를 구현하였습니다.
interface SectionInfoDetailTitleProps {
title?: string;
children?: React.ReactNode;
}
export default function SectionInfoDetailTitle({
title,
children,
}: SectionInfoDetailTitleProps) {
if (!title) return;
return (
<div className="flex">
<div className="flex w-[140px] ml-4 justify-center px-4 py-2 shadow-md border border-gray-200 rounded-t-lg">
{title}
</div>
{children}
</div>
);
}
이는 SectionInfoDetailTitle라는 Sub Component입니다. Main에 종속되어 사용되는 Sub Component입니다. 여기서도 children을 받아서 더 확장할 수 있도록 만들었습니다.
이제는 위의 Main Component, Sub Component를 가지고 어떤식으로 조합하길래 레고라는 표현을 하는지 살펴보도록 하겠습니다.
<A_sectionInfoDetailArea className={classNames}>
<A_sectionInfoDetailArea.Header title={title}>
<A_sectionInfoDetailArea.ExtraInfo
content={content}
extraInfovalue={extraInfovalue}
/>
</A_sectionInfoDetailArea.Header>
<A_sectionInfoDetailArea.table>
<A_sectionInfoDetailArea.tableHeader fieldData={sectionInfoFieldData} />
<A_sectionInfoDetailArea.tableBody>
{genBody()}
</A_sectionInfoDetailArea.tableBody>
</A_sectionInfoDetailArea.table>
</A_sectionInfoDetailArea>
이런식으로 저는 Main과 Header, ExtraInfo, table, tableHeader, tableBody인 Sub Component들로 A_sectionInfoArea를 구현하였습니다.
<A_sectionInfoDetailArea className={classNames}>
<A_sectionInfoDetailArea.Header title={title}/>
<A_sectionInfoDetailArea.table>
<A_sectionInfoDetailArea.tableHeader fieldData={sectionInfoFieldData} />
<A_sectionInfoDetailArea.tableBody>
{genBody()}
</A_sectionInfoDetailArea.tableBody>
</A_sectionInfoDetailArea.table>
</A_sectionInfoDetailArea>
가령 A_sectionInfoArea-2에는 ExtraInfo가 필요 없다고 하면, 해당 Sub Component는 A_sectionInfoArea-2에 추가하지 않는 식으로 구현할 수 있습니다.
이는 마치 내가 만들고자 하는 것에 특정 레고가 필요하냐 필요하지 않느냐에 따라 그것을 가져오냐, 가져오지 않느냐를 판단하듯이 코드를 짤 수 있기에 레고와 같다고 생각했습니다.
저는 Compound Pattern의 장점은 유연성과 재사용성에 있다고 생각합니다.
코드 예시를 보면, 어떤식으로 제가 유연성과 재사용성을 챙기는지 보실 수 있을거라 생각이 듭니다.
추가로 유연성과 재사용성 이외의 장점이 있는데요, 이는 제가 가장 중요하게 생각하는 Compound Pattern의 장점입니다.
그것은 단일책임원칙을 수행하기 적절하다는 것입니다.
tableHeader는 말 그대로 tableHeader를,
tableBody는 말 그대로 tableBody의 역할을 수행하듯이
Compound Pattern을 적절히 잘 사용하면, 단일책임원칙이 자연스럽게 따라오는 것이 저에게 가장 매력적으로 다가왔던 것 같습니다.
감사합니다.