디자인 패턴

·2023년 4월 28일
0

study

목록 보기
67/81
post-thumbnail

자바스크립트에서 보통 사용하고 있는 디자인 패턴들(Design Patterns)

디자인 패턴?

기존 환경 내에서 반복적으로 일어나는 문제들을 어떻게 풀어나갈 것인가에 대한 일종의 솔루션

즉, 개발자들끼리 협업을 잘 할 수 있도록 코드들의 패턴을 짜서 코드의 질과 효율성을 높히는 것

디자인 패턴의 장점과 단점

장점

  • 개발자 간의 원활한 의사소통
  • 소프트웨어 구조 파악 용이
  • 재사용을 통한 개발 시간 단축
  • 설계 변경 용청에 대한 유연한 대처
  • 불필요한 리소스 낭비 방지
  • 야근없이, 빠른 퇴근 가능

단점

  • 객체지향 설계/구현 위주로 사용된다.
  • 초기 투자 비용 부담

디자인 패턴 유형

  1. Creation Pattern
    객체의 생성에 관련된 패턴
    객체의 생성 로직을 숨김으로써 유연성을 향상시킨다.
  2. Strutural Pattern
    클래스와 객체의 구성에 관련된 패턴
  3. Behavioral Pattern
    객체와 객체 간의 통신에 관련된 패턴

디자인 패턴의 종류

Creation Pattern

생성자 패턴(Constructor Pattern)

생성자 패턴은 기존에 클래스를 제공하지 않았던 자바스크립트가 ES6에서 class 키워드 제공을 통해 향상된 패턴

Constructor, 생성자 : 구체적인 속성과 객체로 구성되어 있는 함수

생성자 패턴을 이용해서 같은 객체의 여러 인스턴스화를 할 수 있습니다.

이전

function OldPerson(name, age) {
  this.name = name;
  this.age = age;
  this.getDetails = function () {
    console.log(`[OLD] ${this.name} is ${this.age} years old!`);
  }
}

// 인스턴스 생성
const oldPerson = new OldPerson('John', 20);
oldPerson.say(); // [OLD] Output - “John is 20years old!”

=>
이후

class NewPerson {
  constructor(name, age) {
    this.name = name;
    this.age = age;
    this.say() = function () {
      console.log(`[NEW] ${this.name} is ${this.age} years old!`);
    };
  }
}

// 인스턴스 생성
const newPerson = new NewPerson('John', 20);
newPerson.say(); // [NEW] Output - “John is 20years old!”

팩토리 패턴(Factoru Pattern)

팩토리 패턴은 클래스가 객체를 생성하는 패턴

Factory 클래스에 의해 생성하는 객체는 동일한 인터페이스를 가져야 함

class Person {
  constructor() {}
  
  make (type) {
    switch (type) {
      case 'house':
        return new House();
      case 'car':
        return new Car();
      default:
        return false;
    }
  }
}

class House {
  constructor() {}

  say () {
    console.log(`I'm House`);
  }
}

class Car {
  constructor() {}

  say () {
    console.log(`I'm Car`);
  }
}

const person = new Person()

const house = person.make('house')
const car = person.make('car')

house.say() // I'm House
car.say()   // I'm Car

프로토타입 패턴(Prototype Pattern)

프로토타입 패턴은 객체의 템플릿을 기반으로 새로운 객체를 만들 수 있다. 프로토타입 패턴은 상속을 기반으로 하지만 JavaScript는 Object의 create() 메소드를 활용하여 손쉽게 구현 가능.

class Car {
  constructor (_wheels) {
    this.noOfWheels = _wheels;
  }
  
  start() {
    return `start ${this.noOfWheels}`;
  }
  
  stop() {
    return `stop ${this.noOfWheels}`;
  }
}

const car = new Car(4);

const cloneCar1 = Object.create(car, { owner: { value: 'Mung1' } });
const cloneCar2 = Object.create(car, { owner: { value: 'Mung2' } });

console.log(cloneCar1.__proto__ === car); // true
console.log(cloneCar2.__proto__ === car); // true

cloneCar2.noOfWheels += 10

console.log(cloneCar1.start()) // start 4
console.log(cloneCar1.stop()) // stop 4
console.log(cloneCar1.noOfWheels) // 4
console.log(cloneCar1.owner)  // Mung1

console.log(cloneCar2.noOfWheels) // 14

싱글톤 패턴 (Singleton Pattern)

