2023. 3. 7. 20:59ㆍNode.js
Delegate Pattern
디자인 패턴 중에 하나인 Delegate Pattern은 객체가 기능을 수행할 때, 특정 기능에 대해서 다른 객체에게 위임을 하는 형태입니다. "아 그거 부모 클래스 만들고 자식 클래스 생기게 되면 그냥 기능 수행하는 Method 상속하면 되는거 아니야?"라고 생각할 수 있지만, 부모 클래스 구현에 있어서 변화가 생긴다면 자식 클래스에도 영향을 끼치게 되고 결국에는 잘못된 상속으로 문제를 야기할 수 있겠죠? 그리고 Delegate Pattern을 통해서 반복되는 코드도 정리할 수 있습니다.
패턴 사용 비교해보기
class 박영규 extends 산부인과아들 {
private 자산: number
constructor(비상금:number) {
this.자산 = 비상금
}
task1()<any> {
work and work...
work and work...
work and work...
}
task2()<housework> {
clean and clean...
}
task3()<battle> {
fight and fight...
}
task4()<family> {
love and love...
}
}
delegate 패턴을 사용하지 않은 '박영규' 클래스는 덩어리가 큰 task1을 실행하면서도 서로 다른 성격을 가진 task2, task3, task4의 메소드가 있으면서 다방면으로 역할을 수행해야하는 상황이다. 쉽게 표현해서 간단해 보이지만 만약 실제 코드로 사용한다면 메소드도 많이 복잡해지고 선언된 프로퍼티도 굉장히 많아질 것 이다.
... 박영규 클래스 안이라 가정...
public 영규머니: number
private 월수입: number[]
public 생활비: number
sendDragonMoney(수입: 월수입): void {
this.parent.post(수입.reduce((a,b) => {
this.영규머니 += b * 0.3
this.생활비 += b * 0.5
return a + (b * 0.2)
})
)
}
class 박영규아들 extends 박영규 {
super(1000, 20000, 300)
// 자식 클래스에서 잘못된 프로퍼티 사용?!
sendMoney(this.영규머니) {
this.sendDragonMoney()
}
}
복잡한 구조에서 만약 상속을 받은 자식 클래스에서 잘못된 프로퍼티를 사용해서 메소드를 호출한다면 원치않은 결과를 초래할 수 있다.
그렇다면 '박미선'이라는 delegator를 선언한 다음에 '박영규'클래스에서 사용을 한다면 어떨까? 메인으로 역할을 하는 task 말고는 delegator가 되는 객체에게 전달하여 원하는 값을 받을 수 있을 것 이다.
public interface 박미선 {
taskA() {
clean and clean...
}
taskB() {
battle and battle...
}
taskC() {
love and love...
}
}
// delegator를 사용한 박영규 클래스
class 박영규 extends 산부인과아들 {
private 아내: 박미선
constructor(박미선:Idelegator) {
this.아내 = 박미선
}
task1()<any> {
work and work...
work and work...
work and work...
}
taskOthers()<any> {
this.아내.taskA()
this.아내.taskB()
this.아내.taskC()
}
}
이렇게 '박미선'이라는 delegate 객체에 여러 곳에서 사용될 로직을 담아서 위임을 하게 된다면 유지 보수하기도 훨씬 수월하고 연관성이 없는 서로 다른 class에서 비슷한 로직에 대해서 반복적으로 코드를 작성하지 않고 delegator를 추가하여 사용할 수 있다.
Delegate Pattern 실제 예시
import{Order, OrderMapper } from '../orders/order.ts'
export class payoutService implements IserverAPI {
private orderRepo: IorderRepo
private payoutRepo: IpayoutRepo
private serviceErr: orderServiceError
constructor(orderRepo:IorderRepo, payoutRepo:IpayoutRepo){
this.orderRepo = orderRepo
this.payoutRepo = payoutRepo
}
public async excute(dto:GeneratePayoutDTO): Promise<payout>{
try{
const processed_items = []
// 아이디 값에 해당하는 아이템 데이터를 가져온다
const order_items = await this.orderRepo.getItemsById(dto.order_id)
order_items.forEach((item)=>{
// Mapper를 통해서 원하는 형태로 데이터 가공
const result = OrderMapper(item)
processed_items.push(result)
})
// 정산 데이터 생성에 대한 Promise 값을 리턴한다.
return this.payoutRepo.createPayouts(processed_items)
} catch(e) {
console.log(e)
return this.serviceErr.payoutCreateFail(dto.id)
}
}
}
오픈마켓 플랫폼에서 주문에 해당하는 Order 데이터가 생성되고 이 주문 데이터를 가지고 각 판매자들에게 일정 금액 정산을 위한 Payout 정산 데이터를 만드는 행위에 대해서 주문이 완료되는 시점에서 payoutService 클래스 객체를 선언해서 excute를 실행한다고 가정을 해보자. 이때 경영팀에 요청으로 정산을 해주는 금액에 대해서 시기에 따라 많이 변동된다고 한다면 payoutService 클래스에서 정산 금액을 산출하는 부분을 delegate 패턴을 이용해서 산출하는 계산을 다른 객체에게 위임을 한다면 보다 효율적으로 코드를 유지보수 할 수 있게 된다.
export interface policyDelegator {
caculateOwnerAmount(order:OrderDTO){
const fee = order.total * enum.percentage.owner
const payout_total = (order.price - fee) * 0.8
}
calculateDeliverAmount(order:OrderDTO){
const fee = order.total * enum.percentage.deliver
const payout_total = (order.price - fee) * 0.6
}
}
간단하게 policyDelegator라는 인터페이스를 생성해서 정산 금액 산출에 필요한 계산 함수를 만들었다고 가정해봅시다. 이 delegator를 원하느 클래스에 연결해주고 delegator에게 위임하고 싶은 연산에 대해서 호출을 해주면서 사용해준다면 중간에 결제와 상품, 정산에 대한 정책이 변경이 된다고 해도 각 관련 클래스 객체들을 수정할 필요없이 delegator만 수정을 해주면 됩니다.
import{ Order, OrderMapper } from '../orders/order.ts'
import{ policyDelegator } from '../orders/policy.ts'
export class payoutService implements IserverAPI {
private orderRepo: IorderRepo
private payoutRepo: IpayoutRepo
private serviceErr: orderServiceError
public delegator: Idelegator
constructor(orderRepo:IorderRepo, payoutRepo:IpayoutRepo, delegator:policyDelegator){
this.orderRepo = orderRepo
this.payoutRepo = payoutRepo
this.delegator = policyDelegator
}
public async excute(dto:GeneratePayoutDTO): Promise<payout>{
try{
const processed_items = []
// 아이디 값에 해당하는 아이템 데이터를 가져온다
const order_items = await this.orderRepo.getItemsById(dto.order_id)
order_items.forEach((item)=>{
// Mapper를 통해서 원하는 형태로 데이터 가공
const result = OrderMapper(item)
//가공된 정보에서 delegator를 통해서 개별 데이터의 정산금액을 계산해준다.
const processed_result = result.map((el)=> this.delegator(el))
processed_items.push(processed_result)
})
// 정산 데이터 생성에 대한 Promise 값을 리턴한다.
return this.payoutRepo.createPayouts(processed_items)
} catch(e) {
console.log(e)
return this.serviceErr.payoutCreateFail(dto.id)
}
}
}
정리
delegate pattern의 장점은 다음과 같습니다.
- 로직을 분리하여 다른 클래스에도 사용이 가능하기 때문에 코드의 재사용성이 좋아집니다.
- 프로토콜이 컨트롤 밖에서 정의할 수 있기 때문에 보다 유연하게 서비스를 기획할 수 있습니다.
- 커뮤니케이션 과정에서 불필요한 리소스 낭비를 줄이고 로직 흐름 이해가 쉬워집니다.
하지만 좋은 점도 있으면 아쉬운 점도 있는 법이죠 주의해야할 점으로는
- n:n 대응으로 이벤트를 처리할 때는 오히려 더 복잡해질 수 있다.
- 원형 프로토콜이 정해지면 해당 프로토콜의 형태로 사용해야하기 때문에 큰 변경에 대해서는 위임을 주고 있는 연결된 객체들과 유기적으로 유지보수가 이루어져야 한다.
'Node.js' 카테고리의 다른 글
게시판 SQL 또는 NoSQL에서 고민하는 분들 예쁘기만하고 매력없는 애들이랑은 달라 달라 달라아~ (0) | 2023.08.07 |
---|---|
데코 데코니~ NestJS 데커레이터 😋🤘 (0) | 2023.01.31 |
node.js란? (0) | 2020.09.25 |