Принципы разработки программного обеспечения

Шаблоны проектирования: руководство по принципам разработки JavaScript для чистого и масштабируемого кода

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

Введение — шаблоны проектирования JavaScript

Суть шаблонов проектирования

Привет, народ! За годы работы старшим инженером-программистом я понял, что создание чистого и масштабируемого кода зависит не только от того, что мы создаем, но и структура нашего кода также играет важную роль, и именно в этом помогают шаблоны проектирования. с.

Шаблоны проектирования — что это такое?

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

Сила шаблонов проектирования JavaScript

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

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

Изучение типов шаблонов проектирования в JavaScript

Креативные шаблоны проектирования: создание объектов

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

Шаблоны создания помогают абстрагировать процесс создания экземпляров, делая систему независимой от того, как ее объекты создаются, компонуются и представляются.

Шаблон Factory

Шаблон Factory обеспечивает способ создания объектов, но позволяет подклассам изменять тип создаваемых объектов.

function CarFactory() {
  this.createCar = function(model) {
    let car;
    if (model === 'Sedan') {
      car = new Sedan();
    } else if (model === 'SUV') {
      car = new SUV();
    }
    return car;
  };
}

Шаблон одиночка

Шаблон Singleton запрещает классу создавать экземпляры нескольких объектов, и он полезен, когда для управления действиями требуется один объект.

