1. 팩토리 패턴(Factory Pattern)
객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴이다.
상위 클래스와 하위 클래스가 분리되기 때문에 느슨한 결합을 가지며 상위 클래스에서는 인스턴스 생성 박식에 대해 전혀 알 필요가 없기 때문에 더 많은 유연성을 갖게 됩니다. 그리고 객체 생성 로직이 따로 떼어져 있기 때문에 코드를 리펙토링하더라도 한 곳만 고칠 수 있게 되어 유지 보수성이 증가된다.
예를 들면, 라떼 레시피와 아메리카노 레시피, 우유 레시피라는 구체적인 내용이 들어 있는 하위 클래스가 컨베이어 벨트를 통해 전달되고, 상위 클래스인 바리스타 공장에서 이 레시피들을 토대로 우유 등을 생산하는 생산 공정이라고 생각하면 된다.
JS에서 팩토리 패턴을 구현한다면 간단하게 new Object()로 구현할 수 있다.
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 EspreessoFactory extends coffeeFactory{
static createCoffee() {
return new Espresso()
}
}
const factoryList = { LatteeFactory, EspressoFactory }
const main = () => {
// 라떼 커피를 주문한다.
const coffee = CoffeeFactory.createCoffee("LatteFactory")
// 커피 이름을 부른다.
console.log(coffee.name) // latte
}
main()
static 메소드는 클래스의 인스턴스를 생성하지 않고도 직접 호출할 수 있는 메소드. 이러한 메소드는 클래스 레벨에서 작동하기 때문에, 특정 인스턴스의 데이터에 접근할 수 없다. 그 대신 static 메소드는 주로 유틸리티 함수나, 인스턴스 생성과 관련 없이 클래스와 관련된 작업을 수행하는데 사용된다.
2. 전략 패턴(Strategy Pattern)
전략패턴(strategy pattern)은 정책패턴(policy pattern)이라고 하며,
여러 알고리즘을 캡슐화하고, 이들을 상호 교체 가능하게 만들어서 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변경하게 할 수 있는 디자인 패턴이다. 이 패턴은 특히 알고리즘의 다양한 변형, 알고리즘의 변경이 자주 필요할 때 유용하다.
전략패턴은 세 부분으로 구성이 된다.
1. 컨텍스트(Context) : 클라이언트가 사용할 인터페이스를 제공하며, 전략 객체를 참조한다. 컨텍스트는 필요에 따라 전략을 변경할 수 있으며, 실제 작업을 수행할 때 이 전략 객체를 사용한다.
2. 전략(Stratgy) : 동일한 문제를 해결하는 다양한 알고리즘(또는 전략)을 정의하는 인터페이스이다.
3. 구체적인 전략(Concrete Strategy) : 전략 인터페이스를 구현하는 클래스로, 구체적인 알고리즘을 제공한다.
// 전략 인터페이스
class BarkBehavior {
bark() {}
}
// 구체적인 전략 1
class LoudBark extends BarkBehavior {
bark() {
console.log("Loud Bark!");
}
}
// 구체적인 전략 2
class SoftBark extends BarkBehavior {
bark() {
console.log("Soft Bark!");
}
}
// 컨텍스트
class Dog {
constructor(barkBehavior) {
this.barkBehavior = barkBehavior;
}
performBark() {
this.barkBehavior.bark();
}
setBarkBehavior(newBarkBehavior) {
this.barkBehavior = newBarkBehavior;
}
}
// 사용 예
let dog = new Dog(new LoudBark());
dog.performBark(); // 출력: Loud Bark!
dog.setBarkBehavior(new SoftBark());
dog.performBark(); // 출력: Soft Bark!
이 코드에서 Dog 클래스는 컨텍스트로, 강아지의 짖는 행동을 결정하는 전략 객체를 참조한다. BarkBehavior는 전략 인터페이스이며, LoudBark와 SoftBark는 이 인터페이스를 구현하는 구체적인 전략이다. 이러한 전략 패턴을 사용하면, 강아지의 짖는 행동을 쉽게 변경할 수 있다.
2-1. Passprot 라이브러리와 전략 패턴
전략 패턴을 활용한 라이브러리로 Passport가 있다. passport는 Node.js에서 인증 모듈을 구현할 때 쓰는 미들웨어 라이브러리로, 여러 가지 전략을 기반으로 인증할 수 있게 한다. 서비스 내의 회원가입된 아이디와 비밀번호를 기반으로 인증하는 여러 인증 방법(LocalStrategy 전략과 페이스북, 네이버, 구글) 등 다른 서비스를 기반으로 인증하는 OAuth 전략 등도 지원한다.
다음 코드처럼 '전략'만 바꿔서 인증할 수 있다.
let 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()) 라는 메서드에 전략을 매개 변수로 넣어서 로직을 수행한다.
3. 옵저버 패턴(Observer Pattern)
옵저버 패턴(observer pattern)은 주체가 어떤 객체(subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴이다.
3-1. 객체와 주체가 분리되어 있는 옵저버 패턴
여기서 주체란 객체의 상태 변화를 보고 있는 관찰자이며, 옵저버들이란 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 추가 변화 사항이 생기는 객체들을 의미한다.
3-2. 객체와 주체가 합쳐진 옵저버 패턴
이 그림은 주체와 객체가 있지만, 따로 두지 않고 상태가 변경되는 객체를 기반으로 구축하기도 있다.
3-3. 옵저버 패턴
옵저버 패턴은 주로 이벤트 기반 시스템에 사용하며 MVC(Model-View-Controller) 패턴에도 사용된다.
예를 들어 주체라고 볼 수 있는 모델(modal)에서 변경 사항이 생겨 update() 메서드로 옵저버인 뷰에 알려주고 이를 기반으로 컨트롤러(controller) 등이 작동하는 것이다.
3-4. 자바스크립트 프록시 객체
자바스크립트의 옵저버 패턴은 프록시 객체를 통해 구현할 수 있다.
프록시 객체
프록시(proxy) 객체는 어떠한 대상의 기본적인 동작(속성 접근, 할당, 순회, 열거, 함수 호출 등)의 작업을 가로챌 수 있는 객체를 뜻하며, 자바스크립트에서 프록시 객체는 두개의 매개변수를 가진다.
target : 프록시할 대상
handler : target 동작을 가로채고 어떠한 동작을 할 것인지가 설정되어 있는 함수
const handler = {
get: function(target, name) {
return name === 'name' ? `${target.a} ${target.b}` : target[name]
}
}
const p = new Proxy({a: 'SHINYU', b: 'IS JONJAL'}, handler)
console.log(p.name) // SHINYU IS JONJAL
이 코드는 프록시 객체를 구현한 코드이다.
new Proxy()로 a와 b 속성을 가지고 있는 객체와 handler 함수를 매개변수로 넣고 p라는 변수를 선언했다. 이후 p의 name속성을 참조하니 a와 b라는 속성밖에 없는 객체가 handler의 "name이라는 속성에 접근할 때 a와 b를 합쳐서 문자열을 만들라"는 로직에 따라 어떤 문자열을 만든다. 이렇게 name 속성 등 특정 속성에 접근할 때 그 부분을 가로채서 어떠한 로직을 강제할 수 있는 것이 프록시 객체이다.
3-5. 프록시 객체를 이용한 옵저버 패턴
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; // 성공적으로 값을 설정했음을 나타냄
},
// has(obj, prop) {
// has 트랩의 구현
// return prop in obj;
// }
});
return proxy;
}
const a = {
"계절": "겨울"
};
const b = createReactiveObject(a, console.log);
b.계절 = "겨울"; // 변경 사항이 없으므로 콜백이 호출되지 않음
b.계절 = "봄"; // 콘솔에 출력: 계절이 [겨울] >> [봄] 으로 바뀌었어요.
프록시 객체의 get() 함수는 속성과 함수에 대한 접근을 가로채며, has() 함수는 in 연산자의 사용을 가로챈다. 현재 이 코드에서는 has() 함수는 구현 되지 않았지만, 어떤 것인지 보여주기 위해 주석처리 되어있다. set() 함수는 속성에 대한 접근을 가로챈다. set() 함수를 통해 속성에 대한 접근을 가로채서 계절이라는 속성이 겨울에서 봄으로 되는 것을 감시할 수 있다.
출처 : https://ssdragon.tistory.com/140
출처 : 면접을 위한 CS 전공지식 노트
'CS' 카테고리의 다른 글
싱글톤 패턴 / 팩토리 패턴 / 전략 패턴 (0) | 2024.06.29 |
---|---|
디자인 패턴 - 프록시 패턴 / Nginx / CloudFlare / CORS (0) | 2024.04.29 |
디자인패턴 - 싱글톤 패턴 (3) | 2024.02.28 |
OSI 7계층 (0) | 2023.11.11 |
링크드리스트(연결 리스트)와 배열 (1) | 2023.10.11 |