7 шаблонов проектирования в JavaScript | Бредовое новогоднее эссе

Шаблоны проектирования JavaScript
7 шаблонов проектирования в JavaScript | Бредовое новогоднее эссе

Оригинальный адрес:Understanding Design Patterns in JavaScript

Оригинальный автор:Sukhjinder Arora

Переводчик: HelloGitHub-Роберт

Начиная новый проект, мы не должны сразу начинать программировать. Вместо этого вы должны сначала определить цель и объем проекта, а затем перечислить его функции или спецификации. Если вы начали программировать или работаете над сложным проектом, вам следует выбрать шаблон проектирования, который лучше всего подходит для вашего проекта.

Что такое шаблоны проектирования?

В программной инженерии шаблоны проектирования представляют собой многоразовые решения общих проблем при разработке программного обеспечения. Шаблоны проектирования также являются передовым опытом для опытных разработчиков. Его можно использовать как шаблон для программирования.

Зачем использовать шаблоны проектирования?

Многие инженеры либо считают шаблоны проектирования пустой тратой времени, либо не знают, как правильно их использовать. Но при правильном использовании шаблоны проектирования могут помочь вам написать лучший, более читаемый код, который легче поддерживать и понимать.

Что наиболее важно, шаблоны проектирования предоставляют разработчикам программного обеспечения общий словарь. Они позволяют человеку, изучающему ваш код, быстро понять его назначение. Например, если вы используете шаблон декоратора в своем проекте, новые разработчики могут быстро узнать, что делает этот код, поэтому они могут больше сосредоточиться на решении бизнес-задач, чем на попытках понять, что делает код.

Теперь, когда мы знаем, что такое шаблоны проектирования и почему они важны, давайте углубимся в 7 шаблонов проектирования в JavaScript.

1. Модульный режим

Модуль — это независимый фрагмент кода, поэтому мы можем обновлять модуль, не затрагивая другие части кода. Модули также позволяют нам избежать загрязнения пространства имен, создавая отдельные области видимости для переменных. Мы также можем повторно использовать модули в других проектах, когда они отделены от другого кода.

Модули являются неотъемлемой частью любого современного приложения JavaScript и помогают поддерживать чистоту, независимость и организованность кода. Существует множество способов создания модулей в JavaScript, одним из которых является шаблон модуля.

В отличие от других языков программирования, в JavaScript нет модификаторов доступа, то есть вы не можете объявлять переменные как частные или общедоступные. Следовательно, шаблон модуля также можно использовать для моделирования концепции инкапсуляции.

Шаблон модуля использует IIFE (немедленно вызываемые функциональные выражения), замыкания и область действия для имитации концепции инкапсуляции. Например:

const myModule = (function() {  
  const privateVariable = 'Hello World';  
  function privateMethod() {
    console.log(privateVariable);
  }
  return {
    publicMethod: function() {
      privateMethod();
    }
  }
})();
myModule.publicMethod();

Поскольку это IIFE, код выполняется немедленно, а возвращаемый объект присваиваетсяmyModuleПеременная. Благодаря замыканиям возвращаемый объект может по-прежнему обращаться к функциям и переменным, определенным внутри IIFE, даже после завершения IIFE.

Поэтому переменные и функции, определенные внутри IIFE, невидимы снаружи, что делает егоmyModuleчастные члены модуля.

После выполнения кода переменная myModule выглядит так:

const myModule = {
  publicMethod: function() {
    privateMethod();
  }};

Итак, когда мы звонимpublicMethod()когда он звонитprivateMethod()Например:

// Prints 'Hello World'
module.publicMethod();

2. Показать режим модуля

Выявление шаблона модуля — это небольшое улучшение шаблона модуля, разработанного Кристианом Хейльманном. Проблема с шаблоном модуля заключается в том, что мы должны создавать новые публичные функции для вызова приватных функций и переменных.

В этом шаблоне мы сопоставляем свойства возвращаемого объекта с частными функциями, которые будут доступны для всех. Вот почему он называется паттерном раскрывающего модуля. Например:

const myRevealingModule = (function() {  
  let privateVar = 'Peter';
  const publicVar  = 'Hello World';
  function privateFunction() {
    console.log('Name: '+ privateVar);
  }
  
  function publicSetName(name) {
    privateVar = name;
  }
  function publicGetName() {
    privateFunction();
  }
  /** reveal methods and variables by assigning them to object     properties */
return {
    setName: publicSetName,
    greeting: publicVar,
    getName: publicGetName
  };
})();
myRevealingModule.setName('Mark');
// prints Name: Mark
myRevealingModule.getName();

Этот шаблон упрощает нам определение того, какие функции и переменные являются общедоступными, и фактически улучшает читабельность кода. После выполнения кодаmyRevealingModuleЭто выглядит следующим образом:

const myRevealingModule = {
  setName: publicSetName,
  greeting: publicVar,
  getName: publicGetName
};

когда мы звонимmyRevealingModule.setName('Mark')При фактическом вызове внутреннегоpublicSetName. при звонкеmyRevealingModule.getName()При фактическом вызове внутреннегоpublicGetNameНапример:

