[CS] Design Pattern & Programming Paradigm

·2024년 11월 22일
post-thumbnail

[면접을 위한 CS 전공지식 노트] 책을 사놓고 이제야 읽기 시작했다...!
시간 있을 때 미리미리 공부를 열심히 해두자 !!!
고럼 레쓰고

Design Pattern

  • 프로그램 설계시 발생했던 문제점들을 객체 간 상호 관계 등을 이용해 해결할 수 있도록 하나의 '규약' 형태(재사용 가능한 설계 방법)로 만들어 놓은 것

1. 싱글톤 패턴

class Singleton {
  constructor() {
    if (!Singleton.instance) {
      Singleton.instance = this
    }
    return Singleton.instance
  }
    
  getInstance() {
    return this
  }
}
const a = new Singleton();
const b = new Singleton();
console.log(a === b) // true
  • 특징
    • 하나의 클래스에 하나의 인스턴스
    • 보통 데이터베이스 연결 모듈에 자주 사용됨
  • 단점
    • 싱글톤은 미리 생성된 하나의 인스턴스 기반이므로 TDD(Test Driven Development) 단위 테스트시, 각 테스트마다 독립적인 인스턴스 생성 힘듦
      • TDD 단위 테스트 → 테스트가 독립적이고 어떤 순서로든 실행할 수 있어야 함
  • 의존성 주입(DI, Dependency Injection)
    • 메인모듈의 하위모듈에 대한 직접적인 의존성을 의존성 주입자를 통해 간접적인 의존성으로 → 디커플링
    • 의존성 주입의 원칙
      • 상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 한다. 둘 다 추상화에 의존하며 추상화는 세부 사항에 의존하지 않아야 한다.
    • 장점 : 모듈 교체가 용이해져 마이그레이션 수월
    • 단점 : 모듈이 분리되어 클래스 수 증가로 복잡성 증가, 런타임 페널티

2. 팩토리 패턴

// 전달받은 값에 따라 다른 객체를 생성하며 인스턴스 타입 정함
const num = new Object(42);
const str = new Object('abc');
num.constructor.name; // Number
str.constructor.name; // String
class CoffeeFactory {
  // 정적 메서드 => 클래스 기반으로 객체 만들지 않고 호출 가능
  // 해당 메서드에 대한 메모리 할당을 한 번만 할 수 있음
  static createCoffee(type) {
    const factory = factoryList[type]
    return factory.createCoffee()
  }
}

class Latte {
  constructor() {
    this.name = "latte"
  }
}

class Espresso {
  constructor() {
    this.name = "Espresso"
  }
}

class LatteFactory extends CoffeeFactory{
  static createCoffee() {
    return new Latte()
  }
}

class EspressoFactory extends CoffeeFactory{
  static createCoffee() {
    return new Espresso()
  }
}

const factoryList = { LatteFactory, EspressoFactory }

const main = () => {
  const coffee = CoffeeFactory.createCoffee("LatteFactory")
  console.log(coffee.name) // latte
}
main()
  • 특징
    • 객체 생성 부분을 뗴어내 추상화한 패턴
    • 상속 관계의 두 클래스 중 상위 클래스는 뼈대 결정, 하위 클래스는 객체 생성에 대한 구체적인 내용 결정
  • 장점
    • 상위 클래스에서 인스턴스 생성 방식에 대해 알 필요가 없어 유연성 증가
    • 객체 생성 로직이 분리되어 있어 유지보수 용이

3. 전략 패턴

  • 특징
    • 객체의 행위를 바꾸고 싶을 때 직접 수정하지 않고 캡슐화된 알고리즘(전략)을 컨텍스트 안에서 바꿔주는 패턴

