1. 싱글톤 패턴(singleton pattern)

기술노트

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

1. 싱글톤 패턴(singleton pattern)

싱글톤 패턴이 뭐야?

싱글톤 패턴이란 하나의 클래스에 오직 하나의 인스턴스만을 가지는 패턴이며 보통 데이터베이스 연결 모듈에 많이 쓰인다.

  • 장점
 하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈이 공유하며 사용하기에 인스턴스 생성 비용이 적다.
  • 단점
 의존성이 높다

자바스크립트의 싱글톤 패턴

자바스크립트에서는 리터럴 `{}` 또는 `new Object` 로 객체를 생성하게 되면 독립된 객체이기 때문에 그 자체만으로 싱글톤 패턴이 구현된다.

const obj = {
  a: 27
}
const obj2 = {
  a: 27
}
console.log(obj === obj2)
// false

`new Object` 라는 클래스에서 나온 단 하나의 인스턴스니 어느 정도 싱글톤 패턴이라 보지만 실제 싱글톤 패턴은 보통 다음과 같은 코드로 구성된다.

class Singleton{
  constructor(){
    if(!Singleton.instance){
      Singleton.instance = this
    }
    return Singleton.instance
  }
  getInstacne(){
    return this.instance
  }
}
const a = new Singleton()
const b = new Singleton()
console.log(a === b) // true

`Singleton.instance` 라는 하나의 인스턴스를 가지는 `Singleton` 클래스를 구현했다. 이를 통해 `a` 와 `b` 는 하나의 인스턴스를 가진다. (공유)

데이터베이스 연결 모듈

싱글톤 패턴은 데이터베이스 연결 모듈에 많이 쓰인다.

const URL = "mongodb://localhost:27017/kundolapp"
const createConnection = url => ({"url": url}
class DB{
  constructor(url){
    if(!DB.instance){
      DB.instance = createConnection(url)
    }
    return DB.instance
  }
  connect(){
    return this.instance
  }
}
const a = new DB(URL)
const b = new DB(URL)
console.log(a === b) // true

`DB.instance` 하나의 인스턴스를 기반으로 `a` 와 `b` 를 생성해 데이터베이스 연결에 관한 인스턴스 생성 비용을 줄일 수 있다.

싱글톤 패턴의 단점

싱글톤 패턴은 TDD를 할때 걸림돌이 된다. TDD를 할 때 단위 테스트를 주로 하는데, 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 한다. 하지만 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트마다 독립적인 인스턴스를 만들기 어렵다.

의존성 주입

모듈 간 결합을 강하게 만들 수 있다는 단점이 있다. 이 때 의존성 주입을 통해 결합을 느슨하게 만들어 해결 가능하다. `(디커플링이 된다)`

> 의존성이란 종속성이라고도 하며 A가 B에 의존성이 있다는 것은 B에 변경 사항에 대해 A 또한 변해야 된다는 것이다.

의존성 주입의 장점

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

의존성 주입의 단점

모듈들이 더욱더 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며 약간의 런타임 패널티가 생기기도 한다.

의존성 주입 원칙

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

2. 팩토리 패턴(factory pattern)

팩토리 패턴이 뭐야?

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

장점

  • 상 하위 클래스가 분리되기에 느슨한 결합을 가지며 상위 클래스에서는 인스턴스 생성을 신경 쓰지 않아 더 많은 유연성을 갖게 된다.
  • 객체 생성 로직이 따로 있기에 코드를 리 팩터링 하더라도 한곳만 고칠 수 있게 되니 유지 보수성이 증가한다.

예시

라떼 레시피와 아메리카노 레시피, 우유 레시피라는 구체적인 내용이 들어 있는 하위 클래스가 컨베이어 벨트를 통해 전달되고, 상위 클래스인 바리스타 공장에서 이 레시피들을 토대로 우유 등을 생산하는 생산 공정을 생각하면 된다.

자바스크립트의 팩토리 패턴

자바스크립트에서는 `new Object()` 로 구현할 수 있다.

const num = new Object(42)
const str = new Object("abc")
num.constructor.name; // Number
str.constructor.name; // String

숫자를 전달하거나 문자열을 전달함에 따라 다른 타입의 객체를 생성한다. 즉 전달받은 값에 따라 다른 객체를 생성해 인스턴스의 타입 등을 정한다.

커피 팩토리 기반 코드

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

class LatteFactory{
  static createCoffee(){
    return new Latte()
  }
}
class EspressoFactory{
  static createCoffee(){
    return new Espresso()
  }
}
const factoryList = {LatteFactory, LatteFactory}

class CoffeeFactory{
  static createCoffee(type){
    const factory = factoryList[type]
    return factory.createCoffee()
  }
}

const main = () => {
  // 라떼 커피를 주문한다.
  const coffee = CoffeeCactory.createCoffee("LatteFactory")
  // 커피 이름을 부른다.
  console.log(coffee.name) // latte
}
main()

`CoffeeFactory` 라는 상위 클래스가 중요한 뼈대를 결정하고 하위 클래스인 `LatteFactory` 가 구체적인 내용을 결정하고 있다. 이는 의존성 주입이라고 볼 수 있다. `CoffeeFactory` 에서 `LatteFactory` 의 인스턴스를 생성하는 것이 아닌 `LatteFactory` 에서 생성한 인스턴스를 `CoffeeFactory` 에 주입하고 있기 때문이다.

또한 `CoffeeFactory` 를 보면 `static` 으로 `createCoffe()` 정적 메서드를 정의했는데 정적 메서드를 쓰면 클래스의 인스턴스 없이 호출이 가능하며 메모리를 절약할 수 있고 개별 인스턴스에 묶이지 않으며 클래스 내의 함수를 정의할 수 있는 장점이 있다.

3. 전략 패턴(strategy pattern)

전략 패턴이 뭐야?

전략 패턴은 정책 패턴이라고도 하며, 객체의 행위를 수정시 직접 수정하지 않고 전략이라고 부르는 캡슐화한 알고리즘을 컨텍스트안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴이다.

> 컨텍스트 > 프로그래밍에서의 컨텍스트는 상황, 맥락, 문맥을 의미. 개발자가 어떠한 작업을 완료하는 데 필요한 모든 관련 정보를 말한다.

자바의 전략 패턴

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
interface PaymentStrategy {
    public void pay(int amount);
}

class KAKAOCardStrategy implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;
    private String dateOfExpiry;

    public KAKAOCardStrategy(String nm, String ccNum, String cvv, String expiryDate){
        this.name=nm;
        this.cardNumber=ccNum;
        this.cvv=cvv;
        this.dateOfExpiry=expiryDate;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount +" paid using KAKAOCard.");
    }
}

