πŸ’» 20240622 SOLID μ›μΉ™μœΌλ‘œ λ¦¬μ•‘νŠΈ ν›… μž‘μ„±ν•˜κΈ°

이좘길·2024λ…„ 6μ›” 22일
0

ν•™μŠ΅μΌμ§€

λͺ©λ‘ 보기
4/4
post-thumbnail
post-custom-banner

πŸ“Œ μ°Έμ‘°

(λ²ˆμ—­) SOLID μ›μΉ™μœΌλ‘œ λ¦¬μ•‘νŠΈ ν›… μž‘μ„±ν•˜κΈ°


πŸ“Œ κ°œμš”

μ–΄λ–»κ²Œ ν•˜λ©΄ μ΅œμ ν™” 된 μ»΄ν¬λ„ŒνŠΈ 섀계λ₯Ό ν•  수 μžˆμ„κΉŒ, 이 뢀뢄에 λŒ€ν•΄μ„œ 맀일이 λ°°μ›€μ˜ 과정이고 항상 κΆκΈˆν•˜λ‹€. κ΄€λ ¨ν•΄μ„œ, SOLID 객체지ν–₯ 원칙을 ν™œμš©ν•˜μ—¬ λ¦¬μ•‘νŠΈμ— μ΅œμ ν™” 된 λ¦¬μ•‘νŠΈ 훅을 μž‘μ„±ν•  수 μžˆλŠ” 정보λ₯Ό 전달 λ°›μ•„ 정리 μ°¨μ›μœΌλ‘œ μž‘μ„±ν•˜κ²Œ λ˜μ—ˆλ‹€.


πŸ“Œ 단일 μ±…μž„ 원칙

SRP - Single Responsibility Principle
λͺ¨λ“ˆμ€ 단 ν•˜λ‚˜μ˜ μ•‘ν„°λ§Œ λ‹΄λ‹Ήν•΄μ•Ό ν•©λ‹ˆλ‹€.

μ„€λͺ…

  • UI(ν”„λ ˆμ  ν…Œμ΄μ…˜)λ₯Ό ν‘œμ‹œν•΄μ•Ό ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈμΈκ°€μš”?
    μ•„λ‹ˆλ©΄ 데이터(둜직)λ₯Ό μ²˜λ¦¬ν•΄μ•Ό ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈμΈκ°€μš”?
  • 이 훅은 μ–΄λ–€ 단일 μœ ν˜•μ˜ 데이터λ₯Ό μ²˜λ¦¬ν•΄μ•Ό ν•˜λ‚˜μš”?
  • 이 ν›…μ΄λ‚˜ μ»΄ν¬λ„ŒνŠΈλŠ” μ–΄λ–€ λ ˆμ΄μ–΄μ— μ†ν•˜λ‚˜μš”?
    데이터 μ €μž₯μ†Œλ₯Ό μ²˜λ¦¬ν•˜λŠ” κ±΄κ°€μš”? μ•„λ‹ˆλ©΄ UI의 μΌλΆ€μΌκΉŒμš”?

μ½”λ“œ

import { useState } from 'react';
import { getUser, getTodoTasks } from 'somewhere';

// useUser 훅은 더 이상 todo taskλ₯Ό κ°€μ Έμ˜€λŠ” μž‘μ—…μ„ λ‹΄λ‹Ήν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
const useUser = () => {
  const [user, setUser] = useState();

  useEffect(() => {
    const userInfo = getUser();
    setUser(userInfo);
  }, []);

  return { user };
};

// Todo taskλ₯Ό κ°€μ Έμ˜€λŠ” μž‘μ—…μ€ κ·Έ μžμ‹ μ˜ κ³ μœ ν•œ 훅이 μˆ˜ν–‰ν•©λ‹ˆλ‹€.
// 이 훅은 μ‹€μ œλ‘œ 별도 νŒŒμΌμ— μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€. ν•˜λ‚˜μ˜ νŒŒμΌμ—λŠ” ν•˜λ‚˜μ˜ ν›…λ§Œ λ‘μ„Έμš”!
const useTodoTasks = () => {
  const [todoTasks, setTodoTasks] = useState();

  useEffect(() => {
    const tasks = getTodoTasks();
    setTodoTasks(tasks);
  }, []);

  return { todoTasks };
};

πŸ“Œ 개방 / 폐쇄 원칙

OCP - Open / Closed Principle
λ‹€μ‹œ 건듀지 μ•Šμ„ 수 μžˆλ„λ‘ ν›…κ³Ό μ»΄ν¬λ„ŒνŠΈλ₯Ό μž‘μ„±ν•˜κ³ ,
λ‹€λ₯Έ ν›… / μ»΄ν¬λ„ŒνŠΈμ—μ„œλŠ” μž¬μ‚¬μš©λ§Œ ν•˜μ„Έμš”.