싱글톤은 하나의 객체만 생성하는 목적으로 사용

디비 커넥션 처럼 한 시스템에서 매번 커넥션을 연결할 필요 없을 때 싱글톤을 이용하여 하나의 커넥션만 유지

class Car {
  constructor (_wheels) {
    this.wheels = _wheels;
  }
  
  setWheels(_n) {
    this.wheels = _n;
  }
}

const Singleton = {
  instance: null,

  getInstance(_param1) {
    if(!this.instance) this.instance = new Car(_param1);
    return this.instance
  },
};

let car1 = Singleton.getInstance(4);
let car2 = Singleton.getInstance(3);
let car3 = Singleton.getInstance(2);

console.log(car1.wheels, car2.wheels, car3.wheels); // 4 4 4
car2.setWheels(10)
console.log(car1.wheels, car2.wheels, car3.wheels); // 10 10 10

빌더 패턴 (Builder Pattern)

빌더 패턴은 체이닝 형태를 유지하여 확장성 있는 객체를 만들 수 있다.

이전

class Request { 
  constructor(url, data, method) { 
    this.url = url; 
    this.method = method; 
    this.data = data; 
  } 
}

// data를 반드시 전달해줘야 함
const request = new Request('http://localhost', {}, method)

=>
이후

class Request { 
  constructor() { 
    this.url = ''; 
    this.method = ''; 
    this.data = null; 
  } 
} 

class RequestBuilder { 
  constructor() { 
    this.request = new Request(); 
  } 
  forUrl(url) { 
    this.request.url = url; 
    return this; 
  } 
  
  useMethod(method) { 
    this.request.method = method; 
    return this; 
  } 
  
  setData(data) { 
    this.request.data = data; 
    return this; 
  } 
  
  //  속성을 설정하는 메소드는 반드시 this를 반환하여 체이닝이 가능하도록 해야함
  build() { 
    return this.request; 
  } 
} 

let getRequest = new RequestBuilder() 
  .forUrl('https://blog.naver.com/pjt3591oo') 
  .useMethod('GET') 
  .build(); 
  
let postRequest = new RequestBuilder() 
    .forUrl('https://blog.naver.com/pjt3591oo') 
    .useMethod('POST') 
    .setData({ id: 'hg', password: 1234 }) 
    .build();

Structural Design Pattern

어댑터 패턴(Adapter Pattern)

어댑터 패턴은 서로다른 인터페이스 시스템을 맞추기 위해 어댑터를 추가하여 마치 하나의 시스템인 것 처럼 동작하는 패턴

Client는 과거에 만들어진 인터페이스
Adaptee는 발전된 형태의 인터페이스

기존의 코드는 Client 인터페이스에 맞춰서 개발이 되었기 때문에 새로 개발된 Adaptee를 사용하기 위해선 인터페이스를 수정해야 하지만 Adapter를 이용하여 기존 코드의 인터페이스 수정없이 Adaptee를 사용 할 수 있음.

어댑터 패턴의 핵심은 기존의 사용중인 인터페이스를 사용자의 큰 변화없이 사용하는 것이 목표

컴포지트 패턴 (Composite Pattern)

컴포지트 패턴은 파티셔닝 JS 디자인 패턴이라고도 불리며 디렉터리(폴더) 구조를 관리하는 모습을 보임

마치 React나 Vue와 같이 컴포넌트를 관리하는 모습을 가짐.

모듈 패턴 (Module Pattern)

자바스크립트의 모듈 패턴은 클로저를 이용하여 함수를 마치 클래스처럼 사용하는 기법

function AnimalContainter () {
  const container = [];

  function addAnimal (name) {
    container.push(name);
  }

  function getAllAnimals() {
    return container;
  }

  function removeAnimal(name) {
    const index = container.indexOf(name);
    if(index < 1) {
      throw new Error('Animal not found in container');
    }
    container.splice(index, 1)
  }

  return {
    add: addAnimal,
    get: getAllAnimals,
    remove: removeAnimal
  }
}

  const container = AnimalContainter()

  container.add('Hen')
  container.add('Goat')
  container.add('Sheep')

  console.log(container.get()) // ["Hen", "Goat", "Sheep"]
  container.remove('Sheep')
  console.log(container.get()) // ["Hen", "Goat"]

데코레이터, 장식자 패턴 (Decorator Pattern)

데코레이터는 코드 재사용을 목적으로 함. 자바스크립트에선 이를 쉽게 구현할 수 있다. 이를 이용하여 동일한 클래스로 만들어진 객체에 다른 동작을 부여할 수 있다.

