cs/디자인 패턴과 프로그래밍 패러다임

디자인 패턴

leeeyez 2025. 4. 3. 14:33

디자인 패턴

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

 

 

싱글톤 패턴

: 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴
보통 db 연결 모듈에 많이 사용

하나의 인스턴스를 다른 모듈이 공유하며 사용

  • 인스턴스 생성 비용 ↓
  • 의존성 ↑

 

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

리터럴 {} 또는 new Object로 객체 생성 => 다른 어떤 객체와도 같지 않은 고유한 객체
-> 이 자체만으로도 싱글톤 패턴 구현 가능

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

실제 싱글톤 패턴은 보통 위와 같이 구성

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 생성
  • 데이터베이스 연결에 관한 인스턴스 생성 비용을 아낄 수 있다

 

자바에서의 싱글톤 패턴

중첩 클래스를 이용해서 만드는 방법이 대중적

 

mongoose에서의 싱글톤 패턴

Node.js에서 MongoDB 데이터베이스를 연결할 때 쓰임

mongoose의 데이터베이스를 연결할 때 쓰는 connect()라는 함수 = 싱글톤 인스턴스 반환

Mongoose.prototype.connect = function(uri, options, callback) {
	const _mongoose = this instanceof Mongoose ? this : mongoose;
    const conn = _mongoose.connection;
    
    return _mongoose._promiseOrCallback(callback, cb => {
    	conn.openUri(uri, options, err => {
        	if (err != null) {
            	return cb(err);
            }
            return cb(null, _mongoose);
        });
    });
});

 

MySQL의 싱글톤 패턴

Node.js에서 MySQL 데이터베이스 연결 시 사용

// 메인모듈 
const mysql = require('mysql');
const pool = mysql. createPool({
	connectionlimit: 10, 
    host: 'example.org', 
    user: 'kundol', 
    password: 'secret', 
    database:' 디비'
});
pool. connect();

// 모듈A
pool. query (query, function (error, results, fields) {
	if (error) throw error;
	console.log('The solution is: ', results[0].solution);
});

// 모듈B
pool.query (query, function (error, results, fields) {
	if (error) throw error;
	console.log('The solution is: ', results[0].solution);
});
  • 메인 모듈에서 db 연결에 관한 인스턴스를 정의
  • 다른 모듈인 A 또는 B에서 해당 인스턴스를 기반으로 쿼리를 보내는 형식

 

싱글톤 패턴의 단점

TDD(Test Driven Development)할 때 걸림돌

  • 테스트가 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 하는 단위 테스트 위주
  • 싱글톤 패턴 : 미리 생성된 하나의 인스턴스 기반 구현 패턴
    => 테스트마다 독립적인 인스턴스 만들기 어려움

의존성 주입

모듈 간의 결합을 강하게 만들 수 있음

=> 의존성 주입(DI, Dependency Injection)을 통해 모듈 간의 결합을 느슨하게 만들어 해결 가능

 

의존성 = 종속성
A가 B에 의존성이 있다
= B의 변경 사항에 대해 A 또한 변해야 된다

 

의존성 주입 전

: 메인 모듈이 직접 다른 모듈에 대한 의존성을 주입

 

의존성 주입 후

: 중간에 의존성 주입자가 이 부분을 가로채 메인 모듈이 간접적으로 의존성을 주입

 

이를 통해 메인 모듈(상위 모듈)은 하위 모듈에 대한 의존성 ↓ = 디커플링 된다

 

의존성 주입의 장점

  • 모듈을 쉽게 교체할 수 있는 구조가 된다 -> 테스팅, 마이그레이션이 수월하다
  • 추상화 레이어를 넣고 이를 기반으로 구현체 넣어줌
    • 애플리케이션 의존성 방향 일관됨
    • 애플리케이션을 쉽게 추론 가능
    • 모듈 간의 관계들이 명확해진다

 

의존성 주입의 장점

  • 모듈들이 더욱더 분리
    • 클래스 수 ↑
    • 복잡성 ↑
    • 약간의 런타임 페널티 생김

 

의존성 주입 원칙

  1. 상위 모듈은 하위 모듈에서 어떤 것도 가져오지 않는다.
  2. 두 모듈 모두 추상화에 의존해야 한다.
  3. 추상화는 세부 사항에 의존하지 말아야 한다.

 

 

팩토리 패턴

객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴

상속 관계에 있는 두 클래스에서 상위 클래스중요한 뼈대를 결정