μ„€λͺ…

  1. ν›…μ˜ ν™•μž₯성이 λ†’μ•„μ Έ 더 자주 μž¬μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  2. ν…ŒμŠ€νŠΈλ₯Ό λ‹€μ‹œ μž‘μ„±ν•˜λŠ” νšŸμˆ˜κ°€ 쀄어듀어 훅이 더 κ²¬κ³ ν•΄μ§‘λ‹ˆλ‹€.
  3. 이전 μ½”λ“œλ₯Ό κ±΄λ“œλ¦¬μ§€ μ•ŠμœΌλ©΄ 버그λ₯Ό λ§Œλ“€μ§€ μ•ŠλŠ”λ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.

μ½”λ“œ (사둀)

import { useState } from 'react';
import { getUser, updateUser } from 'somewhere';

const useUser = ({ userType }) => {
  const [user, setUser] = useState();

  useEffect(() => {
    const userInfo = getUser();
    setUser(userInfo);
  }, []);

  const updateEmail = newEmail => {
    if (user && userType === 'admin') {
      updateUser({ ...user, email: newEmail });
    } else {
      console.error('Cannot update email');
    }
  };

  return { user, updateEmail };
};

μ½”λ“œ (ν•΄κ²°)

import { useState } from 'react';
import { getUser, updateUser } from 'somewhere';

// useUser 훅은 이제 이메일 μ—…λ°μ΄νŠΈ κΈ°λŠ₯ 없이
// μ‚¬μš©μžλ§Œ λ°˜ν™˜ν•©λ‹ˆλ‹€.
const useUser = () => {
  const [user, setUser] = useState();

  useEffect(() => {
    const userInfo = getUser();
    setUser(userInfo);
  }, []);

  return { user };
};

// μƒˆλ‘œμš΄ 훅인 useAdmin은 useUser 훅을 ν™•μž₯ν•˜λ©°,
// 이메일을 μ—…λ°μ΄νŠΈν•˜λŠ” μΆ”κ°€ κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€.
const useAdmin = () => {
  const { user } = useUser();

  const updateEmail = newEmail => {
    if (user) {
      updateUser({ ...user, email: newEmail });
    } else {
      console.error('Cannot update email');
    }
  };

  return { user, updateEmail };
};

πŸ“Œ λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜μ˜ 원칙

LSP - Liskov Substitution Principle
ν›… / μ»΄ν¬λ„ŒνŠΈλ₯Ό ν™•μž₯ν•˜λŠ” λͺ¨λ“  ν›…κ³Ό μ»΄ν¬λ„ŒνŠΈλŠ” propsλ₯Ό λ°›μ•„λ“€μ—¬μ•Ό ν•©λ‹ˆλ‹€.
λ°˜ν™˜κ°’λ„ λ§ˆμ°¬κ°€μ§€μž…λ‹ˆλ‹€.

μ„€λͺ…

  1. ν›… / μ»΄ν¬λ„ŒνŠΈλ₯Ό ν™•μž₯ν•  경우, props와 λ°˜ν™˜κ°’μ„ μœ μ§€ν•΄μ•Όν•œλ‹€.
  2. λ¦¬μ•‘νŠΈλŠ” 상속이 μ—†κΈ° λ•Œλ¬Έμ—, ν™•μž₯μ‹œμ— 같은 νŒ¨ν‚€μ§€μ— props,
    λ°˜ν™˜κ°’ μΈν„°νŽ˜μ΄μŠ€λ₯Ό 상속 λ°›μ•„ ν™•μž₯ν•˜μ—¬ ν™œμš©ν•˜μž

μ½”λ“œ

import { useState } from 'react';
import {
  getFromLocalStorage,
  saveToLocalStorage,
  getFromRemoteStorage,
} from 'somewhere';

// λ¦¬μŠ€μ½”ν”„λŠ” ν›…μ˜ μΈν„°νŽ˜μ΄μŠ€(λ³€μˆ˜ 이름)와 μΌμΉ˜μ‹œν‚€κΈ° μœ„ν•΄μ„œ
// 데이터 μƒνƒœ λ³€μˆ˜μ˜ 이름을 localData둜 λ³€κ²½ν–ˆμŠ΅λ‹ˆλ‹€.
const useLocalStorage = ({ onDataSaved }) => {
  const [localData, setLocalData] = useState();

  useEffect(() => {
    const storageData = getFromLocalStorage();
    setLocalData(storageData);
  }, []);

  const saveToStorage = newData => {
    saveToLocalStorage(newData);
    onDataSaved(newData);
  };

  // 이 훅은 이제 "data" λŒ€μ‹  "localData"λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
  return { localData, saveToStorage };
};

