디자인 패턴

디자인 패턴이란 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 '규약' 형태로 만들어 놓은 것을 의미함

예시1) 공부할 때 사용하는 다양한 학습 방법. 
수학 문제를 풀 때 특정한 유형의 문제를 해결하는데 있어서 사용하는 일정한 접근 방법

예시2) 다른 사람들이 문서를 볼 때에도 쉽게 이해할 수 있도록
문서를 작성할 때에 항상 같은 구조(패턴)를 가지고 작성하는 경우

싱글톤 패턴

  • 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴. 하나의 클래스를 기반으로 단 하나의 인스턴스를 만들어 이를 기반으로 로직을 만드는 데 사용되며. 인스턴스를 생성할 때 드는 비용이 줄어드는 장점이 있지만 의존성이 높아진다는 단점이 있다.

사용하는 경우

  • 자원 공유: 여러 곳에서 같은 자원에 접근해야 하는 경우에 유용합니다. 예를 들어, 로그 관리자나 설정 매니저와 같이 애플리케이션 전체에서 한 번만 생성되어야 하는 객체들을 관리할 때 사용될 수 있습니다.

  • 상태 공유: 상태를 관리해야 하는 상황에서 사용될 수 있습니다. 예를 들어, 게임 엔진에서 게임 상태를 관리하는 객체를 싱글톤으로 구현할 수 있습니다.

  • 리소스 풀링: 리소스를 관리하는 데에도 사용될 수 있습니다. 예를 들어, 데이터베이스 연결풀이나 스레드 풀과 같이 많은 리소스를 관리해야 할 때 사용될 수 있습니다.

단점

  • 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트마다 독립적인 인스턴스를 만들기가 어려움.
  • 모듈 간의 결합을 강하게 만들 수 있다는 단점이 있음(의존성 주입으로 모듈간의 결합을 조금 더 느슨하게 만들어 해결할 수 있음)

의존성 주입?

메인 모듈이 직접 다른 하위 모듈에 대한 의존성을 주지 않고 중간에 의존성 주입자가 이 부분을 가로채 메인 모듈이 간접적으로 의존성을 주입하는 방식
이를 통해 메인 모듈(상위 모듈)은 하위 모듈에 대한 의존성이 떨어지게 된다. (디커플링이 된다.)

의존성 주입 전: 메인 모듈 -> 하위 모듈
의존성 주입 후: 의존성 주입자 -> 메인 모듈, 의존성 주입자 -> 하위 모듈

의존성 주입의 장점?
모듈들을 쉽게 교체할 수 있는 구조가 되어 테스팅하기 쉽고 마이그레이션하기도 수월함. 또한 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어주기 때문에 애플리케이션 의존성 방향이 일관되고, 애플리케이션을 쉽게 추론할 수 있으며, 모듈 간의 관계들이 조금 더 명확해진다.

의존성 주입의 단점?
모듈들이 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며, 약간의 런타임 페널티가 생길 가능성이 있음

의존성 주입 원칙
상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 하며 둘 다 추상화에 의존해야 함. 이때 추상화는 세부사항에 의존하지 않아야 함

팩토리 패턴

객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴.

  • 느슨한 결합을 가지며 더 많은 유연성을 갖게 된다.
  • 코드 리팩토링시 유지 보수성이 증가한다.

사용하는 경우

  • 객체 생성을 캡슐화해야 할 때: 객체 생성 과정이 복잡하거나 객체를 생성하는 방법이 바뀔 가능성이 있는 경우에 사용됩니다.

  • 여러 종류의 객체를 생성해야 할 때: 여러 종류의 객체를 생성하는데 사용됩니다.

  • 객체 생성을 중앙 집중화하고 관리해야 할 때: 객체 생성 로직을 중앙 집중화하여 객체를 일관되게 생성하고 관리해야 하는 경우에 사용됩니다.

  • 객체 생성과 관련된 의존성을 줄일 때: 클라이언트 코드가 객체를 생성하는 데 직접적으로 의존하지 않도록 하기 위해 사용됩니다.

