Принципи за разработка на софтуер

Модели на проектиране: Ръководство за принципите за разработка на JavaScript за чист и мащабируем код

Открийте силата на дизайнерските модели в javascript и научете как да ги използвате за чист, поддържаем и мащабируем код, който издържа изпитанието на времето.

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

Същността на дизайнерските модели

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

Шаблони за проектиране — какви са те?

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

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

JavaScript със своята гъвкавост поддържа много стилове на програмиране и, въпреки че това е значително предимство, може също да направи езика да изглежда непосилен, но дизайнерските модели предлагат метод да поддържаме нашия код организиран и ефективен.

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

Изследване на типовете дизайнерски модели в JavaScript

Creational Design Patterns: Конструиране на обекти

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

Моделите на създаване помагат да се абстрахира процесът на инстанциране, правейки системата независима от това как нейните обекти са създадени, съставени и представени.

Фабричният шаблон

Factory Pattern предоставя начин за създаване на обекти, но позволява на подкласовете да променят типа на обектите, които ще бъдат създадени.

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

Моделът Singleton

Singleton Pattern ограничава даден клас от инстанциране на множество обекти и е полезен, когато един обект е необходим за управление на действията.

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();
  };
}

Модели на поведенчески дизайн: Управление на сътрудничеството на обекти

Моделите на поведенчески дизайн се занимават с комуникацията между обектите, как обектите работят и изпълняват своите отговорности през повечето време, увеличавайки гъвкавостта при осъществяването на комуникация между обектите.

Моделът на наблюдателя

Моделът на наблюдателя дефинира зависимостта "един към много" между обектите, така че когато един обект промени състоянието, всички негови зависими се уведомяват и актуализират автоматично.

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, за да създавате различни тематични обекти и след това да използвате Decorator Pattern, за да добавите допълнителни функции към тези теми.

// 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 Pattern, за да гарантирате, че има само един екземпляр на класа 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 Pattern, за да създадете различни типове игрови герои, Observer Pattern може да се използва за информиране на други играчи, когато герой е ударен, а Decorator Pattern може да се използва за добавяне на специални способности към персонаж.

// 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 обещания: Дълбоко потапяне в обработката на грешки и най-добрите практики



И двете статии предлагат практическа представа за специфични аспекти на JavaScript, които могат да подобрят работата ви по разработка, точно като шаблони за проектиране.



Винаги ми е интересно да чуя от вас, така че, моля, споделете вашите мисли, опит или въпроси в коментарите — всички се учим един от друг и вашите прозрения могат да бъдат от полза за общността.

📒 Бърз съвет: Ако решите да закупите тази книга, вие подавате ръка на работата ми, и всичко това без допълнителни разходи. Вашата подкрепа е дълбоко ценена! 🎖️

Повече съдържание в PlainEnglish.io.

Регистрирайте се за нашия безплатен седмичен бюлетин. Следвайте ни в Twitter, LinkedIn, YouTube и Discord .