let Singleton = (function () {
  let instance;
 
  function createInstance() {
    return new Object("I am the instance");
  }
 
  return {
    getInstance: function () {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

Шаблон "Строитель"

Шаблон Builder предполагает, что клиент создает сложный объект, сосредоточив внимание только на его типе и содержимом, и он заботится о сборке объекта.

function CarBuilder() {
  this.car = null;

  this.step1 = function () {
    this.car = new Car();
  };

  this.step2 = function () {
    this.car.addParts();
  };

  this.get = function () {
    return this.car;
  };
}

Шаблоны структурного проектирования: формирование нашего кода

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

Шаблон адаптера

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

class OldCalculator {
  constructor() {
    this.operations = function(term1, term2, operation) {
      switch (operation) {
        case 'add':
          return term1 + term2;
        case 'sub':
          return term1 - term2;
        default:
          return NaN;
      }
    };
  }
}

class NewCalculator {
  constructor() {
    this.add = function(term1, term2) {
      return term1 + term2;
    };
    this.sub = function(term1, term2) {
      return term1 - term2;
    };
  }
}

class CalculatorAdapter {
  constructor() {
    const newCalc = new NewCalculator();
    
    this.operations = function(term1, term2, operation) {
      switch (operation) {
        case 'add':
          return newCalc.add(term1, term2);
        case 'sub':
          return newCalc.sub(term1, term2);
        default:
          return NaN;
      }
    };
  }
}

Шаблон декоратора

Шаблон декоратора описывает поведение, которое должно быть добавлено к отдельному объекту статически или динамически, не влияя на поведение других объектов того же класса.

function Car(name) {
  this.name = name;
}

Car.prototype.getName = function () {
  return this.name;
};

function DecoratedCar(car, color, price) {
  this.car = car;
  this.color = color;
  this.price = price;
}

DecoratedCar.prototype.getName = function () {
  return this.car.getName() + ' has color ' + this.color + ' and price ' + this.price;
};

Шаблон прокси

Шаблон прокси предоставляет суррогатный объект или объект-заполнитель для управления доступом к исходному объекту.

function NetworkAccess() {
  this.connect = function () {
    console.log('Connected to the network.');
  };
}

function NetworkProxy() {
  this.network = new NetworkAccess();
  this.connect = function () {
    console.log('Using network proxy.');
    this.network.connect();
  };
}

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

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

Шаблон наблюдателя

Паттерн Observer определяет зависимость между объектами «один ко многим», так что при изменении состояния одного объекта все его зависимые объекты получают уведомление и автоматически обновляются.

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

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

  unsubscribe(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }

  notifyAll(data) {
    for (let i = 0; i < this.observers.length; i++) {
      this.observers[i].notify(data);
    }
  }
}

class Observer {
  notify(data) {
    console.log(`Observer received: ${data}`);
  }
}

Шаблон стратегии

Паттерн стратегии позволяет заменять метод во время выполнения любым другим методом (стратегией) без ведома клиента, и, по сути, это группа взаимозаменяемых алгоритмов.

class Shipping {
  setStrategy(strategy) {
    this.strategy = strategy;
  }

  calculate(parcel) {
    return this.strategy.calculate(parcel);
  }
}

class UPS {
  calculate(parcel) {
    return `$${parcel.weight * 1.75}`;
  }
}

class FedEx {
  calculate(parcel) {
    return `$${parcel.weight * 2.45}`;
  }
}

class USPS {
  calculate(parcel) {
    return `$${parcel.weight * 1.25}`;
  }
}

Шаблон команды

Шаблон команды обеспечивает инкапсуляцию операций в объектах, не зная специфики запроса.

class Switch {
  execute(command) {
    command.execute();
  }
}

class TurnOnCommand {
  constructor(light) {
    this.light = light;
  }

  execute() {
    this.light.turnOn();
  }
}

class Light {
  turnOn() {
    console.log('Light is on');
  }

  turnOff() {
    console.log('Light is off');
  }
}

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

Практическое применение шаблонов проектирования JavaScript

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

Создание профиля пользователя

Предположим, вам поручили создать систему профилей пользователей для сайта социальной сети: с помощью Factory Pattern вы можете упростить этот процесс, создавая профили с предопределенными шаблонами.

function UserFactory() {
  this.createUser = function(type) {
    let user;

    if (type === 'Personal') {
      user = new PersonalUser();
    } else if (type === 'Business') {
      user = new BusinessUser();
    }

    user.type = type;
    user.say = function() {
      console.log(this.type + ": profile created");
    }
    return user;
  }
}

Интеграция со сторонним API

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

class ThirdPartyAPI {
  constructor() {
    this.specificRequest = function() {
      return "Third-party API response";
    };
  }
}

class Adapter {
  constructor(thirdPartyAPI) {
    this.request = function() {
      return thirdPartyAPI.specificRequest();
    };
  }
}

// Using the Adapter
const thirdPartyAPI = new ThirdPartyAPI();
const adapter = new Adapter(thirdPartyAPI);
adapter.request();

Добавление функций в профили пользователей

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

function User(name) {
  this.name = name;
}

User.prototype.getName = function () {
  return this.name;
};

function DecoratedUser(user, badge, theme) {
  this.user = user;
  this.badge = badge;
  this.theme = theme;
}

DecoratedUser.prototype.getName = function () {
  return `${this.user.getName()}, Badge: ${this.badge}, Theme: ${this.theme}`;
};

Взаимодействие пользователей с сообщениями

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

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

  like(user) {
    this.notifyAll(`Post liked by ${user}`);
  }

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

  notifyAll(message) {
    for (let observer of this.observers) {
      observer.notify(message);
    }
  }
}

class User {
  notify(message) {
    console.log(`User notified: ${message}`);
  }
}

Различные способы доставки

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

class Shipping {
  setStrategy(strategy) {
    this.strategy = strategy;
  }

  calculate(parcel) {
    return this.strategy.calculate(parcel);
  }
}

class UPS {
  calculate(parcel) {
    return `$${parcel.weight * 1.75}`;
  }
}

class FedEx {
  calculate(parcel) {
    return `$${parcel.weight * 2.45}`;
  }
}

class USPS {
  calculate(parcel) {
    return `$${parcel.weight * 1.25}`;
  }
}

Настройка темы веб-сайта

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

// Factory Pattern
function ThemeFactory() {
  this.createTheme = function(type) {
    let theme;

    if (type === 'Dark') {
      theme = new DarkTheme();
    } else if (type === 'Light') {
      theme = new LightTheme();
    }

    theme.type = type;
    return theme;
  }
}

// Decorator Pattern
function DecoratedTheme(theme, color) {
  this.theme = theme;
  this.color = color;
}

DecoratedTheme.prototype.getName = function () {
  return this.theme.getName() + ' in ' + this.color + ' color';
};

Сайт электронной коммерции со специальными предложениями

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

// Strategy Pattern
class SpecialOffer {
  apply(product) {
    // abstract method
  }
}

class BlackFridayOffer extends SpecialOffer {
  apply(product) {
    product.price *= 0.8;  // 20% discount
  }
}

class ChristmasOffer extends SpecialOffer {
  apply(product) {
    product.price *= 0.85;  // 15% discount
  }
}

// Observer Pattern
class Product {
  constructor(price) {
    this.price = price;
    this.observers = [];
  }

  setPrice(price) {
    this.price = price;
    this.notifyAll();
  }

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

  notifyAll() {
    for (let observer of this.observers) {
      observer.notify(this);
    }
  }
}

class Customer {
  notify(product) {
    console.log(`Product price has been updated to $${product.price}`);
  }
}

Система мониторинга производительности

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

// Factory Pattern
function ModuleFactory() {
  this.createModule = function(type) {
    let module;

    if (type === 'Database') {
      module = new DatabaseModule();
    } else if (type === 'Network') {
      module = new NetworkModule();
    }

    module.type = type;
    return module;
  }
}

// Proxy Pattern
class PerformanceProxy {
  constructor(module) {
    this.module = module;
  }

  monitor() {
    console.log('Monitoring performance...');
    // Delegate the call to the original object
    this.module.monitor();
  }
}

Приложение чата

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

// Singleton Pattern
let ChatRoom = (function() {
  let instance;

  function createInstance() {
    let object = new Object("ChatRoom");
    return object;
  }

  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

// Observer Pattern
class User {
  notify(message) {
    console.log(`Received message: ${message}`);
  }
}

Система онлайн-игр

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

// Factory Pattern
function CharacterFactory() {
  this.createCharacter = function(type) {
    let character;

    if (type === 'Warrior') {
      character = new Warrior();
    } else if (type === 'Mage') {
      character = new Mage();
    }

    character.type = type;
    return character;
  }
}

// Observer Pattern
class Character {
  hit() {
    // Notify all observers
  }
}

// Decorator Pattern
function EnhancedCharacter(character, ability) {
  this.character = character;
  this.ability = ability;
}

EnhancedCharacter.prototype.useAbility = function() {
  console.log(`Using ability: ${this.ability}`);
};

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

Подводные камни, которых следует избегать при использовании шаблонов проектирования

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

Вот несколько распространенных ошибок, которых следует избегать:

Чрезмерное использование шаблонов проектирования

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

Неправильное применение шаблонов проектирования

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

Неполное понимание шаблона

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

Игнорирование принципа простоты

Принцип KISS (Keep It Simple, Stupid) является ключевым в разработке программного обеспечения, и я всегда предпочитаю упоминать его: иногда простое процедурное решение может оказаться более подходящим, чем сложный шаблон проектирования.

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

Резюме и рекомендации

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

Дальнейшее обучение

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

Тем, кто заинтересован в более глубоком изучении JavaScript, я предлагаю следующие статьи из моей библиотеки: «Использование синтаксиса деструктурирования и распространения объектов JavaScript: варианты использования и лучшие практики» и JavaScript Promises: Глубокое погружение в обработку ошибок и рекомендации



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



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

📒 Подсказка: если вы решите купить эту книгу, вы поможете мне в моей работе без каких-либо дополнительных затрат. Ваша поддержка очень ценна! 🎖️

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .