디자인 패턴
프로그램을 설계할 때 발생했던 문제점들 -> 객체 간의 상호관계 등을 이용하여 해결할 수 있도록 하나의 '규약' 형태로 만들어놓은것
싱글톤 패턴
: 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴
보통 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 또한 변해야 된다
의존성 주입 전
: 메인 모듈이 직접 다른 모듈에 대한 의존성을 주입
의존성 주입 후
: 중간에 의존성 주입자가 이 부분을 가로채 메인 모듈이 간접적으로 의존성을 주입
이를 통해 메인 모듈(상위 모듈)은 하위 모듈에 대한 의존성 ↓ = 디커플링 된다
의존성 주입의 장점
- 모듈을 쉽게 교체할 수 있는 구조가 된다 -> 테스팅, 마이그레이션이 수월하다
- 추상화 레이어를 넣고 이를 기반으로 구현체 넣어줌
- 애플리케이션 의존성 방향 일관됨
- 애플리케이션을 쉽게 추론 가능
- 모듈 간의 관계들이 명확해진다
의존성 주입의 장점
- 모듈들이 더욱더 분리
- 클래스 수 ↑
- 복잡성 ↑
- 약간의 런타임 페널티 생김
의존성 주입 원칙
- 상위 모듈은 하위 모듈에서 어떤 것도 가져오지 않는다.
- 두 모듈 모두 추상화에 의존해야 한다.
- 추상화는 세부 사항에 의존하지 말아야 한다.
팩토리 패턴
객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴
상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정
-> 하위 클래스에서 객체 생성에 관한 구체적 내용 결정
- 상위 클래스와 하위 클래스 분리 -> 느슨한 결합
- 상위 클래스: 인스턴스 생성 방식에 대해 알 필요 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 압축
-> 데이터 전송량을 줄일 수 있음
-> 압축을 해제했을 때 서버에서의 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 |
---|