Когда дело доходит до написания чистого, удобного в сопровождении и эффективного кода, шаблоны проектирования играют решающую роль в мире разработки программного обеспечения. Шаблоны проектирования — это многократно используемые решения распространенных проблем, с которыми сталкиваются разработчики при проектировании и создании программных систем. Они обеспечивают структурированный подход к решению конкретных задач, упрощая создание кода, который не только надежен, но и прост для понимания и сопровождения.
В объектно-ориентированном программировании (ООП ) шаблоны проектирования служат рекомендациями по структурированию вашего кода таким образом, чтобы обеспечить гибкость, возможность повторного использования и масштабируемость. Они воплощают в себе лучшие практики и принципы проектирования, которые развивались и превращались в проверенные решения.
Шаблоны проектирования можно разделить на три основные группы:
Шаблоны создания. Эти шаблоны сосредоточены на механизмах создания объектов, пытаясь создавать объекты способом, подходящим для конкретной ситуации. Они абстрагируют процесс создания экземпляров, делая его более гибким и независимым от системы.
Структурные шаблоны. Структурные шаблоны связаны с композицией объектов, формируя отношения между объектами для создания более крупных и сложных структур. Они помогают определить, как объекты и классы могут быть объединены для формирования новых структур и обеспечения новой функциональности.
Поведенческие модели. Поведенческие модели связаны с общением между объектами, определяя, как они взаимодействуют и распределяют обязанности. Эти шаблоны помогают проектировать системы, в которых объекты взаимодействуют более гибко и эффективно.
Вот список некоторых распространенных шаблонов проектирования в каждой категории:
Шаблон наблюдателя: определяет зависимость между объектами «один ко многим», поэтому, когда один объект меняет состояние, все его зависимые объекты автоматически уведомляются и обновляются.
Шаблон стратегии: определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми.
Шаблон команды: инкапсулирует запрос как объект, тем самым позволяя параметризовать клиентов с помощью очередей, запросов и операций.
Шаблон состояния: позволяет объекту изменять свое поведение при изменении его внутреннего состояния, помещая поведение в отдельные классы.
Шаблон «Цепочка ответственности»: передает запрос по цепочке обработчиков, позволяя каждому обработчику решить: обработать запрос или передать его следующему обработчику в цепочке.
Шаблон посетителя: представляет собой операцию, выполняемую над элементами структуры объекта, что позволяет вам определять новые операции без изменения классов элементов.
В этом блоге мы углубимся в каждый из этих шаблонов проектирования, предоставив объяснения, реальные варианты использования и примеры кода JavaScript , которые помогут вам понять и эффективно реализовать их в своих проектах.
Шаблон Singleton — это творческий шаблон проектирования, который гарантирует, что класс имеет только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру. Этот шаблон особенно полезен, когда вы хотите ограничить количество экземпляров класса в вашем приложении и контролировать доступ к одному общему экземпляру.
В JavaScript реализация шаблона Singleton относительно проста благодаря гибкости языка. Давайте углубимся в простой пример того, как создать синглтон в JavaScript.
// Singleton instance let instance = null;
class Singleton { constructor() { if (!instance) { instance = this; // Your initialization code here } else { return instance; } } // Your methods and properties here }// Usage const singletonA = new Singleton(); const singletonB = new Singleton(); console.log(singletonA === singletonB); // Output: true (both variables reference the same instance)
В этом примере мы создаем класс Singleton с конструктором, который проверяет, существует ли уже экземпляр. Если экземпляр не существует, он создает его и присваивает его переменной экземпляра. Последующие вызовы конструктора возвращают существующий экземпляр, гарантируя, что существует только один экземпляр класса Singleton.
Шаблон Singleton полезен в различных сценариях, в том числе:
Хотя шаблон Singleton может быть полезным, важно использовать его разумно. Чрезмерное использование шаблона Singleton может привести к тесной связи кода и глобального состояния, что может затруднить поддержку и тестирование вашего приложения. Поэтому крайне важно взвесить все «за» и «против» и применять шаблон там, где он действительно повышает ценность вашей кодовой базы.
Шаблон «Фабрика» и шаблон «Абстрактная фабрика» — это творческие шаблоны проектирования, которые занимаются созданием объектов, но делают это по-разному и служат разным целям. Давайте рассмотрим каждый из этих шаблонов и посмотрим, как их можно реализовать в JavaScript.
Фабричный шаблон — это шаблон создания, который предоставляет интерфейс для создания объектов, но позволяет подклассам изменять тип создаваемых объектов. Он инкапсулирует процесс создания объекта, делая его более гибким и отделенным от клиентского кода.
// Product class class Product { constructor(name) { this.name = name; } }
// Factory for creating products class ProductFactory { createProduct(name) { return new Product(name); } }// Usage const factory = new ProductFactory(); const productA = factory.createProduct('Product A'); const productB = factory.createProduct('Product B');console.log(productA.name); // Output: 'Product A' console.log(productB.name); // Output: 'Product B'
В этом примере ProductFactory отвечает за создание экземпляров класса Product. Он абстрагирует процесс создания, позволяя создавать различные типы продуктов путем расширения фабрики.
Шаблон «Абстрактная фабрика» — это еще один шаблон создания, который предоставляет интерфейс для создания семейств связанных или зависимых объектов без указания их конкретных классов. Это позволяет создавать наборы объектов, которые гармонично работают вместе.
// Abstract Product classes class Button { render() {} }
class Checkbox { render() {} }// Concrete Product classes class MacButton extends Button { render() { return 'Render Mac button'; } }class MacCheckbox extends Checkbox { render() { return 'Render Mac checkbox'; } }class WindowsButton extends Button { render() { return 'Render Windows button'; } }class WindowsCheckbox extends Checkbox { render() { return 'Render Windows checkbox'; } }// Abstract Factory interface class GUIFactory { createButton() {} createCheckbox() {} }// Concrete Factories class MacFactory extends GUIFactory { createButton() { return new MacButton(); } createCheckbox() { return new MacCheckbox(); } }class WindowsFactory extends GUIFactory { createButton() { return new WindowsButton(); } createCheckbox() { return new WindowsCheckbox(); } }// Usage function createUI(factory) { const button = factory.createButton(); const checkbox = factory.createCheckbox(); return { button, checkbox }; }const macUI = createUI(new MacFactory()); console.log(macUI.button.render()); // Output: 'Render Mac button' console.log(macUI.checkbox.render()); // Output: 'Render Mac checkbox'const windowsUI = createUI(new WindowsFactory()); console.log(windowsUI.button.render()); // Output: 'Render Windows button' console.log(windowsUI.checkbox.render()); // Output: 'Render Windows checkbox'
В этом примере у нас есть две конкретные фабрики, MacFactory и WindowsFactory, каждая из которых способна создавать набор связанных компонентов пользовательского интерфейса (кнопок и флажков) для своих соответствующих платформ. Функция createUI позволяет создать целостный пользовательский интерфейс для конкретной платформы, используя соответствующую фабрику.
Когда использовать какой шаблон:
Шаблон Builder — это творческий шаблон проектирования, который отделяет построение сложного объекта от его представления, позволяя одному и тому же процессу конструирования создавать разные представления. Этот шаблон особенно полезен, когда у вас есть объект с большим количеством свойств и вы хотите упростить создание экземпляров, сохранив при этом гибкость.
В JavaScript шаблон Builder часто реализуется с использованием класса или объекта строителя, который направляет пошаговое создание сложного объекта. Давайте углубимся в пример, чтобы понять, как это работает.
// Product class with multiple properties class Product { constructor() { this.name = ''; this.price = 0; this.color = 'white'; // ... other properties }
// Additional methods can be defined here }// Builder for creating Product instances class ProductBuilder { constructor() { this.product = new Product(); } setName(name) { this.product.name = name; return this; // Return the builder for method chaining } setPrice(price) { this.product.price = price; return this; } setColor(color) { this.product.color = color; return this; } // Other methods to set additional properties build() { return this.product; // Return the fully constructed product } }// Usage const builder = new ProductBuilder();const productA = builder .setName('Product A') .setPrice(99.99) .setColor('blue') .build();const productB = builder .setName('Product B') .setPrice(49.99) .build();console.log(productA); console.log(productB);
В этом примере у нас есть класс Product с несколькими свойствами. Класс ProductBuilder помогает создавать экземпляры Product, предоставляя методы для пошаговой установки каждого свойства. Цепочка методов позволяет вам устанавливать несколько свойств плавным и удобочитаемым способом. Наконец, метод сборки возвращает полностью созданный экземпляр Product.
Шаблон Builder полезен в различных сценариях, в том числе:
Хотя шаблон Builder предлагает множество преимуществ, важно отметить, что он усложняет вашу кодовую базу, особенно если конструируемые объекты относительно просты. Поэтому важно оценить, оправдана ли сложность, вносимая Builder, для вашего конкретного случая использования.
Шаблон прототипа — это творческий шаблон проектирования, который позволяет создавать новые объекты путем копирования существующего объекта, известного как прототип. Он способствует созданию объектов без указания точного класса создаваемого объекта. Этот шаблон особенно полезен, когда вы хотите эффективно создавать экземпляры сложных объектов.
В JavaScript шаблон прототипа тесно связан со встроенным свойством prototype
и методом Object.create()
. Давайте рассмотрим, как реализовать и использовать шаблон прототипа в JavaScript.
// Prototype object const vehiclePrototype = { init(make, model) { this.make = make; this.model = model; }, getDetails() { return `${this.make} ${this.model}`; }, };
// Create new instances using the prototype const car1 = Object.create(vehiclePrototype); car1.init('Toyota', 'Camry');const car2 = Object.create(vehiclePrototype); car2.init('Honda', 'Civic');console.log(car1.getDetails()); // Output: 'Toyota Camry' console.log(car2.getDetails()); // Output: 'Honda Civic'
В этом примере мы определяем объект vehiclePrototype
с методами и свойствами, общими для всех транспортных средств. Мы используем Object.create()
для создания новых экземпляров (car1 и car2) на основе этого прототипа. Эти экземпляры наследуют свойства и методы прототипа, что позволяет эффективно создавать новые объекты с общим поведением.
Шаблон «Прототип» полезен в различных сценариях, в том числе:
Несмотря на то, что шаблон прототипа полезен, у него есть некоторые особенности:
Шаблон пула объектов — это шаблон творческого проектирования, который управляет пулом объектов многократного использования, чтобы минимизировать накладные расходы на создание и уничтожение объектов. Это особенно полезно, когда создание и уничтожение объектов является дорогостоящим или ресурсоемким. Шаблон пула объектов помогает повысить производительность и использование ресурсов за счет переработки и повторного использования объектов вместо создания новых с нуля.
В JavaScript вы можете реализовать шаблон пула объектов, используя массивы или собственные классы управления пулом. Давайте рассмотрим, как работает этот шаблон, на простом примере.
class ObjectPool { constructor(maxSize) { this.maxSize = maxSize; this.pool = []; }
create() { if (this.pool.length < this.maxSize) { // Create a new object and add it to the pool const obj = { /* Your object initialization code here */ }; this.pool.push(obj); return obj; } else { // Pool is full, cannot create more objects console.log('Pool is full. Cannot create more objects.'); return null; } } reuse() { if (this.pool.length > 0) { // Reuse an object from the pool return this.pool.pop(); } else { // Pool is empty, no objects available for reuse console.log('Pool is empty. No objects available for reuse.'); return null; } } release(obj) { // Release an object back to the pool for reuse this.pool.push(obj); } }// Usage const pool = new ObjectPool(5); // Create a pool with a maximum size of 5 objectsconst obj1 = pool.create(); const obj2 = pool.create(); const obj3 = pool.create();pool.release(obj2); // Release obj2 back to the pool for reuseconst obj4 = pool.reuse(); // Reuse an object from the pool (obj2)
В этом примере мы создаем класс ObjectPool, который управляет пулом объектов. Метод create создает новые объекты, когда пул не заполнен, метод reuse извлекает объект из пула для повторного использования, а метод Release возвращает объект в пул для будущего использования.
Шаблон пула объектов полезен в различных сценариях, в том числе:
Хотя шаблон пула объектов обеспечивает преимущества в производительности, важно учитывать следующее:
Шаблон адаптера — это шаблон структурного проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе. Он действует как мост между двумя несовместимыми интерфейсами, делая их совместимыми без изменения исходного кода. Этот шаблон особенно полезен, когда вам нужно интегрировать или использовать существующий код, который не совсем соответствует требованиям вашего приложения.
В JavaScript шаблон адаптера можно реализовать с помощью классов или функций, которые обертывают или адаптируют несовместимый интерфейс. Давайте рассмотрим, как реализовать и использовать шаблон адаптера в JavaScript, на практическом примере.
Предположим, у вас есть существующий класс OldSystem
с методом legacyRequest
:
class OldSystem { legacyRequest() { return 'Data from the legacy system'; } }
Теперь вы хотите использовать эту устаревшую систему в своем современном приложении, которому требуется другой интерфейс. Вы можете создать класс или функцию адаптера следующим образом:
class Adapter { constructor(oldSystem) { this.oldSystem = oldSystem; }
newRequest() { const legacyData = this.oldSystem.legacyRequest(); // Adapt the data or perform any necessary transformations return `Adapted: ${legacyData}`; } }
Теперь вы можете использовать класс Adaptor, чтобы сделать устаревшую систему совместимой с вашим современным приложением:
const oldSystem = new OldSystem(); const adapter = new Adapter(oldSystem);
const result = adapter.newRequest(); console.log(result); // Output: 'Adapted: Data from the legacy system'
В этом примере класс Adaptor оборачивает OldSystem и предоставляет новый интерфейс newRequest, совместимый с вашим современным приложением.
Шаблон адаптера полезен в различных сценариях, в том числе:
Хотя шаблон адаптера обеспечивает гибкость и совместимость, важно учитывать несколько моментов:
Шаблон «Декоратор» — это шаблон структурного проектирования, который позволяет динамически добавлять к объектам новое поведение или обязанности, не изменяя их существующий код. Это мощный способ расширить функциональность объектов, обернув их объектами-декораторами. Этот шаблон продвигает принцип «открыт для расширения, но закрыт для модификации», позволяя легко добавлять новые функции к объектам без изменения их базовой реализации.
В JavaScript шаблон «Декоратор» можно реализовать с помощью классов и композиции объектов. Давайте рассмотрим, как реализовать и использовать шаблон «Декоратор» в JavaScript, на практическом примере.
Предположим, у вас есть базовый класс Coffee
:
class Coffee { cost() { return 5; // Base cost of a regular coffee } } Now, you want to add decorators to your coffee to customize it with additional options, such as milk and sugar:
javascript Copy code class MilkDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 2; // Adding the cost of milk } }class SugarDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 1; // Adding the cost of sugar } }
Затем вы можете создать декорированные экземпляры кофе следующим образом:
const regularCoffee = new Coffee(); const coffeeWithMilk = new MilkDecorator(regularCoffee); const coffeeWithMilkAndSugar = new SugarDecorator(coffeeWithMilk);
console.log(regularCoffee.cost()); // Output: 5 console.log(coffeeWithMilk.cost()); // Output: 7 console.log(coffeeWithMilkAndSugar.cost()); // Output: 8
В этом примере у нас есть класс Coffee, представляющий базовый кофе. Классы MilkDecorator и SugarDecorator — это декораторы, которые оборачивают объект кофе и добавляют стоимость молока и сахара соответственно к базовой стоимости.
Шаблон «Декоратор» полезен в различных сценариях, в том числе:
Несмотря на то, что шаблон «Декоратор» универсален, важно учитывать несколько соображений:
Шаблон прокси — это шаблон структурного проектирования, который предоставляет суррогат или заполнитель для другого объекта для управления доступом к нему. Он действует как посредник или оболочка вокруг целевого объекта, позволяя добавлять дополнительные варианты поведения, контролировать доступ или откладывать создание объекта. Шаблон прокси полезен в различных сценариях, таких как реализация отложенной загрузки, контроль доступа и ведение журнала.
В JavaScript прокси можно создавать с помощью встроенного объекта Proxy
. Давайте рассмотрим, как реализовать и использовать шаблон прокси в JavaScript, на практических примерах.
Предположим, у вас есть ресурсоемкий объект, который вы хотите лениво загружать только тогда, когда это необходимо. Вы можете использовать прокси для достижения ленивой загрузки:
class ExpensiveResource { constructor() { console.log('Creating an expensive resource...'); }
fetchData() { console.log('Fetching data...'); } }class LazyResourceProxy { constructor() { this.resource = null; } fetchData() { if (!this.resource) { this.resource = new ExpensiveResource(); } this.resource.fetchData(); } }// Usage const lazyResource = new LazyResourceProxy(); // The actual resource is created and data is fetched only when needed lazyResource.fetchData();
В этом примере LazyResourceProxy действует как суррогат ExpensiveResource, создавая фактический ресурс только при первом вызове метода fetchData.
Вы также можете использовать прокси для управления доступом к объектам и их свойствам:
const user = { username: 'john_doe', password: 'secret123', };
const userProxy = new Proxy(user, { get(target, property) { if (property === 'password') { throw new Error('Access denied to password.'); } return target[property]; }, });console.log(userProxy.username); // Output: 'john_doe' console.log(userProxy.password); // Throws an error: 'Access denied to password.'
В этом примере прокси-сервер перехватывает операцию get и ограничивает доступ к свойству пароля.
Шаблон прокси полезен в различных сценариях, в том числе:
При использовании шаблона прокси имейте в виду следующие соображения:
Составной шаблон — это структурный шаблон проектирования, который позволяет объединять объекты в древовидные структуры для представления иерархий «части-целые». Это позволяет клиентам единообразно обрабатывать отдельные объекты и композиции объектов. Составной шаблон особенно полезен, когда вам нужно работать со сложными структурами, состоящими из более мелких связанных объектов, сохраняя при этом согласованный интерфейс.
В JavaScript вы можете реализовать составной шаблон, используя классы или объекты, имеющие общий интерфейс, что позволяет создавать иерархические структуры. Давайте рассмотрим, как реализовать и использовать составной шаблон в JavaScript, на практических примерах.
Предположим, вы создаете приложение графического дизайна, которому необходимо работать как с простыми фигурами, так и со сложными композициями фигур (например, группами). Вы можете использовать составной шаблон для представления этой иерархии:
// Component interface class Graphic { draw() {} }
// Leaf class (represents simple shapes) class Circle extends Graphic { constructor() { super(); // Circle-specific properties and methods } draw() { // Draw a circle } }// Composite class (represents groups of shapes) class Group extends Graphic { constructor() { super(); this.graphics = []; } add(graphic) { this.graphics.push(graphic); } draw() { // Draw each graphic in the group this.graphics.forEach((graphic) => graphic.draw()); } }// Usage const circle1 = new Circle(); const circle2 = new Circle(); const group = new Group();group.add(circle1); group.add(circle2);group.draw(); // Draws both circles in the group
В этом примере класс Graphic служит интерфейсом компонента. Класс Circle представляет простые фигуры, а класс Group представляет композиции фигур. Оба класса Circle и Group реализуют метод draw, что позволяет единообразно обрабатывать их при рендеринге.
Составной шаблон полезен в различных сценариях, в том числе:
При работе с составным шаблоном учитывайте следующее:
Шаблон Bridge — это шаблон структурного проектирования, который отделяет абстракцию объекта от его реализации. Это позволяет вам создать мост между ними, позволяя им изменяться независимо. Этот шаблон особенно полезен, когда вы хотите избежать постоянной привязки между абстракцией и ее реализацией, что делает ваш код более гибким и удобным в сопровождении.
В JavaScript шаблон Bridge можно реализовать с помощью классов и объектов, которые предоставляют абстрактный интерфейс для абстракции и различные конкретные реализации для различных платформ или функций. Давайте рассмотрим, как реализовать и использовать шаблон Bridge в JavaScript, на практических примерах.
Предположим, вы создаете приложение для рисования, которое может отображать фигуры на разных платформах, таких как веб-браузеры и мобильные устройства. Вы можете использовать шаблон Bridge, чтобы отделить фигуры рисования (абстракцию) от логики рендеринга (реализации):
// Abstraction class Shape { constructor(renderer) { this.renderer = renderer; }
draw() { // Delegating the drawing to the specific renderer this.renderer.renderShape(this); } }// Implementor interface class Renderer { renderShape(shape) {} }// Concrete Implementors class WebRenderer extends Renderer { renderShape(shape) { console.log(`Drawing on the web: ${shape.constructor.name}`); } }class MobileRenderer extends Renderer { renderShape(shape) { console.log(`Drawing on mobile: ${shape.constructor.name}`); } }// Concrete Abstractions (Shapes) class Circle extends Shape { constructor(renderer) { super(renderer); } }class Square extends Shape { constructor(renderer) { super(renderer); } }// Usage const webRenderer = new WebRenderer(); const mobileRenderer = new MobileRenderer();const circle = new Circle(webRenderer); const square = new Square(mobileRenderer);circle.draw(); // Output: Drawing on the web: Circle square.draw(); // Output: Drawing on mobile: Square
В этом примере класс Shape представляет абстракцию (рисуемые фигуры), а класс Renderer представляет интерфейс разработчика (логику рендеринга, зависящую от платформы). Различные конкретные реализации (WebRenderer и MobileRenderer) обеспечивают логику рендеринга для веб-платформ и мобильных платформ соответственно. Классы Circle и Square представляют собой конкретные абстракции, представляющие фигуры.
Шаблон «Мост» полезен в различных сценариях, в том числе:
При использовании шаблона моста учитывайте следующее:
Шаблон Flyweight — это шаблон структурного проектирования, целью которого является снижение потребления памяти и повышение производительности за счет совместного использования общих частей объектов. Это достигается за счет отделения внутреннего состояния объекта (общего и неизменяемого) от его внешнего состояния (уникального и контекстно-зависимого). Этот шаблон особенно полезен, когда у вас большое количество похожих объектов и вы хотите минимизировать использование памяти.
В JavaScript вы можете реализовать шаблон легковеса, используя классы или объекты для представления общего внутреннего состояния и индивидуального внешнего состояния. Давайте рассмотрим, как реализовать и использовать шаблон «Приспособленный вес» в JavaScript, на практических примерах.
Предположим, вы разрабатываете текстовый редактор, которому необходимо отображать большой объем текста. Вместо создания отдельного объекта для каждого символа вы можете использовать шаблон Flyweight для совместного использования объектов символов, если они имеют одинаковые внутренние свойства (например, шрифт и размер):
class Character { constructor(char, font, size) { this.char = char; this.font = font; this.size = size; }
render() { console.log(`Rendering character "${this.char}" in ${this.font}, size ${this.size}`); } }class CharacterFactory { constructor() { this.characters = {}; } getCharacter(char, font, size) { const key = `${char}-${font}-${size}`; if (!this.characters[key]) { this.characters[key] = new Character(char, font, size); } return this.characters[key]; } }// Usage const factory = new CharacterFactory();const charA1 = factory.getCharacter('A', 'Arial', 12); const charA2 = factory.getCharacter('A', 'Arial', 12); const charB = factory.getCharacter('B', 'Times New Roman', 14);charA1.render(); // Output: Rendering character "A" in Arial, size 12 charA2.render(); // Output: Rendering character "A" in Arial, size 12 (shared instance) charB.render(); // Output: Rendering character "B" in Times New Roman, size 14
В этом примере класс символов представляет отдельные символы с внутренними свойствами, такими как сам символ, шрифт и размер. Класс CharacterFactory гарантирует, что символы с одинаковыми внутренними свойствами будут использоваться совместно, а не дублироваться.
Модель «Наилегчайший вес» полезна в различных сценариях, в том числе:
При использовании шаблона «Минивес» учитывайте следующее:
Паттерн «Наблюдатель» — это поведенческий шаблон проектирования, который устанавливает зависимость «один ко многим» между объектами. Это позволяет одному объекту (субъекту или наблюдаемому) уведомлять нескольких наблюдателей (слушателей) об изменениях в его состоянии или данных. Этот шаблон обычно используется для реализации распределенных систем обработки событий, где изменения состояния одного объекта вызывают действия в других зависимых объектах.
В JavaScript вы можете реализовать шаблон наблюдателя, используя собственные классы или встроенные функции, такие как прослушиватели событий и метод addEventListener
. Давайте рассмотрим, как реализовать и использовать шаблон наблюдателя в JavaScript, на практических примерах.
Предположим, вы создаете погодное приложение и хотите, чтобы различные части пользовательского интерфейса обновлялись при изменении погодных условий. Вы можете использовать собственную реализацию шаблона наблюдателя:
class WeatherStation { constructor() { this.observers = []; }
addObserver(observer) { this.observers.push(observer); } removeObserver(observer) { const index = this.observers.indexOf(observer); if (index !== -1) { this.observers.splice(index, 1); } } notifyObservers() { this.observers.forEach((observer) => { observer.update(this); }); } setWeatherData(weatherData) { this.weatherData = weatherData; this.notifyObservers(); } }class WeatherDisplay { update(weatherStation) { console.log(`Current weather: ${weatherStation.weatherData}`); } }// Usage const weatherStation = new WeatherStation(); const display1 = new WeatherDisplay(); const display2 = new WeatherDisplay();weatherStation.addObserver(display1); weatherStation.addObserver(display2);weatherStation.setWeatherData('Sunny'); // Both displays update with the new weather data
В этом примере WeatherStation выступает в роли субъекта, который уведомляет наблюдателей (объекты отображения) при изменении данных о погоде. Наблюдатели подписываются на субъект с помощью метода addObserver и реализуют метод обновления для реагирования на изменения.
JavaScript также предоставляет встроенный способ реализации шаблона наблюдателя с использованием прослушивателей событий:
class NewsPublisher { constructor() { this.subscribers = []; }
subscribe(subscriber) { this.subscribers.push(subscriber); } unsubscribe(subscriber) { const index = this.subscribers.indexOf(subscriber); if (index !== -1) { this.subscribers.splice(index, 1); } } publishNews(news) { this.subscribers.forEach((subscriber) => { subscriber(news); }); } }// Usage const publisher = new NewsPublisher();const subscriber1 = (news) => { console.log(`Subscriber 1 received news: ${news}`); };const subscriber2 = (news) => { console.log(`Subscriber 2 received news: ${news}`); };publisher.subscribe(subscriber1); publisher.subscribe(subscriber2);publisher.publishNews('Breaking News: Important Announcement');
В этом примере NewsPublisher выступает в качестве темы, а подписчики (функции) добавляются с помощью метода subscribe. МетодPublishNews уведомляет подписчиков, вызывая их функции с новостями.
Паттерн «Наблюдатель» полезен в различных сценариях, в том числе:
При использовании шаблона наблюдателя учитывайте следующее:
Шаблон стратегии — это шаблон поведенческого проектирования, который позволяет вам определить семейство взаимозаменяемых алгоритмов, инкапсулировать каждый из них и сделать их взаимозаменяемыми. Это позволяет клиентам динамически выбирать соответствующий алгоритм во время выполнения. Этот шаблон обеспечивает гибкость и возможность повторного использования, отделяя поведение алгоритма от контекста, в котором он используется.
В JavaScript вы можете реализовать шаблон стратегии, используя объекты или функции для представления различных стратегий, а также объект контекста, который может переключаться между этими стратегиями. Давайте рассмотрим, как реализовать и использовать шаблон стратегии в JavaScript, на практических примерах.
Предположим, вы разрабатываете приложение для электронной коммерции и хотите рассчитать скидки для разных типов клиентов. Вы можете использовать шаблон стратегии для инкапсуляции стратегий скидок:
// Discount Strategies const regularCustomerDiscount = (amount) => amount * 0.1; // 10% discount const premiumCustomerDiscount = (amount) => amount * 0.2; // 20% discount
// Context class ShoppingCart { constructor(discountStrategy) { this.items = []; this.discountStrategy = discountStrategy; } addItem(item) { this.items.push(item); } calculateTotal() { const subtotal = this.items.reduce((total, item) => total + item.price, 0); return subtotal - this.discountStrategy(subtotal); } }// Usage const regularCustomerCart = new ShoppingCart(regularCustomerDiscount); const premiumCustomerCart = new ShoppingCart(premiumCustomerDiscount);regularCustomerCart.addItem({ name: 'Item 1', price: 50 }); premiumCustomerCart.addItem({ name: 'Item 2', price: 100 });console.log(`Regular Customer Total: $${regularCustomerCart.calculateTotal()}`); // Output: $45 (after 10% discount) console.log(`Premium Customer Total: $${premiumCustomerCart.calculateTotal()}`); // Output: $80 (after 20% discount)
В этом примере мы определяем две стратегии скидок как функции (regularCustomerDiscount и premiumCustomerDiscount). Класс ShoppingCart принимает в качестве параметра стратегию скидок и рассчитывает общую цену на основе выбранной стратегии.
Модель стратегии полезна в различных сценариях, в том числе:
При использовании шаблона стратегии учитывайте следующее:
Шаблон команды — это шаблон поведенческого проектирования, который превращает запрос или простую операцию в автономный объект. Он позволяет параметризовать объекты с различными запросами, задерживать или ставить в очередь выполнение запроса, а также поддерживать отменяемые операции. Этот шаблон отделяет отправителя запроса от его получателя, что упрощает расширение и поддержку кода.
В JavaScript вы можете реализовать шаблон команды, используя объекты или классы для представления команд и средств вызова, выполняющих эти команды. Давайте рассмотрим, как реализовать и использовать шаблон команды в JavaScript, на практических примерах.
Предположим, вы разрабатываете приложение удаленного управления умным домом и хотите создать гибкий способ управления различными устройствами.
Вы можете использовать шаблон команды:
// Command interface class Command { execute() {} }
// Concrete Commands class LightOnCommand extends Command { constructor(light) { super(); this.light = light; } execute() { this.light.turnOn(); } }class LightOffCommand extends Command { constructor(light) { super(); this.light = light; } execute() { this.light.turnOff(); } }// Receiver (Device) class Light { turnOn() { console.log('Light is on.'); } turnOff() { console.log('Light is off.'); } }// Invoker (Remote Control) class RemoteControl { constructor() { this.commands = []; } addCommand(command) { this.commands.push(command); } executeCommands() { this.commands.forEach((command) => { command.execute(); }); } }// Usage const livingRoomLight = new Light(); const kitchenLight = new Light();const livingRoomLightOn = new LightOnCommand(livingRoomLight); const livingRoomLightOff = new LightOffCommand(livingRoomLight); const kitchenLightOn = new LightOnCommand(kitchenLight); const kitchenLightOff = new LightOffCommand(kitchenLight);const remoteControl = new RemoteControl();remoteControl.addCommand(livingRoomLightOn); remoteControl.addCommand(kitchenLightOff);remoteControl.executeCommands(); // Output: "Light is on." (for living room) // Output: "Light is off." (for kitchen)
В этом примере шаблон команды используется для инкапсуляции действий по включению и выключению света. RemoteControl служит инициатором, а конкретные команды (например, LightOnCommand и LightOffCommand) инкапсулируют действия, которые необходимо выполнить.
Шаблон команды полезен в различных сценариях, в том числе:
При использовании шаблона команды учитывайте следующее:
Шаблон состояния — это шаблон поведенческого проектирования, который позволяет объекту изменять свое поведение при изменении его внутреннего состояния. Он инкапсулирует состояния как отдельные классы и делегирует поведение текущему объекту состояния. Этот шаблон помогает управлять сложными переходами состояний и продвигает принцип «открыто-закрыто», упрощая добавление новых состояний без изменения существующего кода.
В JavaScript вы можете реализовать шаблон состояния, используя классы для представления состояний и объект контекста, который делегирует свое поведение текущему состоянию. Давайте рассмотрим, как реализовать и использовать шаблон состояния в JavaScript, на практических примерах.
Предположим, вы разрабатываете торговый автомат, который продает различные продукты. Поведение торгового автомата зависит от его текущего состояния, например «Готов», «Выдача» или «Распродано». Вы можете использовать шаблон состояния для моделирования такого поведения:
// State interface class VendingMachineState { insertMoney() {} ejectMoney() {} selectProduct() {} dispenseProduct() {} }
// Concrete States class ReadyState extends VendingMachineState { constructor(machine) { super(); this.machine = machine; } insertMoney() { console.log('Money inserted.'); this.machine.setState(this.machine.getDispensingState()); } selectProduct() { console.log('Please insert money first.'); } }class DispensingState extends VendingMachineState { constructor(machine) { super(); this.machine = machine; } dispenseProduct() { console.log('Product dispensed.'); this.machine.setState(this.machine.getReadyState()); } }class VendingMachine { constructor() { this.readyState = new ReadyState(this); this.dispensingState = new DispensingState(this); this.currentState = this.readyState; } setState(state) { this.currentState = state; } getReadyState() { return this.readyState; } getDispensingState() { return this.dispensingState; } insertMoney() { this.currentState.insertMoney(); } selectProduct() { this.currentState.selectProduct(); } dispenseProduct() { this.currentState.dispenseProduct(); } }// Usage const vendingMachine = new VendingMachine();vendingMachine.selectProduct(); // Output: "Please insert money first." vendingMachine.insertMoney(); // Output: "Money inserted." vendingMachine.dispenseProduct(); // Output: "Product dispensed."
В этом примере шаблон состояния используется для управления поведением торгового автомата. Такие состояния, как «Готовность» и «Выдача», представлены как отдельные классы, а контекст (торговый автомат) делегирует свое поведение текущему состоянию.
Шаблон состояния полезен в различных сценариях, в том числе:
При использовании шаблона состояния учитывайте следующее:
Шаблон «Цепочка ответственности» — это шаблон поведенческого проектирования, который помогает вам построить цепочку объектов для обработки запроса. Каждый объект в цепочке имеет возможность обработать запрос или передать его следующему объекту в цепочке. Он отделяет отправителя запроса от его получателей и позволяет нескольким обработчикам быть в цепочке. Этот шаблон обеспечивает гибкость и расширяемость, позволяя добавлять или изменять обработчики, не затрагивая клиентский код.
В JavaScript вы можете реализовать шаблон цепочки ответственности, используя объекты или классы, которые представляют обработчики и клиента, инициирующего запросы. Каждый обработчик имеет ссылку на следующий обработчик в цепочке. Давайте рассмотрим, как реализовать и использовать шаблон цепочки ответственности в JavaScript, на практических примерах.
Предположим, вы разрабатываете систему обработки заказов и хотите обрабатывать заказы на основе их общей суммы. Вы можете использовать шаблон «Цепочка ответственности» для создания цепочки обработчиков, каждый из которых отвечает за обработку заказов в определенном ценовом диапазоне:
// Handler interface class OrderHandler { constructor() { this.nextHandler = null; }
setNextHandler(handler) { this.nextHandler = handler; } handleOrder(order) { if (this.canHandleOrder(order)) { this.processOrder(order); } else if (this.nextHandler) { this.nextHandler.handleOrder(order); } else { console.log('No handler can process this order.'); } } canHandleOrder(order) {} processOrder(order) {} }// Concrete Handlers class SmallOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount <= 100; } processOrder(order) { console.log(`Processing small order for ${order.amount}`); } }class MediumOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount <= 500; } processOrder(order) { console.log(`Processing medium order for ${order.amount}`); } }class LargeOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount > 500; } processOrder(order) { console.log(`Processing large order for ${order.amount}`); } }// Client class Order { constructor(amount) { this.amount = amount; } }// Usage const smallOrderHandler = new SmallOrderHandler(); const mediumOrderHandler = new MediumOrderHandler(); const largeOrderHandler = new LargeOrderHandler();smallOrderHandler.setNextHandler(mediumOrderHandler); mediumOrderHandler.setNextHandler(largeOrderHandler);const order1 = new Order(80); const order2 = new Order(250); const order3 = new Order(600);smallOrderHandler.handleOrder(order1); // Output: "Processing small order for 80" smallOrderHandler.handleOrder(order2); // Output: "Processing medium order for 250" smallOrderHandler.handleOrder(order3); // Output: "Processing large order for 600"
В этом примере шаблон «Цепочка ответственности» используется для обработки заказов разной суммы. Каждый из обработчиков, таких как SmallOrderHandler, MediumOrderHandler и LargeOrderHandler, определяет, могут ли они обработать заказ, в зависимости от суммы заказа. Если они могут, они обрабатывают это; в противном случае они передают заказ следующему обработчику в цепочке.
Модель цепочки ответственности полезна в различных сценариях, в том числе:
При использовании шаблона «Цепочка ответственности» учитывайте следующее:
Шаблон «Посетитель» — это шаблон поведенческого проектирования, который позволяет отделить алгоритм от структуры объекта, над которым он работает. Он предоставляет возможность добавлять новые операции к объектам без изменения их классов, что упрощает расширение функциональности для сложных иерархий объектов. Этот шаблон особенно полезен, когда у вас есть набор отдельных элементов и вы хотите выполнять над ними различные операции, не изменяя их код.
В JavaScript вы можете реализовать шаблон посетителя, используя функции или классы для представления посетителей, посещающих элементы внутри структуры объекта. Давайте рассмотрим, как реализовать и использовать шаблон посетителя в JavaScript, на практических примерах.
Предположим, вы разрабатываете систему управления контентом, в которой есть различные типы элементов контента, такие как статьи, изображения и видео. Вы хотите выполнять различные операции, такие как рендеринг и экспорт, над этими элементами, не изменяя их классы. Вы можете использовать шаблон посетителя:
// Element interface class ContentElement { accept(visitor) {} }
// Concrete Elements class Article extends ContentElement { accept(visitor) { visitor.visitArticle(this); } }class Image extends ContentElement { accept(visitor) { visitor.visitImage(this); } }class Video extends ContentElement { accept(visitor) { visitor.visitVideo(this); } }// Visitor interface class Visitor { visitArticle(article) {} visitImage(image) {} visitVideo(video) {} }// Concrete Visitors class RendererVisitor extends Visitor { visitArticle(article) { console.log(`Rendering article: ${article.title}`); } visitImage(image) { console.log(`Rendering image: ${image.caption}`); } visitVideo(video) { console.log(`Rendering video: ${video.title}`); } }class ExportVisitor extends Visitor { visitArticle(article) { console.log(`Exporting article: ${article.title}`); } visitImage(image) { console.log(`Exporting image: ${image.caption}`); } visitVideo(video) { console.log(`Exporting video: ${video.title}`); } }// Usage const elements = [new Article('Article 1'), new Image('Image 1'), new Video('Video 1')]; const renderer = new RendererVisitor(); const exporter = new ExportVisitor();elements.forEach((element) => { element.accept(renderer); element.accept(exporter); });
В этом примере у нас есть такие элементы контента, как «Статья», «Изображение» и «Видео», и мы хотим выполнять над ними операции рендеринга и экспорта без изменения их классов. Мы достигаем этого, реализуя классы посетителей, такие как RendererVisitor и ExportVisitor, которые посещают элементы и выполняют нужные операции.
Шаблон «Посетитель» полезен в различных сценариях, в том числе:
При использовании шаблона «Посетитель» учитывайте следующее:
В этом всестороннем исследовании шаблонов проектирования в JavaScript мы углубились в различные шаблоны, которые позволяют разработчикам создавать гибкий, удобный в сопровождении и эффективный код. Каждый шаблон проектирования решает конкретные проблемы и предлагает элегантные решения распространенных проблем проектирования программного обеспечения.
Мы начали с понимания фундаментальной концепции шаблонов проектирования и разделили их на три основные группы: творческие, структурные и поведенческие шаблоны. В каждой категории мы рассмотрели популярные шаблоны проектирования и продемонстрировали их практическую реализацию на JavaScript.
Вот краткий обзор ключевых шаблонов проектирования, которые мы рассмотрели:
Шаблоны создания: эти шаблоны сосредоточены на механизмах создания объектов, включая шаблон Singleton для обеспечения единственного экземпляра класса, шаблоны Factory и Abstract Factory для создания объектов с гибкими фабриками, шаблон Builder для пошагового построения сложных объектов, шаблон прототипа для клонирования. объекты и шаблон пула объектов для эффективного повторного использования объектов.
Структурные шаблоны. Эти шаблоны связаны с композицией объектов, предоставляя способы создания сложных структур из более простых компонентов. Мы изучили шаблон адаптера для адаптации интерфейсов, шаблон декоратора для динамического добавления поведения к объектам, шаблон прокси для управления доступом к объектам, составной шаблон для объединения объектов в древовидные структуры, шаблон моста для отделения абстракции от реализации и приспособленец. Шаблон для минимизации использования памяти за счет совместного использования общего состояния.
Поведенческие паттерны. Эти паттерны связаны с взаимодействием и общением между объектами. Мы рассмотрели шаблон наблюдателя для реализации распределенных систем обработки событий, шаблон стратегии для инкапсуляции взаимозаменяемых алгоритмов, шаблон команды для преобразования запросов в автономные объекты, шаблон состояния для управления поведением объекта на основе внутреннего состояния, шаблон цепочки ответственности для построения цепочка обработчиков для обработки запросов и шаблон посетителя для отделения алгоритмов от структур объектов.
Шаблоны проектирования — ценные инструменты в наборе инструментов разработчика, позволяющие создавать масштабируемые и поддерживаемые базы кода. Понимание и применение этих шаблонов в ваших проектах JavaScript позволит вам писать более эффективное, адаптируемое и надежное программное обеспечение.
Помните, что шаблоны проектирования не являются универсальными решениями, и их применимость зависит от конкретных требований и задач вашего проекта. Тщательно продумайте, когда и как их применять, чтобы добиться наилучших результатов.
По мере того, как вы продолжаете расти как разработчик JavaScript, освоение этих шаблонов проектирования позволит вам уверенно и творчески решать сложные задачи проектирования программного обеспечения. Независимо от того, создаете ли вы веб-приложения, игровые движки или любое другое программное обеспечение, шаблоны проектирования станут вашими союзниками в создании элегантного и удобного в сопровождении кода. Приятного кодирования!
Также опубликовано здесь .