class LUNACardStrategy implements PaymentStrategy {
    private String emailId;
    private String password;

    public LUNACardStrategy(String email, String pwd){
        this.emailId=email;
        this.password=pwd;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using LUNACard.");
    }
}

class Item {
    private String name;
    private int price;
    public Item(String name, int cost){
        this.name=name;
        this.price=cost;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}

class ShoppingCart {
    List<Item> items;

    public ShoppingCart(){
        this.items=new ArrayList<Item>();
    }

    public void addItem(Item item){
        this.items.add(item);
    }

    public void removeItem(Item item){
        this.items.remove(item);
    }

    public int calculateTotal(){
        int sum = 0;
        for(Item item : items){
            sum += item.getPrice();
        }
        return sum;
    }

    public void pay(PaymentStrategy paymentMethod){
        int amount = calculateTotal();
        paymentMethod.pay(amount);
    }
}

public class HelloWorld{
    public static void main(String []args){
        ShoppingCart cart = new ShoppingCart();

        Item A = new Item("kundolA",100);
        Item B = new Item("kundolB",300);

        cart.addItem(A);
        cart.addItem(B);

        // pay by LUNACard
        cart.pay(new LUNACardStrategy("kundol@example.com", "pukubababo"));
        // pay by KAKAOBank
        cart.pay(new KAKAOCardStrategy("Ju hongchul", "123456789", "123", "12/01"));
    }
}
/*
400 paid using LUNACard.
400 paid using KAKAOCard.
*/

이 코드는 쇼핑 카트에 아이템을 담아 LUNACard 또는 KAKAOCard 라는 두 개의 전략으로 결제하는 코드이다.

passprot의 전략 패턴

전략 패턴을 활용한 라이브러리로는 passport가 있다. Node.js에서 인증 모듈을 구현할 때 쓰는 미들웨어 라이브러리이며 여러가지 전력을 기반으로 인증할 수 있게 한다.

자바스크립트 코드

var passport = require("passport"),
  LocalStrategy = require("passport-local").Strategy;

passport.use(
  new LocalStrategy(function (username, password, done) {
    User.findOne({ username: username }, function (err, user) {
      if (err) {
        return done(err);
      }
      if (!user) {
        return done(null, false, { message: "Incorrect username." });
      }
      if (!user.validPassword(password)) {
        return done(null, false, { message: "Incorrect password." });
      }
      return done(null, user);
    });
  })
);

passport.use(new LocalStrategy( ... 처럼 passport.use()라는 메서드에 전략을 매개변수로 넣어서 로직을 수행하는 것을 볼 수 있다.

4. 옵저버 패턴(observer pattern)

옵저버 패턴이 뭐야?

옵저버 패턴은 객체의 상태를 보고있는 관찰자가(주체) 객체의 상태 변화가 있을 때 마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴이다. 혹은 주체와 객체를 따로 두지 않고 상태가 변경되는 객체를 기반으로 구축하기도 한다.

옵저버 패턴은 주로 이벤트 기반 시스템에 사용하며 MVC 패턴에도 사용된다. 주체라고 볼 수 있는 model에서 변경사항이 생겨 update() 메서드로 옵저버인 뷰에게 알려주고 이를 기반으로 controller 등이 작동하는 것이다.

트위터의 팔로우한 사람이 글을 올리면 알림이 팔로워에게 가는게 사용 예이다.

자바스크립트의 옵저버 패턴

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

> 프록시 객체
> 프록시 객체는 기본적인 동작(속성 접근, 할당, 순회, 열거, 함수 호출 등)의 작업을 가로챌 수 있는 객체이다.

자바스크립트 프록시 개체 매개변수

1. target : 프록시할 대상 2. handler : 프록시 객체의 target 동작을 가로채서 정의할 동작들이 정해져 있는 함수

프록시 객체 코드

``` const handler = {

 get: function(target, name){
   return name === "name" ? `${taget.a} ${target.b}` : target[name]
 }

} const p = new Proxy({a: "KUNDOL", b: "IS AUMUMU ZANGIN"}, handler) console.log(p.name) // KUNDOL IS AUMUMU ZANGIN

name이라는 속성에 접근할 때는 a, b를 합친 문자열을 만드는 것을 구현한 코드입니다.
p라는 변수에 name이라는 속성을 선언하지 않았는데도 p.name으로 name 속성에 접근할 때 그 부분을 가로채 문자열을 만드는 것을 볼 수 있습니다.

=== 프록시 객체를 이용한 옵저버 패턴 ===

function createReactiveObject(target, callback) {

 const proxy = new Proxy(target, {
   set(obj, prop, value) {
     if (value !== obj[prop]) {
       const prev = obj[prop];
       obj[prop] = value;
       callback(`${prop}가 [${prev}] >> [${value}] 로 변경되었습니다`);
     }
     return true;
   },
 });
 return proxy;

} ```

프록시 함수를 통해 속성 접근을 가로채 형규라는 속성이 커플로 변경되는 것을 감시할 수 있다.

5. 프록시 패턴과 프록시 서버(proxy pattern)

프록시 패턴

짜잔 앞에 설명한 프록시 객체는 사실 디자인 패턴 중 하나인 프록시 패턴이 녹아들어 있다. 프록시 패턴은 대상 객체에 접근하기 전 그 접근에 대한 흐름을 가로채 대상 객체 앞단의 인터페이스 역할을 한다.

주로 객체의 속성, 변환 등을 보안해 보아느 데이터, 검증 캐싱, 로깅에 사용한다.

> 프록시 서버에서의 캐싱
> 캐시에 정보를 담아 두고 캐시 안의 정보를 다시 요청할 때 캐시 데이터를 활용하는 것이다. 이를 통해 불필요하게 외부와 연결하지 않아 트래픽을 줄일 수 있다.

프록시 서버

프록시 서버는 서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해준다.

예1 nginx

nginx는 node.js 서버 앞단의 프록시 서버로 활용된다.

nginx를 통해 익명 사용자의 직접적인 서버로의 접근을 차단하고 간접적으로 한 단계를 더 거침으로써 보안성 강화

예2 CloudFlare

CloudFlare는 전 세계적으로 분산된 서버가 있고 이를 통해 어떠한 시스템의 콘텐츠 전달을 빠르게 할 수 있는 CDN 서비스이다.

CDN 말고도 DDOS공격 방어, HTTPS 구축이 가능하다 그 이유는 웹 서버 앞단에서 프록시 서버로 쓰기 때문이다.

> CDN
> 각 사용자가 인터넷에 접속하는 곳과 가까운 곳에서 콘텐츠를 캐싱 또는 배포하는 서버 네트워크를 말한다. 이를 통해 사용자가 웹 서버로부터 콘텐츠를 다운로드하는 시간을 줄일 수 있다.

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

`CORS` 는 서버가 웹 브라우저에서 리소스를 로드할 때 다른 오리진을 통해 로드하지 못하게 하는 HTTP 헤더 기반 매커니즘이다. 프론트엔드 개발 시 백엔드 서버와 통신할 때 주로 CORS 에러를 마주치는데, 이를 해결하기 위해 프론트엔드에서 프록시 서버를 만들기도 한다.

> 오리진
> 프로토콜과 호스트 이름, 포트의 조합을 말한다. 예를 들어 https://subin:991213/test라는 주소에서 오리진은 https://subin:991213을 뜻한다.

예를 들어 프론트에서 ~:3000으로 테스팅하는데 백엔드 서버는 ~:2000이라면 포트 번호가 다르기에 CORS에러가 나타난다. 이 때 프록시 서버를 둬서 프론트 서버에서 요청되는 오리진을 ~:2000으로 바꿔 해결한다. 이를 사용하면 여러 서버와의 통신도 매끄럽게 가능하다.

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(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라는 이터레이터 프로토콜을 통해 순회하는 것을 볼 수 있다.

> [자세히 알아보기](https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Iterators_and_Generators)

7. 노출모듈 패턴

노출모듈 패턴이 뭐야?

노출모듈 패턴은 즉시 실행 함수를 통해 private, public 같은 접근 제어자를 만드는 패턴이다. 자바스크립트는 이 접근 제어자가 없어 전역 범위에서 스크립트가 실행되는데 그렇기에 노출패턴을 통해 접근 제어자를 구현하기도 한다.

자바스크립트 코드

const pukuba = (() => {
    const a = 1
    const b = () => 2
    const public = {
        c : 2,
        d : () => 3
    }
    return public
})()
console.log(pukuba)
console.log(pukuba.a)
// { c: 2, d: [Function: d] }
// undefined

a와 b는 다른 모듈에서 사용할 수 없는 변수나 함수이며 priavte 범위를 가진다. c와 b는 다른 모듈에서 사용할 수 있는 변수나 함수이며 public 범위를 가진다.

8. MVC 패턴

MVC 패턴이 뭐야?

MVC 패턴은 모델(Model), 뷰(View), 컨트롤러(Controller) 로 이루어진 디자인 패턴이다. 앱의 구성 요소를 세 가지 역할로 구분해 개발 프로세스에서 각각의 구성 요소에만 집중해서 개발할 수 있다. 재사용성과 확장성이 용이하지만 단점은 앱이 복잡해질수록 모델과 뷰의 관계가 복잡해진다.

> MVC 패턴을 이용한 대표적인 라이브러리로는 리액트가 있다.

모델

모델은 애플리케이션의 데이터인 DB, 상수, 변수 등을 뜻한다. 박스 안에 글자가 들어 있다면 그 박스 위치와 정보, 글자 내용과 위치에 관한 정보를 모두 갖고 있어야 한다. 뷰에서 데이터를 생성하거나 수정하면 컨트롤러를 통해 모델을 생성하거나 갱신한다.

뷰는 inputbox, checkbox 등 사용자 인터페이스 요소를 나타낸다. 즉 모델을 기반으로 사용자가 볼 수 있는 화면이다. 모델이 가지고 있는 정보를 따로 저장하지 않아야 하며 단순히 박스 같은 화면에 표시하는 정보만 갖고 있어야 한다. 변경이 일어나면 컨트롤러에 이를 전달한다.

컨트롤러

컨트롤러는 하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할을 한다. 이벤트 등 메인 로직을 담당하며 모델과 뷰의 생명주기도 관리한다. 모델이나 뷰의 변경 통지를 받으면 이를 해석해 각각의 구성 요소에 해당 내용에 대해 알려준다.

9. MVP 패턴

MVP 패턴이 뭐야?

MVC 패턴으로부터 파생되었고 C에 해당하는 컨트롤러가 P인 프레젠터(presenter)로 교체된 패턴이다.

  • MVC는 모델 뷰 서로 연결되어 있어 의존관계를 갖게 되고 (차이점)
  • 모델과 뷰가 분리되어 있고 오직 프레젠터를 통해 상태나 변화를 알려줄 수 있다.

뷰와 프레젠터는 일대일 관계이기에 MVC 패턴보다 더 강한 결합을 지녔다고 볼 수 있다.

10. MVVM 패턴

MVVM 패턴이 뭐야?

MVVM 패턴은 MVC의 C에 해당하는 컨트롤러가 뷰모델(view model)로 바뀐 패턴이다.

> MVVM 패턴을 이용한 대표적인 프레임워크로는 뷰가 있다.

뷰모델

여기서 뷰모델은 뷰를 더 추상화한 계층이며 MVVM 패턴은 MVC패턴과 다르게 커맨드와 데이터 바인딩을 가지는 것이 특징이다.

MVVM 장점

뷰와 뷰모델 사이의 양방향 데이터 바인딩을 지원하며 UI를 별도의 코드 수정 없이 재사용할 수 있고 단위 테스팅하기 쉽다는 장점이 있다.

> 커맨드
> 여러 가지 요소에 대한 처리를 하나의 액션으로 처리할 수 있게 하는 기법이다. > > 데이터 바인딩
> 화면에 보이는 데이터와 웹 브라우저의 메모리 데이터를 일치시키는 기법으로, 뷰모델을 변경하면 뷰가 변경된다.