옵저버 패턴

주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때 옵저버들에게 변화를 알려주는 디자인 패턴

  • 주체: 객체의 상태 변화를 보고 있는 관찰자
  • 옵저버: 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 추가 변화 사항이 생기는 객체들

사용하는 경우

  • 이벤트 처리: 객체 간에 이벤트 발생을 통지하고 처리하는데 사용됩니다. 예를 들어, GUI 프로그래밍에서 버튼 클릭, 키 입력 등의 이벤트를 처리할 때 옵저버 패턴을 사용하여 이벤트를 발생시키고 관련된 객체들에게 통지할 수 있습니다.

  • 모델-뷰-컨트롤러 (MVC) 패턴: MVC 아키텍처에서 모델과 뷰 간의 데이터 변경을 통지하고 처리하는데 사용됩니다. 모델이 변경되면 뷰는 이를 감지하여 업데이트하고, 컨트롤러는 모델의 변경을 처리합니다. 이때 옵저버 패턴을 사용하여 모델과 뷰 사이의 통신을 담당합니다.

  • 분산 시스템: 분산 환경에서 다양한 컴포넌트 간의 통신과 데이터 동기화를 처리하는데 사용됩니다. 이때 옵저버 패턴을 사용하여 분산 시스템의 상태 변화를 관찰하고 처리할 수 있습니다.

js에서의 옵저버 패턴

자바스크립트에서의 옵저버 패턴은 프록시 객체를 통해 구현할 수도 있음

프록시 객체
어떠한 대상의 기본적인 동작의 작업을 가로챌 수 있는 객체로 두 개의 매개변수를 가진다.(프록시 패턴이 녹아들어 있는 객체)

  • target: 프록시할 대상
  • handler: target 동작을 가로채고 어떠한 동작을 할 것인지 설정되어 있는 함수

예시1) get
속성과 함수에 대한 접근을 가로챈다.

const target = {
    name: "Alice",
    age: 30
};

const proxy = new Proxy(target, {
    get(obj, prop) {
        console.log(`속성 "${prop}"에 접근했습니다.`);
        return obj[prop];
    }
});

console.log(proxy.name); // "속성 "name"에 접근했습니다." 출력 후 "Alice" 반환
console.log(proxy.age); // "속성 "age"에 접근했습니다." 출력 후 30 반환

예시2) has
in 연산자의 사용을 가로챈다.

const target = {
    name: "Alice",
    age: 30
};

const proxy = new Proxy(target, {
    has(obj, prop) {
        console.log(`속성 "${prop}"가 존재하는지 확인했습니다.`);
        return prop in obj;
    }
});

console.log("name" in proxy); // "속성 "name"가 존재하는지 확인했습니다." 출력 후 true 반환
console.log("gender" in proxy); // "속성 "gender"가 존재하는지 확인했습니다." 출력 후 false 반환

예시3) set()
속성에 대한 접근을 가로챈다.

const target = {
    name: "Alice",
    age: 30
};

const proxy = new Proxy(target, {
    set(obj, prop, value) {
        if (prop === "age" && typeof value !== "number") {
            throw new TypeError("나이는 숫자여야 합니다.");
        }
        console.log(`속성 "${prop}"에 값을 할당했습니다.`);
        obj[prop] = value;
        return true;
    }
});

proxy.name = "Bob"; // "속성 "name"에 값을 할당했습니다." 출력
console.log(proxy.name); // "Bob" 출력

proxy.age = 35; // "속성 "age"에 값을 할당했습니다." 출력
console.log(proxy.age); // 35 출력

proxy.age = "thirty"; // TypeError: 나이는 숫자여야 합니다. 오류 발생

프록시 패턴

대상 객체에 접근하기 전 그 접근에 대한 흐름을 가로채 해당 접근을 필터링하거나 수정하는 등의 역할을 하는 계층이 있는 패턴으로 객체의 속성, 변환 등을 보완하며 프록시 객체로 쓰이기도 하지만 프록시 서버로도 활용됨

