React에 SOLID 원칙 중 OCP 원칙 적용하기
팀원들과 프로젝트 진행 중에 공통 컴포넌트 중 Header 부분을 맡게 되었는데, 전에 멘토링 시간에 들었던 React에 SOLID 원칙 적용하기에 대한 부분에서 OCP(개방-폐쇄 원칙)를 적용할 수 있을 거 같아서 그 과정을 공유 해보고자 한다.
먼저 SOLID 원칙에 대해서 알아보자!
SOLID 원칙이란?
SOLID 원칙은 객체지향 설계에서 지켜줘야 하는 5가지 원칙을 의미 한다. 각 원칙의 이름은 영문 첫 글자를 따서 SOLID라는 약어를 형성한다. 각 원칙에 대해 간단하게 알아보면,
- SRP: 단일 책임 원칙 Single Responsibility Principle)
- 하나의 클래스는 하나의 책임만 가져야 한다. 즉, 클래스는 하나의 기능만 담당해야 하며, 변경의 이유가 오직 하나여야 한다는 원칙이다.
- OCP: 개방-폐쇄 원칙 (Open/Closed Principle)
- 소프트웨어 요소는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙이다. 즉, 기존 코드를 수정하지 않고 기능을 추가할 수 있어야 한다.
- LSP: 리스코프 치환 원칙 (Liskov Substitution Principle)
- 서브타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다는 원칙이다. 즉, 자식 클래스는 부모 클래스의 행위를 대체해도 소프트웨어가 정상적으로 작동해야 한다.
- ISP: 인터페이스 분리 원칙 (Interface Segregation Principle)
- 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다는 원칙이다. 즉, 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
- DIP: 의존성 역전 원칙 (Dependency Inversion Principle)
- 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다는 원칙이다. 즉, 구체적인 구현이 아닌 추상화된 인터페이스나 추상 클래스에 의존해야 한다.
오늘은 SOLID 원칙 중에서 OCP에 대해서 말해보고자 한다.
OCP를 적용하기 전 Header 컴포넌트
먼저 OCP를 적용하기 전 Header 컴포넌트의 코드를 살펴보면,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Header() {
const location = useLocation();
return (
location.pathname !== "/" && (
<div className={styles["container"]}>
<header className={styles["header"]}>
<div></div>
<Link to="/">
<img className={styles["logo"]} src={logo} alt="팬덤 케이" />
</Link>
<Link to="/mypage">
<img
className={styles["profile"]}
src={profileImage}
alt="프로필 이미지"
/>
</Link>
</header>
</div>
)
);
}
export default Header;
현재 코드의 문제점
현재 Header 컴포넌트는 특정한 구조를 가진다.
location.pathname이/가 아닌 경우에만 렌더링이 되며,- 고정된 로고와 프로필 이미지를 포함한다.
이 구조는 다른 조건이나 추가적인 아이템을 쉽게 확장하기 어렵다. 즉, 확장성이 부족하다. OCP는 확장에 대해 열려 있어야 하고, 변경에 대해서는 닫혀 있어야 한다.
이 코드를 OCP를 적용하여 해결 해보자.
OCP를 적용한 Header 컴포넌트
해결 과정으로는,
- 컴포넌트 분리: 고정된 부분을 재사용 가능한 컴포넌트로 분리
- 조건에 대한 로직 분리:
Header컴포넌트 내부의 조건 로직을 별도의 함수나 훅으로 분리 - 확장 포인트 제공: 로고와 프로필 이미지 뿐만 아니라, 추가적인 요소가 생긴다면 쉽게 삽입할 수 있도록 확장 포인트를 제공
이렇게 3가지 방법을 적용할 예정이다. 코드를 보면서 같이 적용해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 로고 컴포넌트 분리
const Logo = () => (
<Link to="/">
<img className={styles["logo"]} src={logo} alt="팬덤 케이" />
</Link>
);
// 프로필 이미지 컴포넌트 분리
const ProfileImage = () => (
<Link to="/mypage">
<img className={styles["profile"]} src={profileImage} alt="프로필 이미지" />
</Link>
);
// 조건 로직을 별도의 훅으로 분리
const useIsVisibleHeader = () => {
const location = useLocation();
return location.pathname !== "/";
};
// 개선한 Header 컴포넌트
function Header({
EmptyItemComponent = null,
ProfileImageComponent = <ProfileImage />,
logoComponent = <Logo />
}) {
const isVisible = useIsVisibleHeader();
if (!isVisible) return null;
return (
<div className={styles["container"]}>
<header className={styles["header"]}>
<div>{EmptyItemComponent}</div>
{logoComponent}
<div>{ProfileImageComponent}</div>
</header>
</div>
);
}
export default Header;
OCP를 적용하여 개선된 코드 설명
컴포넌트를 분리하여
Logo와ProfileImage컴포넌트를 별도로 분리하여 재사용성을 높였다.조건 로직을 분리하여
useIsVisibleHeader훅을 통해 조건 로직을 분리하여 Header 컴포넌트가 더 간결해졌다.확장 포인트를 제공하여
Header컴포넌트에leftComponent,rightComponent,logoComponent등의 프로퍼티를 추가하여, 필요에 따라 다른 컴포넌트를 쉽게 주입할 수 있도록 했다.
적용 결과
- 확장성: 이제
Header컴포넌트는 다양한 컴포넌트를 주입받아 유연하게 확장할 수 있다. - 유지보수성: 로직이 분리되어 코드가 간결해지고 유지보수가 쉬워졌다.
- 재사용성: 분리된 작은 컴포넌트들은 다른 곳에서도 재사용이 가능하다.
이를 통해 코드가 유연하고 확장이 가능해졌으며, OCP 원칙을 준수할 수 있게 됐다.
느낀 점
멘토링 때 SOLID 원칙에 관해 설명을 들었을 때, 예시로 보면서 이해가 되긴 했지만 완벽하게 이 원칙을 왜 사용해야 하는지에 대한 부분에서는 의문이 들었었다. 하지만 확실히 직접 적용해 보고 나니, 왜 코드에 SOLID 원칙을 적용하는지 체감이 되는 시간이였던 것 같다. 앞으로도 컴포넌트를 만들면서 SOLID 원칙을 적용할 수 있는 코드가 있다면 적용하고 싶고, OCP가 아닌 다른 SOLID 원칙도 적용하고 공유해보고 싶다!