-> 하위 클래스에서 객체 생성에 관한 구체적 내용 결정

 

  • 상위 클래스와 하위 클래스 분리 -> 느슨한 결합
  • 상위 클래스: 인스턴스 생성 방식에 대해 알 필요 X -> 유연성 ↑
  • 객체 생성 로직이 따로 분리 -> 유지 보수성 ↑

 

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

new Object()로 구현 가능 : 숫자/문자열 전달 -> 서로 다른 타입의 객체 생성
즉, 전달받은 값에 따라 다른 객체 생성

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()
  • CoffeeFactory라는 상위 클래스가 중요한 뼈대 결정 -> 하위 클래스인 LatteFactory가 구체적 내용 결정
  • CoffeeFactory에서 LatteFactory의 인스턴스를 생성하는 것이 아닌
    LatteFactory에서 생성한 인스턴스를 CoffeeFactory에 주입하고 있기 때문 (하위 -> 상위)
  • CoffeeFactory 클래스 : static 키워드를 통해 createCoffee() 메서드를 정적 메서드로 선언
    • 클래스를 기반으로 객체를 만들지 않고 호출 가능
    • 해당 메서드에 대한 메모리 할당 한 번만 할 수 있음

 

전략 패턴

전략 패턴 (strategy pattern) = 정책 패턴 (policy pattern)

객체의 행위를 바꾸고 싶은 경우 직접 수정 X

전략이라고 부르는 '캡슐화한 알고리즘'을 컨텍스트 안에서 바꿔주면서 상호 교체 가능하도록

결제할 때 네이버페이, 카카오페이 등 다양한 방법으로 결제하듯이
어떤 아이템을 살 때 LUNACard로 사는 것과 KAKAOCard로 사는 것을 구현한 예제

 

passport의 전략 패턴

passport : Node.js에서 인증 모듈을 구현할 때 쓰는 미들웨어 라이브러리

=> 여러가지 전략을 기반으로 인증 가능

  • LocalStrategy 전략: 서비스 내의 회원가입된 아이디와 비밀번호를 기반으로 인증
  • OAuth 전략: 페이스북, 네이버 등 다른 서비스를 기반으로 인증
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 (luser.validPassword(password)) {
                	return done(nul, false, { message: 'Incorrect password.'
});
				}
				return done(null, user);
			});
       }
));

passport.use(new LocalStrategy(...

-> passport.use()라는 메서드에 '전략'을 매개변수로 넣어서 로직 수행

 

옵저버 패턴

옵저버 패턴(observer pattern)
: 주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드를 통해 옵저버들에게 변화 알려주는 패턴

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

*주체와 객체를 따로 두지 않고 상태가 변경되는 객체 기반으로 구축되기도 함

 

옵저버 패턴을 활용한 서비스 : 트위터

팔로우한 '주체'가 포스팅을 올리면 알림이 팔로워들에게 감

 

  • 옵저버 패턴을 주로 이벤트 기반 시스템에 사용
  • MVC(Model-View-Controller) 패턴에도 사용
    • 1) 주체모델(Model)에서 변경 사항 생김
    • 2) update() 메서드로 옵저버에게 이를 알림
    • 3) 이를 기반으로 컨트롤러(controller) 등이 작동
자바의 상속과 구현
상속 (extends)
자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용
자식 클래스에서 추가 및 확장을 할 수 있는 것
-> 재사용성, 중복성 최소화

구현 (implements)
부모 인터페이스를 자식 클래스에서 재정의하여 구현하는 것
상속과 달리 반드시 부모 클래스의 메서드를 재정의하여 구현

상속과 구현의 차이
상속: 일반 클래스, abstract 클래스를 기반으로 구현
구현: 인터페이스를 기반으로 구현

 

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

-> 프록시 객체를 통해 구현

프록시 객체
: 어떠한 대상의 기본적인 동작의 작업을 가로챌 수 있는 객체
js에서 프록시 객체는 두 개의 매개변수를 가짐
- target: 프록시할 대상
- handler: target 동작을 가로채고 어떠한 동작을 할 것인지가 설정되어 있는 함수

 