사용하는 경우

  • 접근 제어: 객체에 대한 접근을 제어하고자 할 때 사용됩니다. 이를 통해 보안을 강화하거나 객체의 무결성을 보호할 수 있습니다.

  • 캐싱: 객체에 대한 요청을 캐싱하여 성능을 향상시키고 불필요한 작업을 줄일 수 있습니다. 프록시는 요청을 받아 캐시된 데이터를 반환하거나 새로운 데이터를 가져와서 캐시에 저장할 수 있습니다.

  • 지연 로딩: 객체의 생성이나 초기화에 시간이 많이 소요되는 경우, 프록시를 사용하여 객체를 필요한 시점에만 실제로 생성하거나 초기화할 수 있습니다. 이를 통해 시스템 부하를 줄이고 성능을 향상시킬 수 있습니다.

  • 로깅 및 감시: 객체의 메서드 호출이나 속성 접근을 감시하고 로깅하기 위해 사용될 수 있습니다. 프록시는 메서드 호출이나 속성 접근을 가로채어 추가적인 로깅 또는 감시를 수행할 수 있습니다.

프록시 서버

서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템 또는 응용 프로그램.

nginx

주로 Node.js 서버 앞단의 프록시 서버로 활용하여 익명 사용자가 직접적으로 서버에 접근하는 것을 차단하고, 간접적으로 한 단계를 더 거치게 만들어 보안을 강화함.

cloudFlare

전 세계적으로 분산된 서버가 있고 이를 통해 어떠한 시스템의 콘텐츠 전달을 빠르게 할 수 있는 CDN 서비스로 웹 서버 앞단에 프록시 서버를 두어 DDOS 공격 방어나 HTTPS 구축에 사용함.

CORS와 프론트엔드의 프록시 서버

CORS: 서버가 웹 브라우저에서 리소스를 로드할 때 다른 오리진을 통해 로드하지 못하게 하는 HTTP 헤더 기반 메커니즘

프론트엔드 개발 시 프론트 서버를 만들어 백엔드 서버와 통신할 때 주로 CORS 에러를 마주치는데, 이를 해결하기 위해 프론트엔드에서 프록시 서버를 만들기도 함.

예를 들어 포트 번호가 다를 경우 프록시 서버를 둬서 프론트엔드에서 요청되는 오리진의 포트번호를 변경해서 맞춰주는 것. 이를 통해 CORS 해결은 물론 다양한 API 서버와의 통신도 가능하도록 함.

이터레이터 패턴

이터레이터를 사용하여 컬렉션의 요소들에 접근하는 패턴으로 코드의 유연성을 높이고, 컬렉션의 구조가 변경되어도 코드를 수정할 필요가 없게끔 함.

노출모듈 패턴

즉시 실행 함수를 통해 private, public 같은 접근 제어자를 만드는 패턴으로 자바스크립트는 접근 제어자가 존재하지 않고 전역 범위에서 스크립트가 실행된다. 따라서 노출 모듈 패턴을 통해 접근제어자를 구현함.

이 패턴은 모듈의 구현 세부 정보를 숨기고 필요한 부분만 공개하여 모듈을 사용하는 코드를 단순화하는 데 사용됨.

MVC 패턴

모델, 뷰, 컨트롤러로 이루어진 디자인 패턴으로 재사용성과 확장성이 용이한 반면 애플리케이션이 복잡해질수록 모델과 뷰의 관계가 복잡해진다는 단점이 있음.

- 모델
애플리케이션의 데이터인 데이터베이스, 상수, 변수 등을 말함. 
뷰에서 데이터를 생성하거나 수정하면 컨트롤러를 통해 모델을 생성하거나 갱신함

- 뷰
모델을 기반으로 사용자가 볼 수 있는 화면(인풋, 체크박스 등 사용자 인터페이스 요소)
모델이 가지고 있는 정보를 따로 저장하지 않아야하며 단순히 화면에 표시하는 정보만 가지고 있어야 함