myRevealingModule.setName('Mark');
// prints Name: Mark
myRevealingModule.getName();

По сравнению с шаблоном модуля преимущества раскрытия шаблона модуля заключаются в следующем:

  • Изменив одну строку в операторе return, мы можем изменить член с общедоступного на частный и наоборот.
  • Возвращаемый объект не содержит никаких определений функций, все правые выражения определены в IIFE, что делает код более понятным и легким для чтения.

3. Модуль ES6

До ES6 в JavaScript не было встроенных модулей, поэтому разработчикам приходилось полагаться на сторонние библиотеки или шаблон модуля для реализации модулей. Но начиная с ES6 в JavaScript встроены модули.

Модули ES6 хранятся в виде файлов. В одном файле может быть только один модуль. По умолчанию все внутри модуля является приватным. используяexportключевые слова для представления функций, переменных и классов. Код внутри модуля всегда выполняется в строгом режиме.

3.1 Экспорт модулей

Есть два способа экспортировать объявления функций и переменных:

  • добавить перед объявлениями функций и переменныхexportключевые слова. Например:
// utils.js
export const greeting = 'Hello World';
export function sum(num1, num2) {
  console.log('Sum:', num1, num2);
  return num1 + num2;
}
export function subtract(num1, num2) {
  console.log('Subtract:', num1, num2);
  return num1 - num2;
}
// This is a private function
function privateLog() {
  console.log('Private Function');
}
  • добавить в конце кодаexportключевые слова для представления функций и переменных. Например:
// utils.js
function multiply(num1, num2) {
  console.log('Multiply:', num1, num2);
  return num1 * num2;
}
function divide(num1, num2) {
  console.log('Divide:', num1, num2);
  return num1 / num2;
}
// This is a private function
function privateLog() {
  console.log('Private Function');
}
export {multiply, divide};

3.2 Импорт модулей

Подобно экспорту модулей, есть два варианта использованияimportключевое слово для импорта метода модуля. Например:

  • Импорт нескольких проектов одновременно
// main.js
// importing multiple items
import { sum, multiply } from './utils.js';
console.log(sum(3, 7));
console.log(multiply(3, 7));
  • импортировать все модули
// main.js
// importing all of module
import * as utils from './utils.js';
console.log(utils.sum(3, 7));
console.log(utils.multiply(3, 7));

3.3 Используйте псевдонимы при импорте и экспорте

  • переименовать экспорт
// utils.js
function sum(num1, num2) {
  console.log('Sum:', num1, num2);
  return num1 + num2;
}
function multiply(num1, num2) {
  console.log('Multiply:', num1, num2);
  return num1 * num2;
}
export {sum as add, multiply};
  • переименовать импорт
// main.js
import { add, multiply as mult } from './utils.js';
console.log(add(3, 7));
console.log(mult(3, 7));

В-четвертых, одноэлементный режим

Одноэлементный объект — это объект, который может быть создан только один раз. Шаблон singleton создаст новый экземпляр класса, если он не существует. Если экземпляр существует, возвращается только ссылка на этот объект. Повторные вызовы конструктора всегда будут получать один и тот же объект.

JavaScript — это язык, в который всегда были встроены синглтоны. Мы просто не называем их синглтонами, мы называем их объектными литералами. Например:

const user = {
  name: 'Peter',
  age: 25,
  job: 'Teacher',
  greet: function() {
    console.log('Hello!');
  }
};

Поскольку каждый объект в JavaScript занимает уникальное место в памяти, и когда мы вызываем методuserобъект, вы фактически возвращаете ссылку на этот объект.

Если мы попытаемсяuserСкопируйте переменную в другую переменную и измените эту переменную. Например:

const user1 = user;
user1.name = 'Mark';

Мы увидим, что оба объекта изменены, потому что объекты в JavaScript передаются по ссылке, а не по значению. Следовательно, в памяти находится только один объект. Например:

// prints 'Mark'
console.log(user.name);
// prints 'Mark'
console.log(user1.name);
// prints true
console.log(user === user1);

Одноэлементный шаблон может быть реализован с помощью конструкторов. Например:

let instance = null;

function User() {
  if(instance) {
    return instance;
  }
  instance = this;
  this.name = 'Peter';
  this.age = 25;
  
  return instance;
}
const user1 = new User();
const user2 = new User();
// prints true
console.log(user1 === user2);

Когда этот конструктор вызывается, он проверяетinstanceсуществует ли объект. Если объект не существует, он будетthisпеременная, присвоеннаяinstanceПеременная. Объект возвращается только в том случае, если он существует.

Синглтоны также могут быть реализованы с использованием шаблона модуля. Например:

const singleton = (function() {
  let instance;
  
  function init() {
    return {
      name: 'Peter',
      age: 24,
    };
  }
  return {
    getInstance: function() {
      if(!instance) {
        instance = init();
      }
      
      return instance;
    }
  }
})();
const instanceA = singleton.getInstance();
const instanceB = singleton.getInstance();
// prints true
console.log(instanceA === instanceB);