4. 옵저버 패턴

  • 특징

    • 주체가 어떤 객체의 상태 변화를 관찰하다가 변화가 생기면 메서드 등을 통해 옵저버들에게 변화를 알리는 패턴
    • ex. 트위터
    • 옵저버 패턴MVC 패턴에도 사용됨 (모델-주체 에 변경 사항이 생기면 update() 메서드로 옵저버인 뷰에 알려줌 → 컨트롤러 작동)
  • JS의 옵저버 패턴

    • 프록시 객체

      // Proxy 객체 : 속성 접근, 할당 등등 작업을 가로채서 로직 강제
      const handler = {
        get: function(target, handler) {
          return name === 'name' ? `${target.a} ${target.b}` : target[name]
        }
      }
      const p = new Proxy({ a: 'YOOJIN', b: 'ZZANGZZANG' }, handler)
      console.log(p.name) // YOOJIN ZZANGZZANG
    • 프록시 객체를 이용한 옵저버 패턴

      function createReactiveObject(target, callback) {
        const proxy = new Proxy(target, {
          // proxy 객체의 set()으로 속성에 대한 접근 가로채서 변화 감시
          set(obj, prop, value) {
            if (value !== obj[prop]) {
              const prev = obj[prop];
              obj[prop] = value;
              callback(`${prop}이 [${prev}] >> [${value}]로 변경됨`);
            }
            return true;
          }
        })
        return proxy;
      };
      
      const a = {
        "윶": "직장인"
      };
      const b = createReactiveObject(a, console.log);
      b.= "직장인";
      b.= "백수";
      
      // 윶이 [직장인]에서 [백수]로 변경됨
      • get()은 속성, 함수에 대한 접근을 가로챔
      • has()는 in 연산자의 사용 가로챔
      • set()은 속성에 대한 접근을 가로챔

5. 프록시 패턴과 프록시 서버

  • 특징

    • 대상 객체에 접근하기 전 접근에 대한 흐름을 가로채 접근을 필터링하거나 수정하는 역할의 계층이 있는 디자인 패턴
    • 객체의 속성, 변환 등을 보완하며 보안, 데이터 검증, 캐싱, 로깅에 사용 (프록시 객체, 프록시 서버로도 활용)
  • 프록시 서버

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

    • CDN을 프록시 서버로 캐싱 가능

    • nginx

      • 비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹서버
      • Node.js 서버 앞단의 프록시 서버로 활용 → 익명 사용자의 서버 직접 접근 차단 (보안 강화)
    • CloudFlare

      • 웹 서버 앞단에 프록시 서버로 두어 DDOS 공격 방어나 HTTPS 구축에 쓰임
      • DDOS 공격 방어
        • DDOS : 짧은 시간 동안 네트워크에 많은 요청 보내 네트워크 마비 → 웹 사이트 가용성 방해
        • CloudFlare는 의심스러운 트래픽 자동 차단
      • HTTPS 구축
        • CloudFlare를 통해 별도의 인증서 설치 없이 쉽게 구축 가능
    • CORS와 프론트엔드의 프록시 서버
      CORS : 서버가 웹 브라우저에서 리소스 로드시 다른 오리진을 통해 로드하지 못하게 하는 HTTP 헤더 기반 메커니즘
      프론트에 프록시 서버를 둬서 프론트 서버에서 요청되는 오리진을 백엔드 서버 오리진으로 바꿔줌

6. 이터레이터 패턴

const mp = new Map();
mp.set('a', 1);
mp.set('b', 2);
mp.set('c', 3);

const st = new Set();
st.add(1);
st.add(2);
st.add(3);

// 이터레이터 프로토콜 : for a of b
for (let a of mp) console.log(a);
for (let a of st) console.log(a);

/*
['a', 1]
['b', 2]
['c', 3]
1
2
3
*/
  • 특징
    • 이터레이터를 통해 컬렉션 요소에 접근하는 디자인 패턴 (순회 가능한 자료구조와 상관 없이 이터레이터 하나의 인터페이스로 순회 가능)
    • 위 코드에서 set, map 다른 자료구조인데도 똑같이 for a of b (이터레이터 프로토콜)로 순회

7. 노출모듈 패턴

const pukuba = (() => {
  const a = 1;
  const b = () => 2;
  const public = {
    c: 2,
    d: () => 3
  }
  return public
})();

console.log(pukuba);
// { c: 3, d: [Fuction: d] }
console.log(pukuba.a, pukuba.b);
// undefined undefined
  • 특징
    • 즉시 실행 함수((() => {})(), 선언 동시에 실행)를 통해 private, public 같은 접근 제어자를 만드는 패턴
    • JS는 접근 제어자가 없고 전역 범위에서 스크립트가 실행되므로, 노출모듈 패턴을 통해 접근 제어자 구현하기도 함

8. MVC 패턴

  • 특징
    • Model
      • 데이터베이스, 상수, 변수
      • 뷰에서 데이터 생성/수정하면 컨트롤러를 통해 모델 생성/갱신
    • View
      • 사용자 인터페이스 요소
      • 모델의 정보 저장 X
      • 변경이 일어나면 컨트롤러로 전달
    • Controller
      • 하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할 (이벤트 등 메인 로직 담당)
      • 모델과 뷰의 생명주기 관리, 모델/뷰의 변경 통지를 받으면 이를 각각 구성요소에 알려줌
  • Spring
    • MVC 패턴 적용한 대표적인 프레임워크

