
Web을 위한 16가지 디자인 패턴
2022.09.08
dev
(Patterns.dev의 내용을 요약/정리한 것입니다)
Design Patterns (16가지)Singleton PatternProxy PatternProvider PatternPrototype PatternContainer/Presentational PatternObserver PatternModule PatternMixin PatternMediator/Middleware PatternHOC PatternRender Props PatternHooks PatternFlyweight PatternFactory PatternCompound PatternCommand Pattern
Design Patterns (16가지)
Singleton Pattern
Share a single global instance throughout our application
- Singleton : 단 하나의 인스턴스만 만들 수 있고, 전역적으로 접근 가능해야함
- Singleton을 통해 어플리케이션의 global state를 효과적으로 관리할 수 있게끔 함
Object.freeze(object);
- JS에서는 object를 단순하게 만들 수 있기 때문에 anti-pattern으로 여겨짐
Proxy Pattern
Intercept and control interactions to target objects
- proxy는 대리인 이라는 뜻. 즉, object에 interact directly하는게 아니라 proxy object를 통해 interact
get
,set
이 가장 유명한 메서드
Proxy
Reflect
- pros : validation, formatting, notification, debugging 할 때 사용
- cons : affect performance if you use Proxy too much
Provider Pattern
Make data available to multiple child components
- 자식 컴포넌트들에서 데이터를 쓸 때 props drilling이라는 anti-pattern이 있음
- Context Object의
Provider
이라는 HOC(High Order Component)를 이용하기
React.createContext()
Context.Provider
useContext
- ex) styled-components의 Theme Provider
- pros : props-drilling 막을 수 있음, 디버깅 쉬움
- cons : rerender을 발생시켜 성능 저하 가능. 여러개의 provider을 사용하기
Prototype Pattern
share properties among many objects of the same type
- prototype
- prototype chain : JS가
__proto__
가 가리키는 object를 재귀적으로 올라가는 것
- 이게 왜 pattern인지 모르겠으나 class개념이 없는 JS에서 object가 어떻게 상속을 하는지(=prototype을 통해) 알 수 있도록
- pros : prototpye chain을 통해 method나 properties의 duplication을 막아 메모리를 절약할 수 있다.
Container/Presentational Pattern
Enforce seperation of concerns by seperating the view from the application logic
- 관심사 분리(seperation concerns)의 방법 중 하나
- 아래 두 파트로 나눔
- presentational components : how data is shown to user. application logic을 다룸 ex) rendering list of dog images
- container components : what data is shown to user. ex) fetching the dog images
- 간단함. presentational components들은 props로 보여줄 데이터를 container components로 부터 받아 다듬어서 보여주면 됨
- 그러나 이 패턴은 대부분의 경우 React Hooks를 통해 대체됨. Hooks는 container components 없이도 statefulness를 추가할 수 있도록 도와줌
- pros : 관심사 분리, presentational components의 재사용성 증진, p.c.의 경우 application logic을 몰라도 수정 가능하다(디자이너라던지), p.c.의 경우 순수함수라서 testing이 쉽다.
- cons : Hooks라는 강력한 도구의 등장으로 이 패턴이 대체됨… 덕분에 statefulness를 위해 functional 대신 class components를 사용하는 것은 no more.
Observer Pattern
Use observables to notify subscribers when an event occurs
observers
subscribe()
/unsubscribe()
notify()
- asynchronous, event-based data에 유용하다
- pros : 관심사 분리, 단일 책임 원칙을 지키도록 강제함(observable object → 이벤트 모니터링 / observer object → 받은 데이터 처리. 이 둘은 결합(coupling)되지 않아 언제든 (de)coupled 가능)
- cons : observer가 너무 복잡해지면 모든 subscribers에 notifying하는 것에 퍼포먼스 이슈를 발생
Module Pattern
Split up your code into smaller, reusable pieces
- module pattern은 코드를 작고, 재사용 가능한 조각으로 쪼개줌
- 변수들이 file안에서 private함 → name collision 방지
export
&import
문이 바로 이런 module pattern을 이용하는 것
- React에서 사용하는 components들도 module을 만드는 것
- props : encapsulate parts of the code, name collision, global scope pollution 방지
Mixin Pattern
Add functionality to objects or classes without inheritance
mixin
: class나 object에 상속 없이도 reusable custom function을 추가하도록 하는 object
Object.assign
메서드를 통해 mixin을 prototype에 추가할 수 있음
- mixin끼리 상속이 가능하다
- ex) 브라우저의
Window
interface :WindowOrWorkerGlobalScope
&WindowEventHandlers
mixins를 가져와서setTimeout
,setInterval
,indexedDB
,isSecureContext
등의 속성을 사용가능함
- ex) React의 사례 : ES6 classes 개념의 등장 전까지는 mixin을 통해 컴포넌트에 functionality를 추가했지만, mixin 대신 high order components를 사용하도록 권장하고, 지금은 Hooks를 권장.
Mediator/Middleware Pattern
Use a central mediator object to handle communication between components
- 중재자(mediator)을 통해 컴포넌트가 서로 interact할 수 있도록 함 (파일럿-관제탑 같은)
- JS에서 mediator은
object literal
이나function
에 불과한 경우가 많음.
- ex) Express.js → middleware
HOC Pattern
Pass reusable logic down as props to components throughout your application
- 고차 컴포넌트(higher order component, HOC)를 통해 여러 컴포넌트에서 동일한 로직(스타일링, auth 요청, global state 추가 등)을 여러번 사용할 수 있다.
- HOC : 다른 컴포넌트를 받는 컴포넌트
function withStyles(Component) { // withStyles가 HOC return props => { const style = { padding: '0.2rem', margin: '1rem' } return <Component style={style} {...props} /> } } const Button = () = <button>Click me!</button> const Text = () => <p>Hello World!</p> const StyledButton = withStyles(Button) const StyledText = withStyles(Text)
- HOC를 compose 가능하다 (=HOC를 다른 HOC가 wrap 가능)
- 대부분의 경우 HOC 패턴을 React Hooks로 대체 가능하지만, 일반적으로 React Hooks가 HOC 패턴을 대체하지는 않음(공식문서) → 상황에 맞게 HOC, Hooks 둘 다 사용해야
- Hooks를 사용하면 HOC 때문에 발생하는 deeply nested tree를 막을 수 있고, wrap하지 않아도 된다.
- HOC를 사용하면 같은 로직을 여러 컴포넌트에 한번에 제공할 수 있고, Hooks를 사용하면 컴포넌트 안에서 custom behavior을 추가할 수 있지만 여러 컴포넌트들이 이 behavior에 의존하게 된다면 HOC와 다르게 버그를 낼 가능성이 높아진다?
- HOC 사용이 권장되는 경우 :
- same, uncustomized behavior가 여러 컴포넌트에서 사용되어야할 때
- 컴포넌트가 custom logic의 추가 없이 혼자서도 작동할 때
- Hooks 사용이 권장되는 경우 :
- 각각의 컴포넌트별로 behavior가 customized 되어야할 때
- behavior가 전체적인 application에 퍼지지 않고 몇개의 컴포넌트에서만 사용될 때
- behavior가 컴포넌트에 많은 properties를 추가할 때
- ex) Apollo Client →
graphql()
HOC 또는useMutation
hook을 이용할 수 있음
- pros : logic을 한 곳에서 관리 → 코드를 DRY하게, 관심사 분리
- cons : naming collision 발생 가능
Render Props Pattern
Pass JSX elements to components through props
- lifting states 대신
render props
를 사용하는 방법
children
도 render prop으로 넘겨주는 것이다.
- ex) Apollo Client hooks
- pros : 높은 컴포넌트 재사용성. 관심사 분리. HOC처럼 reusability, sharing data를 풀면서도 HOC에서 있던 naming collision, implicit props 이슈를 해결할 수 있음
- cons : lifecycle methods를 render prop에 추가할 수 없음 → hooks로 대부분 대체됨
Hooks Pattern
Use functions to reuse stateful logic among multiple components throughout the app
- React 16.8에서 등장
- 많은 전통적인 디자인 패턴이 Hooks로 대체됨
- class components
- State, Lifecycle Methods(
componentDidMount
,componentWillUnmount
) → - ES2015 class에 대한 이해를 동반해야했음 (
bind
,constructor
,this
등) - HOC나 Render Props 방법으로 하면 wrapper hell이 발생 → data flow를 확인하기 어려움
- 컴포넌트의 로직이 얽혀서 복잡도 증가
- hooks
- React Hooks → 컴포넌트의 상태관리와 라이프사이클 관리를 할 수 있는 function
useState
: 상태관리useEffect
: lifecycle 관리- build-in hooks(
useState
,useEffect
,useReducer
,useRef
,useContext
,useMemo
,useImperativeHandle
,useLayoutEffect
,useDebugValue
,useCallback
) 뿐 아니라 custom hooks도 만들 수 있음 → rules of Hooks를 따르면 됨
- seperate the logic이 더 clear하다
- 컴포넌트에서 stateful logic을 재사용함으로써 testability, flexibility, readability가 모두 증가!
- pros : 적은 라인의 코드, 복잡한 컴포넌트 단순화, stateful logic 재사용, non-visual logic 공유
- cons : rule of hooks을 지켜야함, 올바르게 사용하기 위해 러닝커브가 있음, 잘못된 사용을 조심하기(useCallback, useMemo)
Flyweight Pattern
Reuse existing instances when working with identical objects
- Object를 만들 때 공유해서 사용함으로써 메모리를 절약하는 패턴
- JS에서는 프로토타입 상속(prototypal inheritance)으로 통해 쉽게 구현 가능
Factory Pattern
Use a factory function in order to create objects
- factory functions :
new
키워드를 사용하지 않고 새로운 object를 반환하는 함수
- pros : 같은 properties를 공유하는 여러개의 작은 object를 만들 때
- cons : 패턴이라기 보다 함수에 가까운 간단함. ES6 arrow function도 일종의 factory functions. 새로운 instance를 만드는게 새로운 object를 만드는 것 보다 메모리 효율적이다.
Compound Pattern
Create multiple components that work together to perform a single task
- 서로 dependent한 컴포넌트
Context API
, React.Children.map
- ex) Semantic UI
- pros : child component를 따로 import하지 않아도 됨
- cons : component nesting이 제한적(direct children만 parent component에 access 가능), React.cloneElement → shallow merge를 하기 때문에 naming collision 발생 가능
Command Pattern
Decouple methods that execute tasks by sending commands to a commander
- 클래스 안에 methods를 만들지 말고, 외부에
command functions
를 따로 두는 것
- object와 command functions를 분리하자 ← command pattern
- pros : 메서드를 operation을 실행하는 object와 decouple,
- cons : 사용처가 아주 한정적임, 불필요한 보일러플레이트 추가