Santos의 개발블로그

웹 개발자가 알아야 할 7가지 디자인 패턴 본문

Language & Framework & Library/JavaScript

웹 개발자가 알아야 할 7가지 디자인 패턴

Santos 2021. 4. 4. 12:31

* 이 글은 7 JavaScript Design Patterns Every developer should Know를 번역하였습니다.

 

7 JavaScript Design Patterns Every Developer Should Know

What if you can structure your source code like a beautiful template that can be applied to every project of the same kind?

javascript.plainenglish.io

모든 프로젝트를 위한 템플릿? 듣기 좋은 소리 아니가요? 

그것 또한 제가 노력하고 있는 코딩의 방법입니다. 

 

좋은 구조를 만들고 이쁜 코드를 짜기 위해서 디자인 패턴의 사용은 좋은 방법 중 하나입니다. 

 

이곳에서 자바스크립트에서 보통 사용하고 있는 디자인 패턴을 함께 살펴봅시다. 


1. Constructor Pattern 

구체적인 속성과 메서드가 객체로 구성되어 있는 함수인 생성자는 매우 친근할 것입니다. 

Constructor Pattern은 우리가 알고있는 정의와 비슷합니다. 이 패턴을 사용함으로써 같은 객체의 여러 인스턴스화를 할 수 있습니다. 

 

자바스크립트에서 꽤 많이 사용되고 있는 패턴입니다. 아래는 예시입니다.

// Using {} to create empty objects:
let person = {};

// Using Object() to create empty objects:
let person = new Object();

// Using function constructor:
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.showName = () => console.log(this.name);
}
let person = new Person(‘Amy’, 28);
person.showName();

2. Prototype Pattern 

Prototype Pattern은 객체 기반 창조 디자인 패턴입니다. 이 패턴은 객체의 새로운 인스턴스를 프로토타입에서 복제를 통해 만듭니다. Prototype Pattern에서 중요한 점은 각 객체 생성자에 대한 청사진으로 사용되는 객체를 생성하는 것입니다. 

 

만약 새로운 객체의 생성자를 직접적으로 만든다면 복잡하고, 비효율적일 수 있으니 Prototype Pattern에서는 이를 커버할 수 있습니다. 

 

아래는 Prototype Pattern의 예시입니다. 

function Book(title, price) {
  this.title = title;
  this.price = price;
  this.printTitle = () => console.log(this.title);
}

function BookPrototype(prototype) {
  this.prototype = prototype;
  this.clone = () => {
    let book = new Book();
    book.title = prototype.title;
    book.price = prototype.price;
    
    return book;
  };
}

let sampleBook = new Book(‘JavaScript’, 15);
let prototype = new BookPrototype(sampleBook);
let book = prototype.clone();
book.printTitle();

자바스크립트에는 자체 내장 프로토 타입 기능이 있기 때문에, 아래의 방법을 통해 효과적으로 이 패턴을 사용할 수 있습니다. 

let book = {
  title: ‘JavaScript’,
  price: 15
}

let anotherBook = Object.assign({}, book);

위 코드를 사용하면 프로토 타입으로 만들어진 첫 번째 책을 가질 수 있습니다. 새로운 책을 프로토 타입을 사용함으로 만들 수 있고, 새로운 책은 프로토 타입으로 이미 정의 된 해당 속성들을 모두 가질 수 있습니다.

3. Command Pattern 

Command Pattern의 주요 목적은 액션 또는 객체 인스턴스화를 캡슐화하는 것입니다.

이커머스를 위한 Payment system을 만들어야 한다고 상상을 해봅시다. 여기 선택한 결제 방법에 따라 처리 할 특정 프로세스가 있습니다. 

if (selectedPayment == ‘creditcard’) {
  // handle payment by creditcard
}

위 코드는 오직 한가지 결제 방법으로 구성되어 있지만, 아마도 더 다양한 결제 방법이 존재할 것입니다. 위 코드를 사용하게 되었을 때 인터페이스가 아닌 구현된 부분을 제공하므로 Coupling(결합)이 발생합니다. 

 

Command Pattern은 Coupling(결합)을 느슨하게 하는 좋은 해결 방법입니다. 시스템은 각 특정 지불 방법 처리에 대한 많은 정보를 알아야합니다. 이를 충족시키기 위해서는 작업을 요청하는 코드와 실제 구현을 실행하는 코드를 분리시켜야 합니다. 아래는 예시입니다. 

function Command(operation) {
  this.operation = operation;
}

Command.prototype.execute = function () {
  this.operation.execute();
}

function ProcessCreditCardPayment() {
  return {
    execute: function() {
      console.log(‘Credit Card’)
    }
  };
}

function ProcessPayPalPayment() {
  return {
    execute: function() {
      console.log(‘PayPal’)
    }
  };
}

function ProcessStripePayment() {
  return {
    execute: function() {
      console.log(‘Stripe’)
    }
  };
}

function CreditCardCommand() {
  return new Command(new ProcessCreditCardPayment());
}

function PayPalCommand() {
  return new Command(new ProcessPayPalPayment());
}

function StripeCommand() {
  return new Command(new ProcessStripePayment());
}

function PaymentSystem() {
  let paymentCommand;
    
  return {
    setPaymentCommand: function(command) {
      paymentCommand = command;
    },
    executeCommand: function() {
      paymentCommand.execute();
    }
  };
}