class Vehicle {
  constructor (vehicleType = "car") {
    this.vehicleType = vehicleType;
    this.model = 'default';
    this.license = '12345-123';
  }
}

const truck = new Vehicle( "truck" );

// 여기서 화살표 함수를 사용하면 this 바인딩이 안되기 떄문에 function 이용
truck.setModel = function( _model ){
    this.model = _model;
};

truck.setColor = function( _color ){
    this.color = _color;
};

truck.setModel( "CAT" );
truck.setColor( "blue" );

console.log( truck );
/*
Vehicle {
  vehicleType: 'truck',
  model: 'CAT',
  license: '12345-123',
  setModel: [Function],
  setColor: [Function],
  color: 'blue'
}
*/

const dump = new Vehicle( "dump" );
console.log(dump) // Vehicle { vehicleType: 'dump', model: 'default', license: '12345-123' }

퍼사드 패턴 (Facade Pattern)

퍼사드 패턴은 내부적으로 복잡한 구조(Subsystem)를 단순화(Facade)하여 외부에 공개하는 패턴

class Bank {
  verify (_name, _amount) {
    return true;
  }
}

class Credit {
  get (_name) {
    return true;
  }
}

class Background {
  check (_name) {
    return true;
  }
}

class Mortgage {
  constructor (_name) {
    this.name = _name;
  }
  
  applyFor (amount) {
    let result = "approved";
    if (!new Bank().verify(this.name, amount)) {
        result = "denied";
    } else if (!new Credit().get(this.name)) {
        result = "denied";
    } else if (!new Background().check(this.name)) {
        result = "denied";
    }
    return `${this.name} has been ${result} for a ${amount} mortgage`
  }
}

const mortgage = new Mortgage("Joan Templeton");
const result = mortgage.applyFor("$100,000");

console.log(result);

클래스 1, 2, 3에 해당하는 Bank, Credit, Background 클래스로 구현된 기능을 Mortgage의 applyFor 메소드를 통해 제공

프록시 패턴 (Proxy Pattern)

프록시 패턴은 접근을 제어하고 비용 절감하며 복잡성을 줄이기 위해 사용

프록시는 네트워크 연결, 큰 메모리 객체와 같은 비용이 많이 들거나 복제가 불가능한 리소스 같은 형태에서 사용

만약 파일을 읽어야 한다면 파일을 직접 읽지 않고 프록시 객체를 통해 접근.
프록시는 반복된 파일 접근에 대해 캐싱 처리 등을 수행할 수 있다.
프록시 패턴을 적용하는 상황은 다음과 같다.

  • 가상 프록시: 생성 비용이 높거나 리소스를 많이 사용하는 객체를 위한 프록시
  • 원격 프록시: 원격 객체에 대한 접근 제어
  • 보호 프록시: 민감한 마스터 객체에 대한 접근 권한 제어
class FlightListAPI {
  getFlight() {
      // get master list of flights
      console.log('Generating flight List');
  }

  searchFlight (flightDetails) {
      // search through the flight list based on criteria
      console.log('Searching for flight');
  }
  
  addFlight(flightData) {
      // add a new flight to the database
      console.log('Adding new flight to DB');
  }
};
  
class FlightListProxy {
  constructor () {
    this.flightList = new FlightListAPI();
  }
  getFlight() {
    return this.flightList.getFlight();
  }

  searchFlight(flightDetails) {
    return this.flightList.searchFlight(flightDetails);
  }

  addFlight(flightData) {
    return this.flightList.addFlight(flightData);
  }
}
  
console.log("----------With Proxy----------")
const proxy = new FlightListProxy()
proxy.getFlight() // Generating flight List

경량화 패턴 (Flyweight Pattern)

경량화 패턴은 Cost(비용)이 높은 자원을 공통으로 사용하는 패턴, 캐시를 목적으로 하는 패턴

자원에 대한 비용이란?
1. Duplicate Create (반복되는 패턴)
2. Low Frequncy AND Hight Cost About Create (생성 비용이 높지만 자주 사용되지 않는경우)

class Flyweight{
  map: {[key: string]: Subject} = {};
 
  getSubject( name: string ): Subject {
    let subject: Subject = this.map[name];

    if(!subject){
      subject = new Subject(name);
      this.map[name] = subject;
    }

    return subject;
  }
}

class Subject{
  name: string;

