JS/[inflearn] TS JS 디자인패턴

생성 패턴 (Creational Pattern) _ 빌더(Builder)

web_seul 2025. 1. 18. 07:57

빌더

복잡한 객체의 생성과정을 단계별로 제어하고 캡슐화하여 다양한 구성의 객체 생성,

많은 선택적 매개변수를 가진 객체 생성시 코드 가독성과 유지보수성 향상

interface Btn {
  //버튼 필요요소
  name: string; type: string; onClick: () => void;
}

interface Input{
  //input 필요요소
  name: string; type: string; onChange: () => void; value: string|number;
}

//확정된 필요요소가 아니라서 유연하게 만들고자 함

class GrimpanMenuBtn{
  private name: string;
  private type: string;
  private onClick?: () => void;
  private onChange?: () => void;
}
constructor(name: string, type: string, onClick?: () => void, onChange?:() => void, active?: boolean,
  this.name = name;
  this.type = type;
  this.onClick = onClick;
  this.onChange = onChange;
}

setName(name: string){
  this.name = name;
}
setType(type: string){
  this.type = type;
}
setActive(active: boolean){
  this.active = active;
}
setBalue(value: boolean){
  this.value = value;
}

//backBtn이 완성된 버튼이라고 확신x
const backBtn = new GrimpanMenuBtn('뒤로', 'back', () => {
  console.log('뒤로가기');
});
class GrimpanMenuBtn{
  private name: string;
  private type: string;
  private onClick?: () => void;
  private onChange?: () => void;
}
constructor(name: string, type: string, onClick?: () => void, onChange?:() => void, active?: boolean,
  this.name = name;
  this.type = type;
  this.onClick = onClick;
  this.onChange = onChange;
}

export class GrimpanMenuBtn{
  //builer가 아니면 만들 수 없도록
  private constructor(menu: GrimpanMenu, name: string, onClick?: () => void, active?: boolean) {
    super(menu, name);
    this.active = active;
    this.onClick = onClick;
  }

  draw() {
    const btn = document.createElement('button');
    btn.textContent = this.name;
    if (this.onClick) {
      btn.addEventListener('click', this.onClick.bind(this));
    }
    this.menu.dom.append(btn);
  }

  //버튼만 전문적으로 만드는 Builder
  //Builder를 여러개 선택해야할 때 외부에 두기도 함
  static Builder = class GrimpanMenuBtnBuilder extends GrimpanMenuElementBuilder {
    override btn: GrimpanMenuBtn; (= class GrimpanMenuBtn)
    
    //필수값
    constructor(menu: GrimpanMenu, name: string) {
      super();
      this.btn = new GrimpanMenuBtn(menu, name);
    }
    
   //optional
    setOnClick(onClick: () => void) {
      this.btn.onClick = onClick;
      return this;	//메서드 체이닝
    }   
    setActive(active: boolean) {
      this.btn.active = active;
      return this;
    }
    build(){
      return this.btn;
    }
  }
}

const backBtn = new GrimpanMenuBtn.Builder('뒤로', 'back');	//필수
  .setOnClick(() => {})	//옵셔널
  .setActive(false)
  .build();	//완성된 버튼이므로 build를 함
const backBtnBuilder = new GrimpanMenuBtn.Builder('뒤로', 'back');	
  .setOnClick(() => {})
  
//오래걸리는 작업
backBuilder
  .setValue(longValue)
  .setActive(false)
  .build()
class GrimpanMenuBtn{
  name: string;	//외부에서 사용하므로 private x, director만 참조
  type: string;
  onClick?: () => void;
  onChange?: () => void;
}
constructor(name: string, type: string, onClick?: () => void, onChange?:() => void, active?: boolean,
  this.name = name;
  this.type = type;
  this.onClick = onClick;
  this.onChange = onChange;
}

//builder가 여러개 일때 외부에 둠
interface GrimpanMenuBuilder{
    setOnClick(onClick: () => void): this;
    setActive(active: boolean) : this;
    build(): this;
}