function run() {
  let paymentSystem = new PaymentSystem();
  paymentSystem.setPaymentCommand(new CreditCardCommand());
  paymentSystem.executeCommand();
  paymentSystem.setPaymentCommand(new PayPalCommand());
  paymentSystem.executeCommand();
  paymentSystem.setPaymentCommand(new StripeCommand());
  paymentSystem.executeCommand();
}

run();

 

4. Observer Pattern 

혹시 뉴스를 구독해보신적이 있으신가요? 

만약 해보셨다면, 구독자는 이메일로써 뉴스에 대한 알림을 받게 됩니다. 

 

Observer pattern은 위와 같은 메커니즘을 가지고 있습니다. 이벤트를 구독하는 구독 모델을 제공하며 해당 이벤트가 발생하면 알림을 받게 됩니다. 아래는 예시입니다. 

function Newsletter() {
  this.observers = [];
}

Newsletter.prototype = {
  subscribe: function (observer) {
    this.observers.push(observer);
  },
  unsubscribe: function(observer) {
    this.observers = this.observers.filter(ob => ob !== observer);
  },
  notify: function() {
    this.observers.forEach(observer => console.log(‘Hello ‘ + observer.toString()));
  }
}

let subscriber1 = ‘Subscriber 1’;
let subscriber2 = ‘Subscriber 2’;
let newsletter = new Newsletter();

newsletter.subscribe(subscriber1);

newsletter.subscribe(subscriber2);
newsletter.notify(); // Hello Subscriber 1 Hello Subscriber 2

newsletter.unsubscribe(subscriber2);
newsletter.notify(); // Hello Subscriber 1

5. Singleton Pattern 

이 패턴은 대다수의 개발자들이 알고 있을 것입니다. 보통 이 패턴은 클래스에 대해 오직 하나의 인스턴스를 가져야하고 전역에서 접근할 수 있도록 제한하기 위해 사용합니다. 이 패턴은 하나의 어플리케이션에서 특정 작업을 한 곳에서 처리해야 할때 매우 유용한 패턴입니다. 

 

아래는 예시입니다. 

const utils = (function () {
  let instance;
  
  function initialize() {
    return {
      sum: function (a, b) {
        return a + b;
      }
    };
  }
  return {
    getInstance: function () {
      if (!instance) {
        instance = initialize();
      }
      return instance;
    }
  };
})();

let sum = utils.getInstance().sum(3, 5); // 8

6. Module Pattern

이 패턴은 소프트웨어 모듈의 개념을 구현하는데 사용되는 디자인 패턴입니다. 모듈 패턴은 강력하고 자바스크립트에서 많이 사용되어지는 패턴 중 하나입니다. 

 

자바스크립트 라이브러리를 살펴보면 Singleton Pattern과 마찬가지로 자주 볼 수 있을 것입니다. 이 패턴은 코드에 대한 캡슐화를 제공하기 때문에 더욱 안전한 어플리케이션을 만들 수 있습니다.  

const bookModule = (function() {
  // private
  let title = ‘JavaScript’;
  let price = 15;
  // public
  return {
    printTitle: function () {
      console.log(title);
    }
  }
})();

bookModule.printTitle(); // JavaScript

7. Factory Pattern

아마 자신도 모르게 이 패턴을 자주 사용하고 있었을 것입니다. Factory Pattern은 말 그대로 공장과도 같습니다. 자바스크립트에서는 객체 생성을 나머지 코드와 분리합니다. 객체 생성코드만 따로 싼 다음 다른 객체들도 만들 수 있는 API만 노출 시킵니다. 

const Vehicle = function() {};

const Car = function() {
  this.say = function() {
    console.log(‘I am a car’);
  }
};

const Truck = function() {
  this.say = function() {
    console.log(‘I am a truck’);
  }
};

const Bike = function() {
  this.say = function() {
    console.log(‘I am a bike’);
  }
};

const VehicleFactory = function() {
  this.createVehicle = (vehicleType) => {
    let vehicle;
    switch (vehicleType) {
      case ‘car’:
        vehicle = new Car();
        break;
      case ‘truck’:
        vehicle = new Truck();
        break;
      case ‘bike’:
        vehicle = new Bike();
        break;
      default:
        vehicle = new Vehicle();
    }
 
    return vehicle;
  }
};

const vehicleFactory = new VehicleFactory();

let car = vehicleFactory.createVehicle(‘car’);
let truck = vehicleFactory.createVehicle(‘truck’);
let bike = vehicleFactory.createVehicle(‘bike’);

car.say(); // I am a car
truck.say(); // I am a truck
bike.say(); // I am a bike

Conclusion

솔직하게 이러한 패턴을 프로젝트에 적용시키는 것은 매우 힘든일입니다. 계속적인 시도로 인한 많은 실패는 패턴의 익숙함을 낳을 것입니다. 디자인 패턴을 완전히 이해하고 언제 어떤 패턴을 구현해야하는지 안다면 코드를 가장 강력한 수준으로 끌어 올릴 수 있습니다. 

 

위에 코드는 디자인 패턴의 단면만 보여드린 것입니다. 만약 이러한 토픽에 대해 관심이 있으시다면 더 깊게 공부해보시길 바랍니다. 절대 후회할 일은 없을 것입니다. 

 

긴 글 읽어주셔서 감사합니다. 


< 참고자료 >

 

[사이트] #medium

javascript.plainenglish.io/7-javascript-design-patterns-every-developer-should-know-df9c40e7debf

 

7 JavaScript Design Patterns Every Developer Should Know

What if you can structure your source code like a beautiful template that can be applied to every project of the same kind?

javascript.plainenglish.io

<JavaScript> 7 JavaScript Design Patterns Every Developer Should know

Comments