В приведенном выше коде мы вызываемsingleton.getInstanceметод для создания нового экземпляра. Этот метод просто возвращает экземпляр, если он уже существует. Если экземпляр не существует, вызовомinit()функция для создания нового экземпляра.

5. Заводской режим

Фабричный шаблон использует фабричные методы для создания объектов без указания конкретного класса или конструктора.

Фабричный шаблон используется для создания объектов без раскрытия логики инстанцирования. Этот шаблон можно использовать, когда нам нужно сгенерировать разные объекты на основе определенных условий. Например:

class Car{
  constructor(options) {
    this.doors = options.doors || 4;
    this.state = options.state || 'brand new';
    this.color = options.color || 'white';
  }
}
class Truck {
  constructor(options) {
    this.doors = options.doors || 4;
    this.state = options.state || 'used';
    this.color = options.color || 'black';
  }
}
class VehicleFactory {
  createVehicle(options) {
    if(options.vehicleType === 'car') {
      return new Car(options);
    } else if(options.vehicleType === 'truck') {
      return new Truck(options);
      }
  }
}

ЗдесьCarс однимTruckкласс (с некоторыми значениями по умолчанию), который используется для создания новыхcarа такжеtruck объект. и определяетVehicleFactoryкласс, используемый в соответствии сoptionsв объектеvehicleTypeсвойства для создания и возврата новых объектов.

const factory = new VehicleFactory();
const car = factory.createVehicle({
  vehicleType: 'car',
  doors: 4,
  color: 'silver',
  state: 'Brand New'
});
const truck= factory.createVehicle({
  vehicleType: 'truck',
  doors: 2,
  color: 'white',
  state: 'used'
});
// Prints Car {doors: 4, state: "Brand New", color: "silver"}
console.log(car);
// Prints Truck {doors: 2, state: "used", color: "white"}
console.log(truck);

я для классаVehicleFactoryсоздал новыйfactoryобъект. Затем мы передаем вызовfactory.createVehicleметод и проходoptionsобъект, которыйvehicleTypeСвойства могут бытьcarилиtruckсоздать новыйCarилиTruckобъект.

Шесть, режим декоратора

Шаблон декоратора используется для расширения функциональности объекта без изменения существующих классов или конструкторов. Этот режим можно использовать для добавления признаков к объектам без изменения базового кода.

Простой пример этого шаблона:

function Car(name) {
  this.name = name;
  // Default values
  this.color = 'White';
}
// Creating a new Object to decorate
const tesla= new Car('Tesla Model 3');
// Decorating the object with new functionality
tesla.setColor = function(color) {
  this.color = color;
}
tesla.setPrice = function(price) {
  this.price = price;
}
tesla.setColor('black');
tesla.setPrice(49000);
// prints black
console.log(tesla.color);

Более практичный пример этого шаблона:

Предположим, стоимость автомобиля зависит от количества его характеристик. Без шаблона декоратора нам пришлось бы создавать разные классы для различных комбинаций функций, каждый из которыхcostспособ расчета стоимости. Например:

class Car() {
}

class CarWithAC() {
}

class CarWithAutoTransmission {
}

class CarWithPowerLocks {
}

class CarWithACandPowerLocks {
}

Однако с помощью шаблона декоратора мы можем создать базовый классcarИ добавьте соответствующую логику стоимости к различным объектам с помощью функций декоратора.

class Car {
  constructor() {
  // Default Cost
  this.cost = function() {
  return 20000;
  }
}
}
// Decorator function
function carWithAC(car) {
  car.hasAC = true;
  const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 500;
  }
}
// Decorator function
function carWithAutoTransmission(car) {
  car.hasAutoTransmission = true;
   const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 2000;
  }
}
// Decorator function
function carWithPowerLocks(car) {
  car.hasPowerLocks = true;
  const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 500;
  }
}

Во-первых, мы создаем базовый класс для автомобиляCar. Затем создается декоратор для добавляемой функции, и этот декоратор начинается сCarОбъекты являются параметрами. Затем функция стоимости объекта переопределяется, возвращая обновленную стоимость автомобиля, и добавляется свойство, определяющее, была ли добавлена ​​функция.

Чтобы добавить новый функционал, нам просто нужно сделать следующее:

const car = new Car();
console.log(car.cost());
carWithAC(car);
carWithAutoTransmission(car);
carWithPowerLocks(car);

Наконец, мы можем рассчитать стоимость автомобиля следующим образом:

// Calculating total cost of the car
console.log(car.cost());

В заключение

Мы видели различные шаблоны проектирования, используемые в JavaScript, но здесь мы не коснулись шаблонов проектирования, которые могут быть реализованы в JavaScript.

Хотя важно понимать различные шаблоны проектирования, не менее важно не злоупотреблять ими. Прежде чем использовать шаблон проектирования, вы должны тщательно подумать, соответствует ли ваша проблема этому шаблону проектирования. Чтобы узнать, подходит ли шаблон для решения вашей задачи, вы должны хорошо изучить шаблон проектирования и его приложения.


Сфокусируйся наОфициальный аккаунт HelloGitHubПолучайте немедленные обновления.

Есть больше представлений о проектах с открытым исходным кодом и ценных проектов, которые ждут вас.