  constructor( name: string){
    this.name = name;
    console.log("create : " + name);
  }
}

const flyweight: Flyweight = new Flyweight();

flyweight.getSubject("a");
flyweight.getSubject("a"); // 다시 생성하지 않음
flyweight.getSubject("b");
flyweight.getSubject("b"); // 다시 생성하지 않음

/*
실행결과

create : a
create : b
*/

Behavioral Design Pattern

Chain of Responsibility Pattern

빌더 패턴을 구현하는 핵심 패턴

class Request {
  constructor (_amount) {
    this.amount = _amount;
  }

  get (bill) {
    const count = Math.floor(this.amount / bill);
    this.amount -= count * bill;
    console.log(`Dispense ${count} $${bill} bills`);
    return this;
  }
}

const request = new Request(378); //Requesting amount
request.get(100).get(50).get(20).get(10).get(5).get(1);

this를 반환하여 체이닝을 할 수 있도록 함

Command Pattern

Command 패턴은 동작이나 작업을 캡술화하는 패턴

A라는 클래스를 작업을 하기 위해 A 객체를 직접 접근하지 않고 중간 객체를 통해 작업수행

const calculator = {
  add: function(x, y) {
      return x + y;
  },
  sub: function(x, y) {
      return x - y;
  },
  divide: function(x,y){
      return x/y;
  },
  multiply: function (x,y){
      return x*y;
  }
}

const manager = {
  execute: function(name) {
      if (name in calculator) {
          return calculator[name].apply(calculator, [].slice.call(arguments, 1));
      }
      return false;
  }
}

console.log(manager.execute("add", 5, 2)); //  7
console.log(manager.execute("multiply", 2, 4)); //  8

Observer Pattern

Observer 패턴은 관찰중인 객체(Subject)에 발생하는 이벤트에 대해 여러 객체(Observer)에게 알리는 구독 메커니즘 제공

class Click {
  constructor () {
    this.observers = [];
  }

  subscribe (fn) {
    this.observers.push(fn);
  }

  unsubscribe(fn) {
    this.observers = this.observers.filter( (item) => item !== fn );
  }

  fire(o, thisObj) {
    let scope = thisObj;
    this.observers.forEach(function(item) {
      item.call(scope, o);
    });
  }
}

const clickHandler = item => { 
  console.log("Fired:" +item);
};

const click = new Click();

click.subscribe(clickHandler);
click.fire('event #1');

click.unsubscribe(clickHandler);
click.fire('event #2');

click.subscribe(clickHandler);
click.fire('event #3');

/* 
실행결과

Fired:event #1
Fired:event #3
*/

Iterator Pattern

반복자 패턴을 통해 순차적으로 접근 가능

const items = [1, "hello", false, 99.8];

class Iterator {
  constructor(items) {
    this.items = items;
    this.index = 0;
  }

  hasNext(){
    return this.index < this.items.length;
  }

  next(){
    return this.items[this.index++]
  }
}

const iterator =  new Iterator(items);
while(iterator.hasNext()){
  console.log(iterator.next());
}

하지만 ES6의 제너레이터를 이용하면 좀 더 편하게 구현 가능

// 제너레이터 이용
const items = [1, "hello", false, 99.8];

function* Iterator (target) {
  for(item in target) {
    yield item;
  }
}

const iterator = Iterator(items);

for (item of iterator) {
  console.log(item)
}

Template Pattern

템플릿 패턴은 객체의 뼈대만 정의. 객체가 만들어지면 세부적인 내용 정의

// 인터페이스 클래스
// 사용자가 구현할 항목 명시
class DataStore {
  connect() {}

  select() {}

  disconnect() {}
}

class Mysql extends DataStore {
  constructor () {
    super()
  }

  process() {
    this.connect();
    this.select();
    this.disconnect();
  }
}

const mySql = new Mysql();

// 상속받은 인터페이스 정의
mySql.connect = function() {
  console.log("MySQL: connect step");
};

mySql.select = function() {
  console.log("MySQL: select step");
};

mySql.disconnect = function() {
  console.log("MySQL: disconnect step");
};

mySql.process();

템플릿 패턴은 특정 기능을 제공하기 위해 구현할 기능 인터페이스를 제공함으로써 사용자가 원하는 기능을 구현할 수 있도록 도와줌.

참고
자바스크립트 디자인 패턴
각각의 디자인 패턴에 대한 설명

profile
개발자 꿈나무

0개의 댓글