9. MVP 패턴

  • 특징
    • MVC로부터 파생됨 (Controller가 Presenter로 교체)
    • 뷰와 프레젠터가 일대일 관계이므로 MVC보다 강한 결합

10. MVVM 패턴

  • 특징
    • MVC의 Controller가 View Model로 바뀐 패턴
    • 뷰모델은 뷰를 더 추상화한 계층으로, 커맨드(여러 요소를 한 액션으로 처리)와 데이터 바인딩(화면에 보이는 데이터와 웹 브라우저의 메모리 데이터 일치시키기)을 가짐
    • 뷰와 뷰모델 사이의 양방향 바인딩 → UI를 별도의 코드 수정 없이 재사용 가능
  • Vue.js

Programming Paradigm

1. 선언형과 함수형 프로그래밍

  • 특징

    • 선언형 프로그래밍 : '무엇을' 풀어내는가에 집중, "프로그램은 함수로 이루어진 것이다" 명제가 담긴 패러다임
    • 함수형 프로그래밍은 선언형 프로그래밍의 일종
    • '순수 함수'들을 블록처럼 쌓아 로직 구현 → '고차 함수'를 통해 재사용성 높임
    • JS는 함수가 일급 객체이므로 객체지향보단 함수형이 선호됨
  • 순수 함수

    const pure = (a, b) => {
      return a + b;
    };
    
    const list = [1, 2, 3, 4, 5, 11, 12];
    const ret = list.reduce((max, num) => num > max ? num : max, 0)
    console.log(ret) // 12
    • 출력이 입력에만 의존
    • 함수에 들어가는 매개변수 이외의 전역 변수가 출력에 영향을 주면 순수 함수가 아님!
  • 고차 함수

    • 함수가 함수를 값처럼 매개변수로 받아 로직 생성
    • 일급 객체
      고차 함수를 사용하려면 해당 언어가 일급 객체의 특징을 가져야 함
      1. 변수나 메서드에 함수를 할당할 수 있다.
      2. 함수 안에 함수를 매개변수로 담을 수 있다.
      3. 함수가 함수를 반환할 수 있다.

2. 객체지향 프로그래밍

  • 특징

    • 객체들의 집합으로 프로그래밍의 상호 작용 표현
    • 데이터를 객체 취급하여 객체 내부에 선언된 메서드 활용
    • 추상화 | 복잡한 시스템에서 핵심적인 개념이나 기능 간추림
    • 캡슐화 | 객체의 속성과 메서드를 하나로 묶고 일부를 외부에 감춤
    • 상속성 | 상위 클래스 특성을 하위 클래스가 이어받아서 재사용, 추가, 확장
    • 다형성 | 하나의 메서드나 클래스가 다양한 방법으로 동작 (오버로딩, 오버라이딩)
      • 오버로딩 (컴파일_정적 다형성) : 같은 이름을 가진 메서드를 여러 개 두는 것
      • 오버라이딩 (런타임_동적 다형성) : 상위 클래스로부터 상속받은 메서드를 하위 클래스가 재정의
  • SOLID 설계 원칙

    • S(단일 책임 원칙) : 모든 클래스는 각각 하나의 책임만 가져야 하는 원칙
    • O(개방-폐쇄 원칙) : 유지보수 사항 있을 땐 코드를 쉽게 확장해야 하고, 수정할 땐 닫혀있어야 함 (기존 코드는 잘 변경하지 X, 확장은 쉬워야 한다)
    • L(리스코프 치환 원칙) : 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 함 (클래스의 부모 객체에 자식 객체를 넣어도 시스템에 문제없어야 함)
    • I(인터페이스 분리 원칙) : 하나의 일반적인 인터페이스보다 구체적인 여러 인터페이스를 만들어야 함
    • D(의존 역전 원칙) : 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 의존성을 없애는 원칙

3. 절차형 프로그래밍

  • 특징
    • 연속적인 로직 계산 과정으로 이루어져 있어서 코드 가독성 굿, 실행 속도도 빠름 (→ 계산 많은 작업에 쓰임)
    • 모듈화 하기 어렵고 유지 보수성이 떨어짐

2개의 댓글

comment-user-thumbnail
2024년 11월 25일

진짜 열심히 작성하셨네요!
언제나 응원하고 있습니다!

1개의 답글