const handle = {
	get: function(target, name) {
    	return name === 'name' ? `${target.a} ${target.b}` : target[name]
    }
}
const p = new Proxy({a: 'KUNDOL', b: 'IS AUMUMU ZANGIN'}, handle)
console.log(p.name) // KUNDOL IS AUMUMU ZANGIN
  • new Proxy(): a와 b의 속성을 갖고 있는 객체와 handler 함수를 매개변수로 넣고 p라는 변수 선언
  • p의 name 속성을 참조
    : a와 b라는 속성밖에 없는 객체가 handler의 "name이라는 속성에 접근할 때 a와 b를 합쳐서 문자열을 만들라"는 로직에 따라 어떤 문자열 만듦
  • 프록시 객체 : name 속성 등 특정 속성에 접근할 때 그 부분을 가로채서 어떠한 로직을 강제할 수 있는 것

 

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

function createReactivelbject(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
}
const a = {
	" 형규" : "솔로"
}
const b = createReactivelbject(a, console.log)
b. 형규= " 솔로"
b. 형 규= " 커플"
// 형규가 [솔로] >> [커플]로 변경되었습니다.
  • 프록시 객체의 get() 함수 : 속성과 함수에 대한 접근을 가로챔
  • has() 함수 : in 연산자의 사용을 가로챔
  • set() 함수 : 속성에 대한 접근을 가로챔

Vue.js 3.0의 옵저버 패턴

Vue.js 3.0에서 ref나 reactive로 정의하면 해당 값이 변경되었을 때 자동으로 DOM에 있는 값이 변경

-> 프록시 객체를 이용한 옵저버 패턴 이용하여 구현한 것

 

 

 

프록시 패턴과 프록시 서버

프록시 패턴

: 대상 객체에 접근하기 전 그 접근에 대한 흐름을 가로채 해당 접근을 필터링하거나 수정하는 역할을 하는 계층이 있는 패턴

-> 이를 통해 객체의 속성, 변환 등을 보완하며 보안, 데이터 검증, 캐싱, 로깅에 사용

프록시 서버에서의 캐싱
캐시 안에 정보를 담아두고, 캐시 안에 있는 정보를 요구하는 요청에 대해 다시 저 멀리 있는 원격 서버에 요청하지 않고
캐시 안에 있는 데이터를 활용
-> 불필요하게 외부와 연결 X -> 트래픽 줄일 수 있음

 

프록시 서버

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

 

프록시 서버로 쓰는 nginx

nginx: 비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹 서버

주로 Node.js 서버 앞단의 프록시 서버로 활용

 

Node.js 창시자 라이언 달
"Node.js의 버퍼 오버플로우 취약점을 예방하기 위해서는
nginx를 프록시 서버 앞단에 놓고
Node.js를 뒤쪽에 놓는 것이 좋다"

-> 익명 사용자가 직접적으로 서버에 접근하는 것을 차단
-> 간접적으로 한 단계를 더 거치게 만들어 보안 강화 가능

 

실제 포트를 숨길 수 있고 정적 자원을 gzip 압축하거나, 메인 서버 앞단에서의 로깅 가능

버퍼 오버플로우
버퍼는 보통 데이터가 저장되는 메모리 공간이며, 버퍼 오버플로우는 메모리 공간을 벗어나는 경우를 말함
사용되지 않아야 할 영역에 데이터가 덮어씌워져 주소, 값을 바꾸는 공격이 발생하기도 함
gzip 압축
-> 데이터 전송량을 줄일 수 있음
-> 압축을 해제했을 때 서버에서의 CPU 오버헤드 고려 필요

 

프록시 서버로 쓰는 CloudFlare

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

 

웹 서버 앞단에 프록시 서버로 두어 DDOS 공격 방어나 HTTPS 구축에 쓰임

서비스 배포 이후 해외에서 의심스러운 트래픽이 많이 발생 -> 클라우드 서비스 비용 ↑

=> 이때 CloudFlare가 의심스러운 트래픽인지를 먼저 판단해 CAPTCHA 등을 기반으로 일정 부분 막아주는 역할 수행

DDOS 공격 방어

DDOS: 짧은 기간 동안 네트워크에 많은 요청을 보내 네트워크 마비시켜 웹 사이트의 가용성을 방해하는 사이버 공격

  • CloudFlare는 의심스러운 트래픽을 자동으로 차단
  • CloudFlare의 거대한 네트워크 용량과 캐싱 전략으로 소규모 DDOS 공격은 쉽게 막을 수 있음
  • 공격에 대한 방화벽 대시보드도 제공

HTTPS 구축

서버에서 HTTPS 구축 시 인증서 기반으로 구축 가능 -> CloudFlare는 인증서 없이도 가능

 

 

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

CORS(Cross-Origin Resource Sharing)

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

 