- 컨트롤러
하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할 + 이벤트 등 메인 로직 담당
모델과 뷰의 생명주기도 관리하며 모델이나 뷰의 변경 통지를 받으면 각각의 구성 요소에게 전달함.

특징

  • 모델과 뷰 사이에 강한 의존성이 존재함.
  • 컨트롤러는 모델의 상태를 변경하고 적절한 뷰를 선택하여 업데이트.
  • 주로 서버 기반 웹 애플리케이션에서 많이 사용됨.

MVP 패턴

MVC 패턴으로부터 파생되었으며 컨트롤러가 프레젠터로 교체된 패턴. 한 개의 뷰는 하나의 프레젠터와만 연결되어 있는 일대일 관계이기 때문에 MVC 패턴보다 더 강한 결합을 지님.

특징

  • 프레젠터가 뷰와 모델 사이의 중개자 역할을 하며, 뷰에 대한 로직을 처리.
  • 뷰와 모델을 완전히 분리하여 테스트 용이성 향상.
  • 주로 안드로이드 애플리케이션에서 많이 사용됨.

MVVM 패턴

MVC의 컨트롤러가 뷰모델로 바뀐 패턴. 뷰모델은 뷰를 더 추상화한 계층으로 뷰와 뷰모델 사이의 양항향 데이터 바인딩을 지원함.

데이터 바인딩: 
화면에 보이는 데이터와 웹 브라우저의 메모리 데이터를 일치시키는 기법, 뷰모델을 변경하면 뷰가 변경됨.

특징

  • 뷰모델은 뷰에 표시할 데이터를 가공하고 준비함.
  • 데이터 바인딩을 통해 뷰와 뷰모델이 자동으로 동기화 됨.
  • 주로 웹 애플리케이션에서 많이 사용됨.

컨트롤러, 프레젠터, 뷰모델 차이점

컨트롤러(Controller):

컨트롤러는 요리사, 요리사는 요리를 만들어내는데 필요한 재료(데이터)를 사용하여 요리(애플리케이션 로직)를 만들지만 직접 요리를 손님에게 제공하지 않고, 서빙하는 역할은 서빙하는 사람(뷰)이 함.

예를 들어, 웹 애플리케이션에서 로그인 기능을 가진 페이지를 만들 경우 컨트롤러는 사용자가 제출한 로그인 양식 데이터를 받아와서 데이터베이스에 사용자가 있는지 확인하고, 그 결과를 바탕으로 로그인 성공 또는 실패 페이지를 준비한 다음 결과를 뷰에 전달하여 적절한 화면을 사용자에게 보여줌.

프레젠터(Presenter):

프레젠터는 가이드나 해설사, 프레젠터는 사용자에게 정보를 제공하고, 그들의 질문에 답하며, 관심 있는 부분을 안내함. 그러나 사용자가 실제로 여행지(데이터)를 경험하는 것은 아님.

예를 들어, 모바일 애플리케이션에서 사용자의 프로필 페이지의 경우 프레젠터는 사용자의 프로필 정보를 표시하고, 사용자가 프로필을 편집할 수 있는 옵션을 제공하며, 사용자의 액션에 따라 화면을 업데이트함.

뷰모델(ViewModel):

뷰모델은 정보를 제공하는 역할. 뷰모델은 데이터의 가공과 준비를 담당하고, 뷰가 필요로 하는 데이터를 제공.

예를 들어, 온라인 쇼핑 애플리케이션의 상품 목록 페이지의 경우 뷰모델은 서버로부터 받은 상품 데이터를 가져와서 가격을 포맷하고, 이미지를 준비하고, 필요한 정보만 추출하여 뷰에 전달함.

컨트롤러는 데이터를 처리하고 뷰에 전달합니다.
프레젠터는 데이터를 사용자에게 보여주고 상호작용을 관리합니다.
뷰모델은 데이터를 가공하고 뷰에 표시할 준비를 합니다.
각각의 역할은 사용자와 데이터 간의 중개자 역할을 하지만, 그 방식과 목적은 조금씩 다릅니다.

profile
차근차근 배워나가는 개생아🐾

0개의 댓글