//Chrome 그림판 메뉴 버튼
class ChromeGrimpanMenuBtnBuilder implements GrimpanMenuBtnBuilder{
  btn: GrimpanMEnuBtn;
  constructor(name: string, type: string){
    this.btn = new GrimpanMEnuBtn(name, type);
  }
  setOnclick(onClick: () => void){
    this.btn.onClick = onClick;
    return this;
  }
  setActive(active: () => boolean){
    this.btn.active = active;
    return this;
  }
}

//director를 통해서만 btn을 수정함
export class GrimpanMenuBtnDirector{
  static createBackBtn(builder: GrimpanMenuBuilder){
    const backBtnBuilder = builder
      .setOnClick(() => {})
      .setActivE(false);
    return backBtnBuilder;
  }
}

GrimpanMenuBtnDirector.createBackBtn(new ChromeGrimpanMenuBtnBuilder('뒤로', 'back'))

 

import {GrimpanMenu} from './GrimpanMenu.js';

export class GrimpanMenuBtn {
 private menu: GrimpanMenu;
 private name: string;
 private onClick?: () => void;

 private constructor(menu: GrimpanMenu, name: string, type: string, onClick?: () => void,
   this.menu = menu;
   this.name = name;
   this.onClick = onClick;
 }
 
 draw(){
   //단일책임원칙 위반
   if(this.type === 'button'){
     const btn = document.createElement('button');
     btn.textContent = this.name;
     if(this.onClick){
       btn.addEventListener('click', this.onClick.bind(this)));
     }
   }else if(this.type === 'input'){
     const btn = document.createElement('input');
     btn.type = 'color';
     if(this.onChange){
       btn.addEventListener('change', this.onChange.bind(this));
     }
     this.menu.dom.append(btn);
   }
 }
}
import {GrimpanMenu} from './GrimpanMenu.js';

export abstract class GrimpanMenuElement {
 private menu: GrimpanMenu;
 private name: string;
 private onClick?: () => void;

 private constructor(menu: GrimpanMenu, name: string, type: string, onClick?: () => void,
   this.menu = menu;
   this.name = name;
   this.onClick = onClick;
 }

 
draw(){
   const btn = document.createElement('input');
   ~~
}

//단일책임원칙 위반으로 분리
class GrimpanMenuBtn extends GrimpanMenuElement{
 ~~
}
class GrimpanMenuInput extends GrimpanMenuElement{
 ~~
}

 

 

//GrimpanMenuBtn.ts
import { GrimpanMenu } from "./GrimpanMenu.js";

class GrimpanMenuElementBuilder {
  btn!: GrimpanMenuElement;
  constructor() {}

  build() {
    return this.btn;
  }
}

class GrimpanMenuElement {
  protected menu: GrimpanMenu;
  protected name: string;

  protected constructor(menu: GrimpanMenu, name: string) {
    this.menu = menu;
    this.name = name;
  }

  abstract draw(): void;
}

export class GrimpanMenuInput extends GrimpanMenuElement {
  onChange;
  value;

  constructor(menu:, name, onChange, value) {
    super(menu, name);
    this.onChange = onChange;
    this.value = value;
  }

  draw() {
    const btn = document.createElement('input');
    btn.type = 'color';
    btn.title = this.name;
    if (this.onChange) {
      btn.addEventListener('change', this.onChange.bind(this));
    }
    this.menu.dom.append(btn);
  }

  //공통 분리
  static Builder = class GrimpanMenuInputBuilder extends GrimpanMenuElementBuilder {
    override btn: GrimpanMenuInput;
    constructor(menu: GrimpanMenu, name: string //필수요소) {
      super();
      this.btn = new GrimpanMenuInput(menu, name);
    }
  
    setOnChange(onChange: () => void) {
      this.btn.onChange = onChange;
      return this;
    }
    setValue(value: string | number) {
      this.btn.value = value;
      return this;
    }
  }
}
const backBtnBuilder = new GrimpanMenuBtn.Builder('뒤로', 'back');	
  .setOnClick(() => {})
  
//오래걸리는 작업
backBuilder
  .setValue(longValue)
  .setActive(false)
  .build()
반응형