[CS] 디자인 패턴 - 구조 패턴

팔랑이·2025년 1월 2일
0

CS

목록 보기
7/19
post-thumbnail

⛳️ 인프런 - cs 지식의 정석 강의를 듣고 학습한 내용입니다.


디자인 패턴

이전 게시글(생성 패턴, 행위 패턴)에 이어서 구조 패턴을 알아보도록 하자.

프록시 패턴

객체가 어떤 대상 객체에 접근하기 전, 대리자(Proxy) 가 접근에 대한 흐름을 가로채 해당 접근을 필터링하거나 수정하도록 하는 패턴이다. 주요 목적은 원 객체에 대한 제어.

Javascript Proxy 코드 예제

Javascript 엔진에는 Proxy가 내장되어 있어 다음과 같이 사용할 수 있다.

function createReactiveObject(target, callback) {
    // Proxy 객체 생성: target 객체를 프록시로 감싸서 속성 변경 시 인터셉트
    const proxy = new Proxy(target, {
        // set Trap 정의: 속성이 변경될 때 실행됨
        set(obj, prop, value) {
            // 새로운 값(value)이 기존 값(obj[prop])과 다를 때만 실행
            if (value !== obj[prop]) {
                const prev = obj[prop]; // 이전 값을 저장
                obj[prop] = value;     // 실제 객체의 값을 업데이트
                // 변경 사항을 콜백 함수로 전달
                callback(`${prop}가 [${prev}] >> [${value}] 로 변경되었습니다`);
            }
            return true; // 프로퍼티 설정 성공
        }
    });
    return proxy;
}

// 원본 객체 a
const a = {
    "형규": "솔로"
};

// a 객체를 프록시로 감싸서 반응형 객체로 만들기: 변경 사항을 console.log로 출력하도록 설정
const b = createReactiveObject(a, console.log);

// 속성 값 변경 시도 1: 값이 동일하므로 콜백 호출되지 않음
b.형규 = "솔로";

// 속성 값 변경 시도 2: 값이 변경되므로 콜백 호출됨
b.형규 = "커플";

// 출력: 형규가 [솔로] >> [커플] 로 변경되었습니다

Javascript의 Proxy에 대해 간단히 알아보고 넘어가자면,

  • Trap: get, set, deleteProperty 등 객체의 동작을 가로채는 메서드를 지원한다 (위에서는 set을 사용)
  • 동적 동작 추가: 기본 객체의 속성을 동적으로 관리하거나 제어할 수 있다.
  • 확장 가능: 객체를 변경하지 않고도 새로운 동작을 쉽게 추가할 수 있다.

위 코드에서는

  • createReactiveObject 함수는 Proxy를 이용해 대상 객체인 target을 감싸고, set 트랩을 적용해 속성이 변경된다면 특정 동작을 수행하도록 설정한다.
  • set 트랩은 객체의 속성 값이 변경될 때 실행되며, 변경 사항을 콜백으로 전달한다.

flux 패턴

이전 게시글에서 다룬 패턴들은 모두 'GoF 디자인 패턴'에 기반한 전통적인 패턴들이고, Flux 패턴은 현대 개발에서 새롭게 추가된 사례이다.

Flux 패턴은 페이스북에서 '읽음 표시' 기능 장애를 해결하기 위해 고안되었다. 당시 모델과 뷰 간의 복잡한 상호작용으로 인해, 뷰에서 발생한 동작이 모델에 영향을 미치고, 모델의 변경 사항이 다시 뷰에 영향을 주는 순환 구조가 있었다. 이러한 관계는 데이터의 일관성을 유지하기 어렵게 만들었고, 이를 해결하기 위해 데이터가 '한 방향'으로만 흐르도록 설계한 Flux 패턴을 도입하게 되었다고 한다.

flux 패턴의 구조

  • Action: 사용자의 이벤트를 담당한다. 마우스 클릭 또는 게시물 작성 등을 의미하며 해당 이벤트에 관한 객체를 만들어 dispatcher에 전달한다.
  • Dispatcher: 들어오는 Action 객체 정보를 기반으로 어떤 "행위"를 할 것인지 결정한다. 보통 Action 객체의 type을 기반으로 미리 만들어 놓은 로직을 수행하고, 이를 Store에 전달한다.
  • Store: 애플리케이션 상태를 관리하고 저장하는 계층으로, 도메인의 상태나 사용자의 인터페이스 등의 상태를 모두 저장한다.
  • View: 데이터를 기반으로 표출되는 사용자 인터페이스

Redux 라이브러리 내부 코드 예시

Redux 라이브러리는 Flux 패턴을 기반으로 만들어졌다.
Dispatcher에서 타입에 따른 행동을 명시해놓고, 이를 Store에 전달하는 코드를 확인할 수 있다.

다음은 Redux 라이브러리 내부 코드.

// 초기 상태 정의
const initialState = {
  visibilityFilter: 'SHOW_ALL', // 현재 필터 설정 (기본값: 'SHOW_ALL')
  todos: [] // 초기에는 비어 있는 할 일 목록
};

// 리듀서 함수: 상태(state)와 액션(action)을 받아 새로운 상태를 반환
function appReducer(state = initialState, action) {
  // Action 타입에 따라 switch로 상태를 처리
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER': {
      return Object.assign({}, state, {
        visibilityFilter: action.filter // 필터 값을 액션에서 가져옴
      });
    }

    case 'ADD_TODO': {
      return Object.assign({}, state, {
        todos: state.todos.concat({
          id: action.id, // 새 할 일의 ID
          text: action.text, // 새 할 일의 텍스트
          completed: false // 기본값: 완료되지 않음
        })
      });
    }

    case 'TOGGLE_TODO': {
      // todos 배열을 순회하며, 지정된 ID에 해당하는 항목의 완료 상태를 반전
      return Object.assign({}, state, {
        todos: state.todos.map(todo => {
          if (todo.id !== action.id) {
            return todo; // ID가 일치하지 않으면 기존 객체 반환
          }
          // ID가 일치하면 완료 상태를 반전
          return Object.assign({}, todo, {
            completed: !todo.completed
          });
        })
      });
    }

    case 'EDIT_TODO': {
      // todos 배열을 순회하며, 지정된 ID에 해당하는 항목의 텍스트를 업데이트
      return Object.assign({}, state, {
        todos: state.todos.map(todo => {
          if (todo.id !== action.id) {
            return todo; // ID가 일치하지 않으면 기존 객체 반환
          }
          // ID가 일치하면 텍스트를 업데이트
          return Object.assign({}, todo, {
            text: action.text
          });
        })
      });
    }

    default:
      return state; // 기존 상태를 그대로 반환
  }
}
profile
정체되지 않는 성장

0개의 댓글