// λ¦¬μŠ€μ½”ν”„λŠ” useLocalStorage의 ν”„λ‘­ μΈν„°νŽ˜μ΄μŠ€μ™€ μΌμΉ˜μ‹œν‚€κΈ° μœ„ν•΄
// 이 훅에 onDataSaved μ½œλ°±μ„ μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€,
const useLocalAndRemoteStorage = ({ onDataSaved }) => {
  const [localData, setLocalData] = useState();
  const [remoteData, setRemoteData] = useState();

  useEffect(() => {
    const storageData = getFromLocalStorage();
    setLocalData(storageData);
  }, []);

  useEffect(() => {
    const storageData = getFromRemoteStorage();
    setRemoteData(storageData);
  }, []);

  const saveToStorage = newData => {
    saveToLocalStorage(newData);
    onDataSaved(newData);
  };

  return { localData, remoteData, saveToStorage };
};

πŸ“Œ μΈν„°νŽ˜μ΄μŠ€ 뢄리 원칙

ISP - Interface Segregation Principle
μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” λ©”μ„œλ“œμ— μ˜μ‘΄ν•˜λ„λ‘ μ½”λ“œλ₯Ό κ°•μ œν•΄μ„œλŠ” μ•ˆ λ©λ‹ˆλ‹€.

μ„€λͺ…

  1. ν•¨μˆ˜μ™€ ν΄λž˜μŠ€λŠ” λͺ…μ‹œμ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€λ§Œ κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.
  2. ν•„μš”ν•œ μ½”λ“œμ˜ 쑴재, 즉 ν˜Όλž€μ„ ν”Όν•˜λŠ” 것
  3. μžŠμ§€ 말아야 ν•  것은 ν…ŒμŠ€νŠΈ κ°€λŠ₯μ„±

μ½”λ“œ(사둀)

// λŒ€μ•ˆ 1

// 이 μΈν„°νŽ˜μ΄μŠ€μ—λŠ” λ‹€μŒκ³Ό 같은 λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€.
// 속성이 μΈν„°νŽ˜μ΄μŠ€μ˜ 일뢀 μ†ŒλΉ„μžμ—κ²Œ ν•„μˆ˜μž„μ—λ„ λΆˆκ΅¬ν•˜κ³ 
// 선택적 속성이 ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
interface Website {
  companyName?: string;
  ownerFirstname?: string;
  ownerFamilyName?: number;
  domain: string;
  type: string;
}

// λŒ€μ•ˆ 2

// 이것은 μ›λž˜ μ›Ήμ‚¬μ΄νŠΈ μΈν„°νŽ˜μ΄μŠ€κ°€ μ‚¬λžŒμ— 맞게 이름이 바뀐 κ²ƒμž…λ‹ˆλ‹€.
// 즉, 이전 μ½”λ“œμ™€ ν…ŒμŠ€νŠΈλ₯Ό μ—…λ°μ΄νŠΈν•΄μ•Ό ν–ˆκ³ 
// 잠재적으둜 λͺ‡ 가지 버그가 λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.
interface PersonWebsite {
  ownerFirstname: string;
  ownerFamilyName: number;
  domain: string;
  type: string;
}

// νšŒμ‚¬μ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” μƒˆλ‘œμš΄ μΈν„°νŽ˜μ΄μŠ€μž…λ‹ˆλ‹€.
interface CompanyOwnedWebsite {
  companyName: string;
  domain: string;
  type: string;
}

μ½”λ“œ(ν•΄κ²°)

interface Person {
  firstname: string;
  familyName: string;
  age: number;
}

interface Company {
  companyName: string;
}

interface Website {
  domain: string;
  type: string;
}

πŸ“Œ μ˜μ‘΄μ„± μ—­μ „ 원칙

Dependency Inversion Principle, DIP
κ³ μˆ˜μ€€ λͺ¨λ“ˆμ€ μ €μˆ˜μ€€ λͺ¨λ“ˆλ‘œλΆ€ν„° 아무것도 κ°€μ Έμ˜€μ§€ 말아야 ν•˜λ©°,
λ‘˜ λ‹€ 좔상화에 μ˜μ‘΄ν•΄μ•Ό ν•©λ‹ˆλ‹€.

μ„€λͺ…

  1. HOC(Higher Order Component) ν™œμš©

μ½”λ“œ

const withAuth = (Component) => {
  return (props) => {
    const { user } = useContext(AuthContext)

    if (!user) {
      return <LoginComponent>
    }

    return <Component {...props} user={user} />
  }
}

const Profile = () => { // Profile μ»΄ν¬λ„ŒνŠΈ... }

// withAuth HOCλ₯Ό μ‚¬μš©ν•˜μ—¬ Profile μ»΄ν¬λ„ŒνŠΈμ— userλ₯Ό μ‚½μž…ν•©λ‹ˆλ‹€.
const ProfileWithAuth = withAuth(Profile)
profile
일지λ₯Ό κΎΈμ€€νžˆ μž‘μ„±ν•˜μž.
post-custom-banner

0개의 λŒ“κΈ€