예를 들어 프론트와 백엔드의 포트 번호가 다를 때 CORS 에러 발생

이때 프록시 서버를 통해 프론트 서버에서 요청되는 오리진을 백 포트로 바꾸는 것!

 

 

이터레이터 패턴

: 이터레이터(iterator)를 사용하여 컬렉션(collection)의 요소들에 접근하는 디자인 패턴

-> 순회할 수 있는 여러 가지 자료형의 구조와 상관없이 이터레이터라는 하나의 인터페이스로 순회 가능

이터레이터라는 똑같은 배로, 동그라미로 이루어진 컬렉션이든 마름모로 이루어진 컬렉션이든 순회 가능

 

자바스크립트에서의 이터레이터 패턴

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라는 이터레이터 프로토콜을 통해 순회 가능

 

노출모듈 패턴

: 즉시 실행 함수를 통해 private, public 같은 접근 제어자를 만드는 패턴

  • 자바스크립트
    : 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는 다른 모듈에서 사용할 수 없는 변수나 함수 => private 범위
  • c와 d는 다른 모듈에서 사용할 수 있는 변수나 함수 => public 범위
  • 노출모듈 패턴을 기반으로 만든 js 모듈 방식 : CJS(CommonJS) 모듈 방식
public
: 클래스에 정의된 함수에서 접근 O + 자식 클래스/외부 클래스 접근 O
protected
: 클래스에 정의된 함수에서 접근 O + 자식 클래스 접근 O + 외부 클래스 접근 X
private
: 클래스에 정의된 함수에서 접근 O + 자식 클래스/외부 클래스 접근 X
즉시 실행 함수
: 함수를 정의하자마자 바로 호출하는 함수
초기화 코드, 라이브러리 내 전역 변수의 충돌 방지 등에 사용

 

 

MVC 패턴

: Model, View, Controller로 이루어진 디자인 패턴

  • 재사용성과 확장성 용이
  • 애플리케이션이 복잡해질수록 모데로가 뷰의 관계가 복잡해짐

모델

: 애플리케이션의 데이터인 db, 상수, 변수 등을 뜻함

뷰에서 데이터를 생성하거나 수정하면 컨트롤러를 통해 모델 생성하거나 갱신

: inputbox, checkbox, textarea 등 사용자 인터페이스 요소 = 모델을 기반으로 사용자가 볼 수 있는 화면

변경이 일어나면 컨트롤러에 이를 전달

컨트롤러

: 하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할, 이벤트 등 메인 로직을 담당

모델과 뷰의 생명주기 관리, 모델이나 뷰의 변경 통지를 받으면 이를 해석하여 각각의 구성 요소에 해당 내용 알려줌

 

MVC 패턴의 예 스프링

스프링의 WEB MVC

  • @RequestParam, @RequestHeader, @PachVariable 등의 에너테이션을 기반으로 사용자의 요청 값 쉽게 분석 가능
  • 사용자의 어떠한 요청이 유효한 요청인지를 쉽게 거를 수 있음
  • 재사용 가능한 코드, 테스트, 쉽게 리디렉션할 수 있게 하는 등의 장점 존재

 

MVP 패턴

MVC로 파생되었으며 C에 해당하는 컨트롤러가 프리젠터(presenter)로 교체된 패턴

  • 뷰 - 프레젠터 = 1:1 관계
  • MVC 패턴보다 더 강한 결합을 지닌 디자인 패턴

 

MVVM 패턴

C에 해당하는 컨트롤러가 뷰모델(view model)로 바뀐 패턴

뷰모델 = 뷰를 더 추상화한 계층

  • MVVM 패턴은 MVC 패턴과는 다르게 커맨드데이터 바인딩을 가짐
  • 뷰와 뷰모델 사이의 양방향 데이터 바인딩 지원
  • UI를 별도의 코드 수정 없이 재사용할 수 있음
  • 단위 테스팅이 쉽다

 

MVVM 패턴의 예: 뷰

MVVM 패턴을 가진 대표적인 프레임워크: 뷰(Vue.js)

  • watch, computed 등으로 쉽게 반응형적인 값들을 구축 가능
  • 함수를 사용하지 않고 값 대입만으로도 변수 변경
  • 양방향 바인딩, html을 토대로 컴포넌트 구축 가능

 

'cs > 디자인 패턴과 프로그래밍 패러다임' 카테고리의 다른 글

프로그래밍 패러다임  (0) 2025.04.03